diff --git a/go.mod b/go.mod index 203119d4..20343203 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.26.3 require ( github.com/getsentry/sentry-go v0.46.1 github.com/golang-migrate/migrate/v4 v4.19.1 + github.com/jackc/pgx/v5 v5.5.4 github.com/jellydator/ttlcache/v3 v3.4.0 - github.com/lib/pq v1.12.3 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 go.opentelemetry.io/otel v1.43.0 @@ -121,6 +121,10 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jgautheron/goconst v1.8.2 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jjti/go-spancheck v0.6.5 // indirect @@ -138,6 +142,7 @@ require ( github.com/ldez/tagliatelle v0.7.2 // indirect github.com/ldez/usetesting v0.5.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect + github.com/lib/pq v1.12.3 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/macabu/inamedparam v0.2.0 // indirect github.com/magiconair/properties v1.8.6 // indirect @@ -219,6 +224,7 @@ require ( go.uber.org/multierr v1.10.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.50.0 // indirect golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 // indirect golang.org/x/mod v0.34.0 // indirect golang.org/x/net v0.53.0 // indirect diff --git a/go.sum b/go.sum index e05ba383..be7ed962 100644 --- a/go.sum +++ b/go.sum @@ -388,6 +388,16 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= +github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= +github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= github.com/jgautheron/goconst v1.8.2 h1:y0XF7X8CikZ93fSNT6WBTb/NElBu9IjaY7CCYQrCMX4= @@ -632,6 +642,7 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= @@ -731,6 +742,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1079,6 +1092,7 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/adapters/accountrepository/postgres.go b/internal/adapters/accountrepository/postgres.go index c69068dc..56318467 100644 --- a/internal/adapters/accountrepository/postgres.go +++ b/internal/adapters/accountrepository/postgres.go @@ -7,8 +7,8 @@ import ( "fmt" "time" + "github.com/jackc/pgx/v5" "github.com/jmoiron/sqlx" - "github.com/lib/pq" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" @@ -67,7 +67,7 @@ func (p *Postgres) StoreAccount(ctx context.Context, account domain.Account) err } defer txx.Rollback() - _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pq.QuoteIdentifier(p.schema))) + _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pgx.Identifier{p.schema}.Sanitize())) if err != nil { err := fmt.Errorf("failed to set search path: %w", err) reporting.Report(ctx, err, map[string]string{ @@ -168,7 +168,7 @@ func (p *Postgres) RemoveUsername(ctx context.Context, username string) error { _, err := p.db.ExecContext(ctx, fmt.Sprintf(` DELETE FROM %s.usernames WHERE lower(username) = lower($1)`, - pq.QuoteIdentifier(p.schema), + pgx.Identifier{p.schema}.Sanitize(), ), username, ) @@ -200,7 +200,7 @@ func (p *Postgres) GetAccountByUUID(ctx context.Context, uuid string) (domain.Ac player_uuid, username, queried_at FROM %s.usernames WHERE player_uuid = $1`, - pq.QuoteIdentifier(p.schema), + pgx.Identifier{p.schema}.Sanitize(), ), uuid, ) @@ -232,7 +232,7 @@ func (p *Postgres) GetAccountByUsername(ctx context.Context, username string) (d player_uuid, username, queried_at FROM %s.usernames WHERE lower(username) = lower($1)`, - pq.QuoteIdentifier(p.schema), + pgx.Identifier{p.schema}.Sanitize(), ), username, ) diff --git a/internal/adapters/accountrepository/postgres_test.go b/internal/adapters/accountrepository/postgres_test.go index 9d9e0e58..715b0793 100644 --- a/internal/adapters/accountrepository/postgres_test.go +++ b/internal/adapters/accountrepository/postgres_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" + "github.com/jackc/pgx/v5" "github.com/jmoiron/sqlx" - "github.com/lib/pq" "github.com/stretchr/testify/require" "github.com/Amund211/flashlight/internal/adapters/database" @@ -22,9 +22,9 @@ func newPostgres(t *testing.T, db *sqlx.DB, schemaSuffix string) *Postgres { logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) - db.MustExec(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", pq.QuoteIdentifier(schema))) + db.MustExec(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", pgx.Identifier{schema}.Sanitize())) - migrator := database.NewDatabaseMigrator(db, logger) + migrator := database.NewDatabaseMigrator(database.LocalConnectionString, logger) err := migrator.Migrate(t.Context(), schema) require.NoError(t, err) @@ -61,7 +61,7 @@ func TestPostgres(t *testing.T) { require.NoError(t, err) defer txx.Rollback() - _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pq.QuoteIdentifier(p.schema))) + _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pgx.Identifier{p.schema}.Sanitize())) require.NoError(t, err) var entries []dbUsernamesEntry @@ -102,7 +102,7 @@ func TestPostgres(t *testing.T) { require.NoError(t, err) defer txx.Rollback() - _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pq.QuoteIdentifier(p.schema))) + _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pgx.Identifier{p.schema}.Sanitize())) require.NoError(t, err) var entries []dbUsernameQueriesEntry diff --git a/internal/adapters/database/cloudsql.go b/internal/adapters/database/cloudsql.go index 8b3fb5f3..6353ace7 100644 --- a/internal/adapters/database/cloudsql.go +++ b/internal/adapters/database/cloudsql.go @@ -5,7 +5,7 @@ import "fmt" // https://cloud.google.com/sql/docs/mysql/connect-functions func GetCloudSQLConnectionString(dbUsername, dbPassword, unixSocketPath string) string { return fmt.Sprintf( - "user=%s password=%s database=%s host=%s", + "user=%s password=%s dbname=%s host=%s", dbUsername, dbPassword, DBName, diff --git a/internal/adapters/database/migrator.go b/internal/adapters/database/migrator.go index 343becc2..2a01e2e4 100644 --- a/internal/adapters/database/migrator.go +++ b/internal/adapters/database/migrator.go @@ -8,25 +8,24 @@ import ( "log/slog" "github.com/golang-migrate/migrate/v4" - "github.com/golang-migrate/migrate/v4/database/postgres" + pgxmigrate "github.com/golang-migrate/migrate/v4/database/pgx/v5" "github.com/golang-migrate/migrate/v4/source/iofs" - "github.com/jmoiron/sqlx" - "github.com/lib/pq" + "github.com/jackc/pgx/v5" ) //go:embed migrations/*.sql var embeddedMigrations embed.FS type migrator struct { - db *sqlx.DB + connectionString string logger *slog.Logger } -func NewDatabaseMigrator(db *sqlx.DB, logger *slog.Logger) *migrator { +func NewDatabaseMigrator(connectionString string, logger *slog.Logger) *migrator { return &migrator{ - db: db, - logger: logger, + connectionString: connectionString, + logger: logger, } } @@ -35,37 +34,32 @@ func (m *migrator) Migrate(ctx context.Context, schemaName string) error { } func (m *migrator) migrate(ctx context.Context, schemaName string) error { - conn, err := m.db.Conn(ctx) + db, err := openSchemaScopedDB(m.connectionString, schemaName) if err != nil { - return fmt.Errorf("migrate: failed to connect to db: %w", err) + return fmt.Errorf("migrate: %w", err) } - defer conn.Close() + defer db.Close() - _, err = conn.ExecContext(ctx, fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s", pq.QuoteIdentifier(schemaName))) + _, err = db.ExecContext(ctx, fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s", pgx.Identifier{schemaName}.Sanitize())) if err != nil { return fmt.Errorf("migrate: failed to create schema: %w", err) } - _, err = conn.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pq.QuoteIdentifier(schemaName))) - if err != nil { - return fmt.Errorf("migrate: failed to set search path: %w", err) - } - migrationSource, err := iofs.New(embeddedMigrations, "migrations") if err != nil { return fmt.Errorf("migrate: failed to create driver from embedded migrations: %w", err) } defer migrationSource.Close() - dbDriver, err := postgres.WithConnection(ctx, conn, &postgres.Config{ + dbDriver, err := pgxmigrate.WithInstance(db, &pgxmigrate.Config{ DatabaseName: DBName, SchemaName: schemaName, }) if err != nil { - return fmt.Errorf("migrate: failed to create postgres driver: %w", err) + return fmt.Errorf("migrate: failed to create pgx driver: %w", err) } - migratorInstance, err := migrate.NewWithInstance("iofs", migrationSource, "postgres", dbDriver) + migratorInstance, err := migrate.NewWithInstance("iofs", migrationSource, "pgx5", dbDriver) if err != nil { return fmt.Errorf("migrate: failed to create migration instance: %w", err) } diff --git a/internal/adapters/database/migrator_test.go b/internal/adapters/database/migrator_test.go index 01372197..a056ab8b 100644 --- a/internal/adapters/database/migrator_test.go +++ b/internal/adapters/database/migrator_test.go @@ -7,9 +7,9 @@ import ( "testing" "github.com/golang-migrate/migrate/v4" - "github.com/golang-migrate/migrate/v4/database/postgres" + pgxmigrate "github.com/golang-migrate/migrate/v4/database/pgx/v5" "github.com/golang-migrate/migrate/v4/source/iofs" - "github.com/lib/pq" + "github.com/jackc/pgx/v5" "github.com/stretchr/testify/require" ) @@ -28,32 +28,30 @@ func TestMigrator(t *testing.T) { db, err := NewPostgresDatabase(LocalConnectionString) require.NoError(t, err) - db.MustExec(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", pq.QuoteIdentifier(schemaName))) + db.MustExec(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", pgx.Identifier{schemaName}.Sanitize())) logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) - migrator := NewDatabaseMigrator(db, logger) + migrator := NewDatabaseMigrator(LocalConnectionString, logger) err = migrator.migrate(ctx, schemaName) require.NoError(t, err, "error migrating up") // Migrate down manually - conn, err := db.Conn(ctx) - require.NoError(t, err) - defer conn.Close() - _, err = conn.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pq.QuoteIdentifier(schemaName))) + schemaDB, err := openSchemaScopedDB(LocalConnectionString, schemaName) require.NoError(t, err) + defer schemaDB.Close() migrationSource, err := iofs.New(embeddedMigrations, "migrations") require.NoError(t, err) defer migrationSource.Close() - dbDriver, err := postgres.WithConnection(ctx, conn, &postgres.Config{ + dbDriver, err := pgxmigrate.WithInstance(schemaDB, &pgxmigrate.Config{ DatabaseName: DBName, SchemaName: schemaName, }) require.NoError(t, err) - migratorInstance, err := migrate.NewWithInstance("iofs", migrationSource, "postgres", dbDriver) + migratorInstance, err := migrate.NewWithInstance("iofs", migrationSource, "pgx5", dbDriver) require.NoError(t, err) defer migratorInstance.Close() diff --git a/internal/adapters/database/postgres.go b/internal/adapters/database/postgres.go index c897638d..f4115feb 100644 --- a/internal/adapters/database/postgres.go +++ b/internal/adapters/database/postgres.go @@ -1,10 +1,12 @@ package database import ( + "database/sql" "fmt" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/stdlib" "github.com/jmoiron/sqlx" - "github.com/lib/pq" "github.com/Amund211/flashlight/internal/config" ) @@ -24,7 +26,7 @@ func GetSchemaName(isTesting bool) string { } func NewPostgresDatabase(connectionString string) (*sqlx.DB, error) { - db, err := sqlx.Connect("postgres", connectionString) + db, err := sqlx.Connect("pgx", connectionString) if err != nil { return nil, fmt.Errorf("failed to connect to db: %w", err) } @@ -37,20 +39,34 @@ func NewPostgresDatabase(connectionString string) (*sqlx.DB, error) { return db, nil } -func NewCloudsqlPostgresDatabase(conf config.Config) (*sqlx.DB, error) { - var connectionString string +func ConnectionString(conf config.Config) string { if conf.IsDevelopment() { - connectionString = LocalConnectionString - } else { - connectionString = GetCloudSQLConnectionString(conf.DBUsername(), conf.DBPassword(), conf.CloudSQLUnixSocketPath()) + return LocalConnectionString } + return GetCloudSQLConnectionString(conf.DBUsername(), conf.DBPassword(), conf.CloudSQLUnixSocketPath()) +} - db, err := NewPostgresDatabase(connectionString) +// openSchemaScopedDB returns a one-off *sql.DB whose every connection has its +// search_path set to schemaName. Used by the migrator (and its tests) so +// migrate's WithInstance driver runs unqualified SQL in the right schema +// regardless of which conn from the pool it borrows. +// +// The schema name is double-quoted before being sent as a startup parameter: +// Postgres parses each comma-separated entry of search_path as a SQL +// identifier, so unquoted entries get lower-cased and reject hyphens. Without +// the quoting, mixed-case or hyphenated schema names silently resolve to a +// non-existent schema and unqualified DDL fails with SQLSTATE 3F000. +func openSchemaScopedDB(connectionString, schemaName string) (*sql.DB, error) { + connConfig, err := pgx.ParseConfig(connectionString) if err != nil { - return nil, fmt.Errorf("failed to create postgres database: %w", err) + return nil, fmt.Errorf("failed to parse connection config: %w", err) + } + if connConfig.RuntimeParams == nil { + connConfig.RuntimeParams = map[string]string{} } + connConfig.RuntimeParams["search_path"] = pgx.Identifier{schemaName}.Sanitize() - return db, nil + return stdlib.OpenDB(*connConfig), nil } func createDatabaseIfNotExists(db *sqlx.DB, dbName string) error { @@ -68,7 +84,7 @@ func createDatabaseIfNotExists(db *sqlx.DB, dbName string) error { return nil } - _, err := db.Exec(fmt.Sprintf("CREATE DATABASE %s", pq.QuoteIdentifier(dbName))) + _, err := db.Exec(fmt.Sprintf("CREATE DATABASE %s", pgx.Identifier{dbName}.Sanitize())) if err != nil { return fmt.Errorf("createDB: failed to create database: %w", err) } diff --git a/internal/adapters/database/postgres_test.go b/internal/adapters/database/postgres_test.go index 592cb554..36cc6584 100644 --- a/internal/adapters/database/postgres_test.go +++ b/internal/adapters/database/postgres_test.go @@ -33,7 +33,7 @@ func TestDB(t *testing.T) { t.Run("createDatabaseIfNotExists", func(t *testing.T) { t.Parallel() - db, err := sqlx.Connect("postgres", LocalConnectionString) + db, err := sqlx.Connect("pgx", LocalConnectionString) require.NoError(t, err) t.Run("already existing", func(t *testing.T) { t.Parallel() diff --git a/internal/adapters/playerrepository/repository.go b/internal/adapters/playerrepository/repository.go index 621bfb6f..ddfd7436 100644 --- a/internal/adapters/playerrepository/repository.go +++ b/internal/adapters/playerrepository/repository.go @@ -11,8 +11,8 @@ import ( "time" "github.com/google/uuid" + "github.com/jackc/pgx/v5" "github.com/jmoiron/sqlx" - "github.com/lib/pq" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" @@ -208,7 +208,7 @@ func (p *PostgresPlayerRepository) StorePlayer(ctx context.Context, player *doma } defer txx.Rollback() - _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pq.QuoteIdentifier(p.schema))) + _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pgx.Identifier{p.schema}.Sanitize())) if err != nil { err := fmt.Errorf("failed to set search path: %w", err) reporting.Report(ctx, err, map[string]string{ @@ -328,7 +328,7 @@ func (p *PostgresPlayerRepository) GetHistory(ctx context.Context, playerUUID st } defer txx.Rollback() - _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pq.QuoteIdentifier(p.schema))) + _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pgx.Identifier{p.schema}.Sanitize())) if err != nil { err := fmt.Errorf("failed to set search path: %w", err) reporting.Report(ctx, err, map[string]string{ @@ -480,7 +480,7 @@ func (p *PostgresPlayerRepository) GetPlayerPITs(ctx context.Context, playerUUID } defer conn.Close() - _, err = conn.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pq.QuoteIdentifier(p.schema))) + _, err = conn.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pgx.Identifier{p.schema}.Sanitize())) if err != nil { err := fmt.Errorf("failed to set search path: %w", err) reporting.Report(ctx, err, map[string]string{ @@ -600,7 +600,7 @@ func (p *PostgresPlayerRepository) FindMilestoneAchievements(ctx context.Context } defer txx.Rollback() - _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pq.QuoteIdentifier(p.schema))) + _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pgx.Identifier{p.schema}.Sanitize())) if err != nil { err := fmt.Errorf("failed to set search path: %w", err) reporting.Report(ctx, err, map[string]string{ diff --git a/internal/adapters/playerrepository/repository_test.go b/internal/adapters/playerrepository/repository_test.go index 79898290..1418ca46 100644 --- a/internal/adapters/playerrepository/repository_test.go +++ b/internal/adapters/playerrepository/repository_test.go @@ -10,8 +10,8 @@ import ( "time" "github.com/google/uuid" + "github.com/jackc/pgx/v5" "github.com/jmoiron/sqlx" - "github.com/lib/pq" "github.com/stretchr/testify/require" "github.com/Amund211/flashlight/internal/adapters/database" @@ -23,9 +23,9 @@ import ( func newPostgresPlayerRepository(t *testing.T, db *sqlx.DB, schema string) *PostgresPlayerRepository { logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) - db.MustExec(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", pq.QuoteIdentifier(schema))) + db.MustExec(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", pgx.Identifier{schema}.Sanitize())) - migrator := database.NewDatabaseMigrator(db, logger) + migrator := database.NewDatabaseMigrator(database.LocalConnectionString, logger) err := migrator.Migrate(t.Context(), schema) require.NoError(t, err) @@ -73,7 +73,7 @@ func TestPostgresPlayerRepository(t *testing.T) { require.NoError(t, err) defer txx.Rollback() - _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pq.QuoteIdentifier(schemaName))) + _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pgx.Identifier{schemaName}.Sanitize())) require.NoError(t, err) row := txx.QueryRowx("SELECT COUNT(*) FROM stats WHERE player_uuid = $1 AND player_data = $2 AND queried_at = $3", normalizedUUID, playerData, player.QueriedAt) @@ -249,7 +249,7 @@ func TestPostgresPlayerRepository(t *testing.T) { require.NoError(t, err) defer txx.Rollback() - _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pq.QuoteIdentifier(schemaName))) + _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pgx.Identifier{schemaName}.Sanitize())) require.NoError(t, err) _, err = txx.ExecContext( @@ -353,7 +353,7 @@ func TestPostgresPlayerRepository(t *testing.T) { require.NoError(t, err) defer txx.Rollback() - _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pq.QuoteIdentifier(schemaName))) + _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pgx.Identifier{schemaName}.Sanitize())) require.NoError(t, err) // Only one row for this player should exist @@ -577,7 +577,7 @@ func TestPostgresPlayerRepository(t *testing.T) { require.NoError(t, err) defer txx.Rollback() - _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pq.QuoteIdentifier(p.schema))) + _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pgx.Identifier{p.schema}.Sanitize())) require.NoError(t, err) _, err = txx.ExecContext(ctx, "DELETE FROM stats") @@ -591,7 +591,7 @@ func TestPostgresPlayerRepository(t *testing.T) { require.NoError(t, err) defer txx.Rollback() - _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pq.QuoteIdentifier(p.schema))) + _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pgx.Identifier{p.schema}.Sanitize())) require.NoError(t, err) var count int diff --git a/internal/adapters/userrepository/postgres.go b/internal/adapters/userrepository/postgres.go index a3fdcf83..1c2149e7 100644 --- a/internal/adapters/userrepository/postgres.go +++ b/internal/adapters/userrepository/postgres.go @@ -5,8 +5,8 @@ import ( "fmt" "time" + "github.com/jackc/pgx/v5" "github.com/jmoiron/sqlx" - "github.com/lib/pq" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" @@ -65,7 +65,7 @@ func (p *Postgres) RegisterVisit(ctx context.Context, userID string, ipHash stri last_user_agent = EXCLUDED.last_user_agent, seen_count = users.seen_count + 1 RETURNING user_id, first_seen_at, last_seen_at, last_ip_hash, last_user_agent, seen_count`, - pq.QuoteIdentifier(p.schema)), + pgx.Identifier{p.schema}.Sanitize()), userID, now, ipHash, diff --git a/internal/adapters/userrepository/postgres_test.go b/internal/adapters/userrepository/postgres_test.go index eb26404a..75f17606 100644 --- a/internal/adapters/userrepository/postgres_test.go +++ b/internal/adapters/userrepository/postgres_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" + "github.com/jackc/pgx/v5" "github.com/jmoiron/sqlx" - "github.com/lib/pq" "github.com/stretchr/testify/require" "github.com/Amund211/flashlight/internal/adapters/database" @@ -23,9 +23,9 @@ func newPostgres(t *testing.T, db *sqlx.DB, schemaSuffix string, nowFunc func() logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) - db.MustExec(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", pq.QuoteIdentifier(schema))) + db.MustExec(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", pgx.Identifier{schema}.Sanitize())) - migrator := database.NewDatabaseMigrator(db, logger) + migrator := database.NewDatabaseMigrator(database.LocalConnectionString, logger) err := migrator.Migrate(t.Context(), schema) require.NoError(t, err) @@ -48,7 +48,7 @@ func TestPostgresRegisterVisit(t *testing.T) { require.NoError(t, err) defer txx.Rollback() - _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pq.QuoteIdentifier(schema))) + _, err = txx.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", pgx.Identifier{schema}.Sanitize())) require.NoError(t, err) var user dbUser diff --git a/main.go b/main.go index 988ecb02..9232eb62 100644 --- a/main.go +++ b/main.go @@ -130,7 +130,8 @@ func main() { logger.InfoContext(ctx, "Initialized Sentry middleware") logger.InfoContext(ctx, "Initializing database connection") - db, err := database.NewCloudsqlPostgresDatabase(config) + connectionString := database.ConnectionString(config) + db, err := database.NewPostgresDatabase(connectionString) if err != nil { fail("Failed to initialize PostgresPlayerRepository", "error", err.Error()) } @@ -145,7 +146,7 @@ func main() { repositorySchemaName := database.GetSchemaName(!config.IsProduction()) - err = database.NewDatabaseMigrator(db, logger.With("component", "migrator")).Migrate(ctx, repositorySchemaName) + err = database.NewDatabaseMigrator(connectionString, logger.With("component", "migrator")).Migrate(ctx, repositorySchemaName) if err != nil { fail("Failed to migrate database", "error", err.Error()) }