Repository: xataio/pgstream (go)

Issue:
Error when using a db that has dot (`.`) in the name
I'm using the `build` folder and [this article](https://xata.io/blog/postgres-webhooks-with-pgstream) as a guide.


When the database is called `example` it works correctly:
```
pgstream init --pgurl "postgres://postgres:postgres@db:5432/example?sslmode=disable"
```


When the database is called `example".com` it fails:
```
pgstream init --pgurl "postgres://postgres:postgres@db:5432/example.com?sslmode=disable"

ERROR   failed to create replication slot: ERROR: replication slot name "pgstream_example.com_slot" contains invalid character (SQLSTATE 42602)
Error: failed to create replication slot: ERROR: replication slot name "pgstream_example.com_slot" contains invalid character (SQLSTATE 42602)
```

And when I run the `pgstream` compose service I get this:

```
using config file: pg2webhook.env

   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.12.0
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on [::]:9900
2024-12-01T08:30:05.254120391Z INF logger.go:37 > adding translation to processor...
2024-12-01T08:30:05.254173041Z INF logger.go:37 > running subscription server...
2024-12-01T08:30:05.254439849Z INF logger.go:37 > running postgres listener...
2024-12-01T08:30:05.25441942Z INF logger.go:37 > subscription server listening on: :9900... service=webhook_subscription_server
2024-12-01T08:30:05.254569887Z INF logger.go:37 > running webhook notifier...
2024-12-01T08:30:05.254794494Z INF logger.go:37 > replication handler: identifySystem success db_name=example.com position=14545114416 service=postgres_replication_handler slot_name=pgstream_example.com_slot system_id=7296245781040128039 timeline=1
Error: start replication: read last position: no rows in result set
```



Pull request description:
Replace period on replication slot name
Resolves #90 

Patch:
diff --git a/internal/postgres/pg_replication_conn.go b/internal/postgres/pg_replication_conn.go
index 7924c55..4aabb7f 100644
--- a/internal/postgres/pg_replication_conn.go
+++ b/internal/postgres/pg_replication_conn.go
@@ -6,6 +6,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"strings"
 	"time"
 
 	"github.com/jackc/pglogrepl"
@@ -121,6 +122,9 @@ func (c *ReplicationConn) Close(ctx context.Context) error {
 }
 
 func DefaultReplicationSlotName(dbName string) string {
+	// sanitise the dbName before creating the replication slot name to ensure
+	// the name does not contain invalid characters.
+	dbName = strings.ReplaceAll(dbName, ".", "_")
 	return "pgstream_" + dbName + "_slot"
 }
 


Test patch:
diff --git a/internal/postgres/pg_test.go b/internal/postgres/pg_test.go
new file mode 100644
index 0000000..e2fe44b
--- /dev/null
+++ b/internal/postgres/pg_test.go
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: Apache-2.0
+
+package postgres
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func Test_DefaultReplicationSlotName(t *testing.T) {
+	t.Parallel()
+
+	tests := []struct {
+		name         string
+		wantSlotName string
+	}{
+		{
+			name:         "example",
+			wantSlotName: "pgstream_example_slot",
+		},
+		{
+			name:         "example.com",
+			wantSlotName: "pgstream_example_com_slot",
+		},
+		{
+			name:         "example.test.com",
+			wantSlotName: "pgstream_example_test_com_slot",
+		},
+	}
+
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+
+			slotName := DefaultReplicationSlotName(tc.name)
+			require.Equal(t, tc.wantSlotName, slotName)
+		})
+	}
+}