Skip to content

Commit a771834

Browse files
ivanauthclaude
andcommitted
fix: correct MySQL DSN format, CockroachDB port, and URL encoding
- MySQL: Use DSN format (user:pass@tcp(host:port)/db?parseTime=true) instead of URI format (mysql://...) which is incompatible with driver - CockroachDB: Use correct default port 26257 instead of 5432 - URL encode credentials for Postgres/CockroachDB to handle special chars - Update tests to use correct MySQL DSN format 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 5fc3cc8 commit a771834

3 files changed

Lines changed: 67 additions & 12 deletions

File tree

docs/spicedb.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,21 @@ spicedb datastore gc [flags]
8787
--datastore-connect-rate duration rate at which new connections are allowed to the datastore (at a rate of 1/duration) (cockroach driver only) (default 100ms)
8888
--datastore-connection-balancing enable connection balancing between database nodes (cockroach driver only) (default true)
8989
--datastore-credentials-provider-name string retrieve datastore credentials dynamically using ("aws-iam")
90+
--datastore-database string datastore database name (used to build connection URI if datastore-conn-uri is not provided)
9091
--datastore-disable-watch-support disable watch support (only enable if you absolutely do not need watch)
9192
--datastore-engine string type of datastore to initialize ("cockroachdb", "mysql", "postgres", "spanner") (default "memory")
9293
--datastore-experimental-column-optimization enable experimental column optimization (default true)
9394
--datastore-follower-read-delay-duration duration amount of time to subtract from non-sync revision timestamps to ensure they are sufficiently in the past to enable follower reads (cockroach and spanner drivers only) or read replicas (postgres and mysql drivers only) (default 4.8s)
9495
--datastore-gc-interval duration amount of time between passes of garbage collection (postgres driver only) (default 3m0s)
9596
--datastore-gc-max-operation-time duration maximum amount of time a garbage collection pass can operate before timing out (postgres driver only) (default 1m0s)
9697
--datastore-gc-window duration amount of time before revisions are garbage collected (default 24h0m0s)
98+
--datastore-host string datastore host (used to build connection URI if datastore-conn-uri is not provided)
9799
--datastore-include-query-parameters-in-traces include query parameters in traces (postgres and CRDB drivers only)
98100
--datastore-max-tx-retries int number of times a retriable transaction should be retried (default 10)
99101
--datastore-migration-phase string datastore-specific flag that should be used to signal to a datastore which phase of a multi-step migration it is in
100102
--datastore-mysql-table-prefix string prefix to add to the name of all SpiceDB database tables
103+
--datastore-password string datastore password (used to build connection URI if datastore-conn-uri is not provided)
104+
--datastore-port string datastore port (used to build connection URI if datastore-conn-uri is not provided)
101105
--datastore-prometheus-metrics set to false to disabled metrics from the datastore (do not use for Spanner; setting to false will disable metrics to the configured metrics store in Spanner) (default true)
102106
--datastore-read-replica-conn-pool-read-healthcheck-interval duration amount of time between connection health checks in a remote datastore's connection pool (default 30s)
103107
--datastore-read-replica-conn-pool-read-max-idletime duration maximum amount of time a connection can idle in a remote datastore's connection pool (default 30m0s)
@@ -125,6 +129,7 @@ spicedb datastore gc [flags]
125129
--datastore-spanner-min-sessions uint minimum number of sessions across all Spanner gRPC connections the client can have at a given time (default 100)
126130
--datastore-tx-overlap-key string static key to touch when writing to ensure transactions overlap (only used if --datastore-tx-overlap-strategy=static is set; cockroach driver only) (default "key")
127131
--datastore-tx-overlap-strategy string strategy to generate transaction overlap keys ("request", "prefix", "static", "insecure") (cockroach driver only - see https://spicedb.dev/d/crdb-overlap for details) (default "static")
132+
--datastore-username string datastore username (used to build connection URI if datastore-conn-uri is not provided)
128133
--datastore-watch-buffer-length uint16 how large the watch buffer should be before blocking (default 1024)
129134
--datastore-watch-buffer-write-timeout duration how long the watch buffer should queue before forcefully disconnecting the reader (default 1s)
130135
--datastore-watch-connect-timeout duration how long the watch connection should wait before timing out (cockroachdb driver only) (default 1s)
@@ -256,17 +261,21 @@ spicedb datastore repair [flags]
256261
--datastore-connect-rate duration rate at which new connections are allowed to the datastore (at a rate of 1/duration) (cockroach driver only) (default 100ms)
257262
--datastore-connection-balancing enable connection balancing between database nodes (cockroach driver only) (default true)
258263
--datastore-credentials-provider-name string retrieve datastore credentials dynamically using ("aws-iam")
264+
--datastore-database string datastore database name (used to build connection URI if datastore-conn-uri is not provided)
259265
--datastore-disable-watch-support disable watch support (only enable if you absolutely do not need watch)
260266
--datastore-engine string type of datastore to initialize ("cockroachdb", "mysql", "postgres", "spanner") (default "memory")
261267
--datastore-experimental-column-optimization enable experimental column optimization (default true)
262268
--datastore-follower-read-delay-duration duration amount of time to subtract from non-sync revision timestamps to ensure they are sufficiently in the past to enable follower reads (cockroach and spanner drivers only) or read replicas (postgres and mysql drivers only) (default 4.8s)
263269
--datastore-gc-interval duration amount of time between passes of garbage collection (postgres driver only) (default 3m0s)
264270
--datastore-gc-max-operation-time duration maximum amount of time a garbage collection pass can operate before timing out (postgres driver only) (default 1m0s)
265271
--datastore-gc-window duration amount of time before revisions are garbage collected (default 24h0m0s)
272+
--datastore-host string datastore host (used to build connection URI if datastore-conn-uri is not provided)
266273
--datastore-include-query-parameters-in-traces include query parameters in traces (postgres and CRDB drivers only)
267274
--datastore-max-tx-retries int number of times a retriable transaction should be retried (default 10)
268275
--datastore-migration-phase string datastore-specific flag that should be used to signal to a datastore which phase of a multi-step migration it is in
269276
--datastore-mysql-table-prefix string prefix to add to the name of all SpiceDB database tables
277+
--datastore-password string datastore password (used to build connection URI if datastore-conn-uri is not provided)
278+
--datastore-port string datastore port (used to build connection URI if datastore-conn-uri is not provided)
270279
--datastore-prometheus-metrics set to false to disabled metrics from the datastore (do not use for Spanner; setting to false will disable metrics to the configured metrics store in Spanner) (default true)
271280
--datastore-read-replica-conn-pool-read-healthcheck-interval duration amount of time between connection health checks in a remote datastore's connection pool (default 30s)
272281
--datastore-read-replica-conn-pool-read-max-idletime duration maximum amount of time a connection can idle in a remote datastore's connection pool (default 30m0s)
@@ -294,6 +303,7 @@ spicedb datastore repair [flags]
294303
--datastore-spanner-min-sessions uint minimum number of sessions across all Spanner gRPC connections the client can have at a given time (default 100)
295304
--datastore-tx-overlap-key string static key to touch when writing to ensure transactions overlap (only used if --datastore-tx-overlap-strategy=static is set; cockroach driver only) (default "key")
296305
--datastore-tx-overlap-strategy string strategy to generate transaction overlap keys ("request", "prefix", "static", "insecure") (cockroach driver only - see https://spicedb.dev/d/crdb-overlap for details) (default "static")
306+
--datastore-username string datastore username (used to build connection URI if datastore-conn-uri is not provided)
297307
--datastore-watch-buffer-length uint16 how large the watch buffer should be before blocking (default 1024)
298308
--datastore-watch-buffer-write-timeout duration how long the watch buffer should queue before forcefully disconnecting the reader (default 1s)
299309
--datastore-watch-connect-timeout duration how long the watch connection should wait before timing out (cockroachdb driver only) (default 1s)
@@ -417,17 +427,21 @@ spicedb serve [flags]
417427
--datastore-connect-rate duration rate at which new connections are allowed to the datastore (at a rate of 1/duration) (cockroach driver only) (default 100ms)
418428
--datastore-connection-balancing enable connection balancing between database nodes (cockroach driver only) (default true)
419429
--datastore-credentials-provider-name string retrieve datastore credentials dynamically using ("aws-iam")
430+
--datastore-database string datastore database name (used to build connection URI if datastore-conn-uri is not provided)
420431
--datastore-disable-watch-support disable watch support (only enable if you absolutely do not need watch)
421432
--datastore-engine string type of datastore to initialize ("cockroachdb", "mysql", "postgres", "spanner") (default "memory")
422433
--datastore-experimental-column-optimization enable experimental column optimization (default true)
423434
--datastore-follower-read-delay-duration duration amount of time to subtract from non-sync revision timestamps to ensure they are sufficiently in the past to enable follower reads (cockroach and spanner drivers only) or read replicas (postgres and mysql drivers only) (default 4.8s)
424435
--datastore-gc-interval duration amount of time between passes of garbage collection (postgres driver only) (default 3m0s)
425436
--datastore-gc-max-operation-time duration maximum amount of time a garbage collection pass can operate before timing out (postgres driver only) (default 1m0s)
426437
--datastore-gc-window duration amount of time before revisions are garbage collected (default 24h0m0s)
438+
--datastore-host string datastore host (used to build connection URI if datastore-conn-uri is not provided)
427439
--datastore-include-query-parameters-in-traces include query parameters in traces (postgres and CRDB drivers only)
428440
--datastore-max-tx-retries int number of times a retriable transaction should be retried (default 10)
429441
--datastore-migration-phase string datastore-specific flag that should be used to signal to a datastore which phase of a multi-step migration it is in
430442
--datastore-mysql-table-prefix string prefix to add to the name of all SpiceDB database tables
443+
--datastore-password string datastore password (used to build connection URI if datastore-conn-uri is not provided)
444+
--datastore-port string datastore port (used to build connection URI if datastore-conn-uri is not provided)
431445
--datastore-prometheus-metrics set to false to disabled metrics from the datastore (do not use for Spanner; setting to false will disable metrics to the configured metrics store in Spanner) (default true)
432446
--datastore-read-replica-conn-pool-read-healthcheck-interval duration amount of time between connection health checks in a remote datastore's connection pool (default 30s)
433447
--datastore-read-replica-conn-pool-read-max-idletime duration maximum amount of time a connection can idle in a remote datastore's connection pool (default 30m0s)
@@ -456,6 +470,7 @@ spicedb serve [flags]
456470
--datastore-spanner-min-sessions uint minimum number of sessions across all Spanner gRPC connections the client can have at a given time (default 100)
457471
--datastore-tx-overlap-key string static key to touch when writing to ensure transactions overlap (only used if --datastore-tx-overlap-strategy=static is set; cockroach driver only) (default "key")
458472
--datastore-tx-overlap-strategy string strategy to generate transaction overlap keys ("request", "prefix", "static", "insecure") (cockroach driver only - see https://spicedb.dev/d/crdb-overlap for details) (default "static")
473+
--datastore-username string datastore username (used to build connection URI if datastore-conn-uri is not provided)
459474
--datastore-watch-buffer-length uint16 how large the watch buffer should be before blocking (default 1024)
460475
--datastore-watch-buffer-write-timeout duration how long the watch buffer should queue before forcefully disconnecting the reader (default 1s)
461476
--datastore-watch-connect-timeout duration how long the watch connection should wait before timing out (cockroachdb driver only) (default 1s)

pkg/cmd/datastore/datastore.go

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"net/url"
89
"os"
910
"strings"
1011
"time"
@@ -394,19 +395,20 @@ func buildConnectionURI(engine, host, port, username, password, database string)
394395
// Set default port based on engine if not provided
395396
if port == "" {
396397
switch engine {
397-
case PostgresEngine, CockroachEngine:
398+
case PostgresEngine:
398399
port = "5432"
400+
case CockroachEngine:
401+
port = "26257"
399402
case MySQLEngine:
400403
port = "3306"
401404
}
402405
}
403406

404-
// URL encode credentials to handle special characters
405-
encodedUsername := username
407+
// URL encode credentials to handle special characters like @, :, /
408+
encodedUsername := url.QueryEscape(username)
406409
encodedPassword := ""
407410
if password != "" {
408-
// Note: we don't URL-encode here because the URL parsing libraries handle it
409-
encodedPassword = ":" + password
411+
encodedPassword = ":" + url.QueryEscape(password)
410412
}
411413

412414
// Build URI based on engine type
@@ -415,16 +417,24 @@ func buildConnectionURI(engine, host, port, username, password, database string)
415417
// Format: postgres://username:password@host:port/database
416418
uri := fmt.Sprintf("postgres://%s%s@%s:%s", encodedUsername, encodedPassword, host, port)
417419
if database != "" {
418-
uri += "/" + database
420+
uri += "/" + url.PathEscape(database)
419421
}
420422
return uri
421423
case MySQLEngine:
422-
// Format: mysql://username:password@host:port/database
423-
uri := fmt.Sprintf("mysql://%s%s@%s:%s", encodedUsername, encodedPassword, host, port)
424+
// MySQL uses DSN format: username:password@tcp(host:port)/database?parseTime=true
425+
// Special characters in username/password must be URL-encoded per go-sql-driver/mysql docs
426+
encodedMySQLUsername := url.QueryEscape(username)
427+
encodedMySQLPassword := ""
428+
if password != "" {
429+
encodedMySQLPassword = ":" + url.QueryEscape(password)
430+
}
431+
dsn := fmt.Sprintf("%s%s@tcp(%s:%s)", encodedMySQLUsername, encodedMySQLPassword, host, port)
424432
if database != "" {
425-
uri += "/" + database
433+
dsn += "/" + database
426434
}
427-
return uri
435+
// parseTime=true is required for MySQL to handle time.Time correctly
436+
dsn += "?parseTime=true"
437+
return dsn
428438
default:
429439
return ""
430440
}

pkg/cmd/datastore/datastore_test.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ func TestBuildConnectionURI(t *testing.T) {
147147
username: "root",
148148
password: "rootpass",
149149
database: "mydb",
150-
expected: "mysql://root:rootpass@localhost:3306/mydb",
150+
expected: "root:rootpass@tcp(localhost:3306)/mydb?parseTime=true",
151151
},
152152
{
153153
name: "mysql with default port",
@@ -157,7 +157,7 @@ func TestBuildConnectionURI(t *testing.T) {
157157
username: "root",
158158
password: "rootpass",
159159
database: "mydb",
160-
expected: "mysql://root:rootpass@localhost:3306/mydb",
160+
expected: "root:rootpass@tcp(localhost:3306)/mydb?parseTime=true",
161161
},
162162
{
163163
name: "empty host returns empty",
@@ -179,6 +179,36 @@ func TestBuildConnectionURI(t *testing.T) {
179179
database: "db",
180180
expected: "",
181181
},
182+
{
183+
name: "postgres with special characters in password",
184+
engine: PostgresEngine,
185+
host: "localhost",
186+
port: "5432",
187+
username: "testuser",
188+
password: "pass@word:with/special",
189+
database: "testdb",
190+
expected: "postgres://testuser:pass%40word%3Awith%2Fspecial@localhost:5432/testdb",
191+
},
192+
{
193+
name: "mysql with special characters in password",
194+
engine: MySQLEngine,
195+
host: "localhost",
196+
port: "3306",
197+
username: "root",
198+
password: "pass@word:with/special",
199+
database: "mydb",
200+
expected: "root:pass%40word%3Awith%2Fspecial@tcp(localhost:3306)/mydb?parseTime=true",
201+
},
202+
{
203+
name: "cockroachdb with special characters in username and password",
204+
engine: CockroachEngine,
205+
host: "localhost",
206+
port: "26257",
207+
username: "user@domain",
208+
password: "p@ss:word",
209+
database: "testdb",
210+
expected: "postgres://user%40domain:p%40ss%3Aword@localhost:26257/testdb",
211+
},
182212
}
183213

184214
for _, tt := range tests {

0 commit comments

Comments
 (0)