From ce1b57da2db1617579401c28eae6d23e08b196ac Mon Sep 17 00:00:00 2001 From: Andras Banki-Horvath Date: Wed, 6 Dec 2023 19:47:56 +0100 Subject: [PATCH] sqldb: exclude sqlite from the JS and unsupported platform builds --- sqldb/config.go | 55 +++++++++++++++++++++++ sqldb/no_sqlite.go | 18 ++++++++ sqldb/postgres.go | 60 ------------------------- sqldb/postgres_fixture.go | 35 +++++++++++++++ sqldb/sqlerrors.go | 2 + sqldb/sqlerrors_no_sqlite.go | 86 ++++++++++++++++++++++++++++++++++++ sqldb/sqlite.go | 25 +---------- 7 files changed, 198 insertions(+), 83 deletions(-) create mode 100644 sqldb/config.go create mode 100644 sqldb/no_sqlite.go create mode 100644 sqldb/sqlerrors_no_sqlite.go diff --git a/sqldb/config.go b/sqldb/config.go new file mode 100644 index 000000000..5bc807806 --- /dev/null +++ b/sqldb/config.go @@ -0,0 +1,55 @@ +package sqldb + +import ( + "fmt" + "net/url" + "time" +) + +const ( + // defaultMaxConns is the number of permitted active and idle + // connections. We want to limit this so it isn't unlimited. We use the + // same value for the number of idle connections as, this can speed up + // queries given a new connection doesn't need to be established each + // time. + defaultMaxConns = 25 + + // connIdleLifetime is the amount of time a connection can be idle. + connIdleLifetime = 5 * time.Minute +) + +// SqliteConfig holds all the config arguments needed to interact with our +// sqlite DB. +// +//nolint:lll +type SqliteConfig struct { + Timeout time.Duration `long:"timeout" description:"The time after which a database query should be timed out."` + BusyTimeout time.Duration `long:"busytimeout" description:"The maximum amount of time to wait for a database connection to become available for a query."` + MaxConnections int `long:"maxconnections" description:"The maximum number of open connections to the database. Set to zero for unlimited."` + PragmaOptions []string `long:"pragmaoptions" description:"A list of pragma options to set on a database connection. For example, 'auto_vacuum=incremental'. Note that the flag must be specified multiple times if multiple options are to be set."` + SkipMigrations bool `long:"skipmigrations" description:"Skip applying migrations on startup."` +} + +// PostgresConfig holds the postgres database configuration. +// +//nolint:lll +type PostgresConfig struct { + Dsn string `long:"dsn" description:"Database connection string."` + Timeout time.Duration `long:"timeout" description:"Database connection timeout. Set to zero to disable."` + MaxConnections int `long:"maxconnections" description:"The maximum number of open connections to the database. Set to zero for unlimited."` + SkipMigrations bool `long:"skipmigrations" description:"Skip applying migrations on startup."` +} + +func (p *PostgresConfig) Validate() error { + if p.Dsn == "" { + return fmt.Errorf("DSN is required") + } + + // Parse the DSN as a URL. + _, err := url.Parse(p.Dsn) + if err != nil { + return fmt.Errorf("invalid DSN: %w", err) + } + + return nil +} diff --git a/sqldb/no_sqlite.go b/sqldb/no_sqlite.go new file mode 100644 index 000000000..9ea35c43c --- /dev/null +++ b/sqldb/no_sqlite.go @@ -0,0 +1,18 @@ +//go:build js || (windows && (arm || 386)) || (linux && (ppc64 || mips || mipsle || mips64)) + +package sqldb + +import "fmt" + +// SqliteStore is a database store implementation that uses a sqlite backend. +type SqliteStore struct { + cfg *SqliteConfig + + *BaseDB +} + +// NewSqliteStore attempts to open a new sqlite database based on the passed +// config. +func NewSqliteStore(cfg *SqliteConfig, dbPath string) (*SqliteStore, error) { + return nil, fmt.Errorf("SQLite backend not supported in WebAssembly") +} diff --git a/sqldb/postgres.go b/sqldb/postgres.go index cf52f7a72..e6e88c93b 100644 --- a/sqldb/postgres.go +++ b/sqldb/postgres.go @@ -1,21 +1,15 @@ package sqldb import ( - "context" - "crypto/rand" "database/sql" - "encoding/hex" - "fmt" "net/url" "path" "strings" - "testing" "time" postgres_migrate "github.com/golang-migrate/migrate/v4/database/postgres" _ "github.com/golang-migrate/migrate/v4/source/file" // Read migrations from files. // nolint:lll "github.com/lightningnetwork/lnd/sqldb/sqlc" - "github.com/stretchr/testify/require" ) var ( @@ -27,30 +21,6 @@ var ( DefaultPostgresFixtureLifetime = 10 * time.Minute ) -// PostgresConfig holds the postgres database configuration. -// -//nolint:lll -type PostgresConfig struct { - Dsn string `long:"dsn" description:"Database connection string."` - Timeout time.Duration `long:"timeout" description:"Database connection timeout. Set to zero to disable."` - MaxConnections int `long:"maxconnections" description:"The maximum number of open connections to the database. Set to zero for unlimited."` - SkipMigrations bool `long:"skipmigrations" description:"Skip applying migrations on startup."` -} - -func (p *PostgresConfig) Validate() error { - if p.Dsn == "" { - return fmt.Errorf("DSN is required") - } - - // Parse the DSN as a URL. - _, err := url.Parse(p.Dsn) - if err != nil { - return fmt.Errorf("invalid DSN: %w", err) - } - - return nil -} - // replacePasswordInDSN takes a DSN string and returns it with the password // replaced by "***". func replacePasswordInDSN(dsn string) (string, error) { @@ -167,33 +137,3 @@ func NewPostgresStore(cfg *PostgresConfig) (*PostgresStore, error) { }, }, nil } - -// NewTestPostgresDB is a helper function that creates a Postgres database for -// testing using the given fixture. -func NewTestPostgresDB(t *testing.T, fixture *TestPgFixture) *PostgresStore { - t.Helper() - - // Create random database name. - randBytes := make([]byte, 8) - _, err := rand.Read(randBytes) - if err != nil { - t.Fatal(err) - } - - dbName := "test_" + hex.EncodeToString(randBytes) - - t.Logf("Creating new Postgres DB '%s' for testing", dbName) - - _, err = fixture.db.ExecContext( - context.Background(), "CREATE DATABASE "+dbName, - ) - if err != nil { - t.Fatal(err) - } - - cfg := fixture.GetConfig(dbName) - store, err := NewPostgresStore(cfg) - require.NoError(t, err) - - return store -} diff --git a/sqldb/postgres_fixture.go b/sqldb/postgres_fixture.go index be897657b..a0f0893ce 100644 --- a/sqldb/postgres_fixture.go +++ b/sqldb/postgres_fixture.go @@ -1,7 +1,12 @@ +//go:build !js && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)) && !(netbsd || openbsd) + package sqldb import ( + "context" + "crypto/rand" "database/sql" + "encoding/hex" "fmt" "strconv" "strings" @@ -118,3 +123,33 @@ func (f *TestPgFixture) TearDown(t *testing.T) { err := f.pool.Purge(f.resource) require.NoError(t, err, "Could not purge resource") } + +// NewTestPostgresDB is a helper function that creates a Postgres database for +// testing using the given fixture. +func NewTestPostgresDB(t *testing.T, fixture *TestPgFixture) *PostgresStore { + t.Helper() + + // Create random database name. + randBytes := make([]byte, 8) + _, err := rand.Read(randBytes) + if err != nil { + t.Fatal(err) + } + + dbName := "test_" + hex.EncodeToString(randBytes) + + t.Logf("Creating new Postgres DB '%s' for testing", dbName) + + _, err = fixture.db.ExecContext( + context.Background(), "CREATE DATABASE "+dbName, + ) + if err != nil { + t.Fatal(err) + } + + cfg := fixture.GetConfig(dbName) + store, err := NewPostgresStore(cfg) + require.NoError(t, err) + + return store +} diff --git a/sqldb/sqlerrors.go b/sqldb/sqlerrors.go index 930347676..a939ec499 100644 --- a/sqldb/sqlerrors.go +++ b/sqldb/sqlerrors.go @@ -1,3 +1,5 @@ +//go:build !js && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)) + package sqldb import ( diff --git a/sqldb/sqlerrors_no_sqlite.go b/sqldb/sqlerrors_no_sqlite.go new file mode 100644 index 000000000..717d94152 --- /dev/null +++ b/sqldb/sqlerrors_no_sqlite.go @@ -0,0 +1,86 @@ +//go:build js || (windows && (arm || 386)) || (linux && (ppc64 || mips || mipsle || mips64)) + +package sqldb + +import ( + "errors" + "fmt" + + "github.com/jackc/pgconn" + "github.com/jackc/pgerrcode" +) + +var ( + // ErrRetriesExceeded is returned when a transaction is retried more + // than the max allowed valued without a success. + ErrRetriesExceeded = errors.New("db tx retries exceeded") +) + +// MapSQLError attempts to interpret a given error as a database agnostic SQL +// error. +func MapSQLError(err error) error { + // Attempt to interpret the error as a postgres error. + var pqErr *pgconn.PgError + if errors.As(err, &pqErr) { + return parsePostgresError(pqErr) + } + + // Return original error if it could not be classified as a database + // specific error. + return err +} + +// parsePostgresError attempts to parse a postgres error as a database agnostic +// SQL error. +func parsePostgresError(pqErr *pgconn.PgError) error { + switch pqErr.Code { + // Handle unique constraint violation error. + case pgerrcode.UniqueViolation: + return &ErrSQLUniqueConstraintViolation{ + DBError: pqErr, + } + + // Unable to serialize the transaction, so we'll need to try again. + case pgerrcode.SerializationFailure: + return &ErrSerializationError{ + DBError: pqErr, + } + + default: + return fmt.Errorf("unknown postgres error: %w", pqErr) + } +} + +// ErrSQLUniqueConstraintViolation is an error type which represents a database +// agnostic SQL unique constraint violation. +type ErrSQLUniqueConstraintViolation struct { + DBError error +} + +func (e ErrSQLUniqueConstraintViolation) Error() string { + return fmt.Sprintf("sql unique constraint violation: %v", e.DBError) +} + +// ErrSerializationError is an error type which represents a database agnostic +// error that a transaction couldn't be serialized with other concurrent db +// transactions. +type ErrSerializationError struct { + DBError error +} + +// Unwrap returns the wrapped error. +func (e ErrSerializationError) Unwrap() error { + return e.DBError +} + +// Error returns the error message. +func (e ErrSerializationError) Error() string { + return e.DBError.Error() +} + +// IsSerializationError returns true if the given error is a serialization +// error. +func IsSerializationError(err error) bool { + var serializationError *ErrSerializationError + return errors.As(err, &serializationError) +} diff --git a/sqldb/sqlite.go b/sqldb/sqlite.go index 9b2d08487..705d5cc47 100644 --- a/sqldb/sqlite.go +++ b/sqldb/sqlite.go @@ -1,3 +1,5 @@ +//go:build !js && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)) + package sqldb import ( @@ -6,7 +8,6 @@ import ( "net/url" "path/filepath" "testing" - "time" sqlite_migrate "github.com/golang-migrate/migrate/v4/database/sqlite" "github.com/lightningnetwork/lnd/sqldb/sqlc" @@ -23,30 +24,8 @@ const ( // sqliteTxLockImmediate is a dsn option used to ensure that write // transactions are started immediately. sqliteTxLockImmediate = "_txlock=immediate" - - // defaultMaxConns is the number of permitted active and idle - // connections. We want to limit this so it isn't unlimited. We use the - // same value for the number of idle connections as, this can speed up - // queries given a new connection doesn't need to be established each - // time. - defaultMaxConns = 25 - - // connIdleLifetime is the amount of time a connection can be idle. - connIdleLifetime = 5 * time.Minute ) -// SqliteConfig holds all the config arguments needed to interact with our -// sqlite DB. -// -//nolint:lll -type SqliteConfig struct { - Timeout time.Duration `long:"timeout" description:"The time after which a database query should be timed out."` - BusyTimeout time.Duration `long:"busytimeout" description:"The maximum amount of time to wait for a database connection to become available for a query."` - MaxConnections int `long:"maxconnections" description:"The maximum number of open connections to the database. Set to zero for unlimited."` - PragmaOptions []string `long:"pragmaoptions" description:"A list of pragma options to set on a database connection. For example, 'auto_vacuum=incremental'. Note that the flag must be specified multiple times if multiple options are to be set."` - SkipMigrations bool `long:"skipmigrations" description:"Skip applying migrations on startup."` -} - // SqliteStore is a database store implementation that uses a sqlite backend. type SqliteStore struct { cfg *SqliteConfig