sqldb: exclude sqlite from the JS and unsupported platform builds

This commit is contained in:
Andras Banki-Horvath 2023-12-06 19:47:56 +01:00
parent 88370de1ab
commit ce1b57da2d
No known key found for this signature in database
GPG Key ID: 80E5375C094198D8
7 changed files with 198 additions and 83 deletions

55
sqldb/config.go Normal file
View File

@ -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
}

18
sqldb/no_sqlite.go Normal file
View File

@ -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")
}

View File

@ -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
}

View File

@ -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
}

View File

@ -1,3 +1,5 @@
//go:build !js && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64))
package sqldb
import (

View File

@ -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)
}

View File

@ -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