multinode/multinodedb: add sqlite3 support

Change-Id: I023ffb75f836de2c33eb7dbee52072e1622448bd
This commit is contained in:
Yaroslav Vorobiov 2021-02-04 16:38:45 +02:00
parent dc2bec9f89
commit 2e1455bc55
6 changed files with 682 additions and 35 deletions

View File

@ -21,7 +21,7 @@ import (
// Config defines multinode configuration.
type Config struct {
Database string `help:"multinode database connection string" releaseDefault:"postgres://" devDefault:"postgres://"`
Database string `help:"multinode database connection string" default:"sqlite3://file:$CONFDIR/master.db"`
multinode.Config
}

View File

@ -5,6 +5,7 @@ package multinodedb
import (
"context"
"strings"
"github.com/spacemonkeygo/monkit/v3"
"github.com/zeebo/errs"
@ -48,14 +49,17 @@ func Open(ctx context.Context, log *zap.Logger, databaseURL string) (multinode.D
if err != nil {
return nil, err
}
// TODO: do we need cockroach implementation?
if implementation != dbutil.Postgres && implementation != dbutil.Cockroach {
return nil, Error.New("unsupported driver %q", driver)
}
source, err = pgutil.CheckApplicationName(source, "storagenode-multinode")
if err != nil {
return nil, err
switch implementation {
case dbutil.SQLite3:
source = sqlite3SetDefaultOptions(source)
case dbutil.Postgres:
source, err = pgutil.CheckApplicationName(source, "multinode")
if err != nil {
return nil, err
}
default:
return nil, Error.New("unsupported driver %q", driver)
}
dbxDB, err := dbx.Open(driver, source)
@ -63,6 +67,7 @@ func Open(ctx context.Context, log *zap.Logger, databaseURL string) (multinode.D
return nil, Error.New("failed opening database via DBX at %q: %v",
source, err)
}
log.Debug("Connected to:", zap.String("db source", source))
dbutil.Configure(ctx, dbxDB.DB, "multinodedb", mon)
@ -98,3 +103,20 @@ func (db *multinodeDB) CreateSchema(ctx context.Context) error {
_, err := db.ExecContext(ctx, db.DB.Schema())
return err
}
// sqlite3SetDefaultOptions sets default options for disk-based db with URI filename source string
// if no options were set.
func sqlite3SetDefaultOptions(source string) string {
if !strings.HasPrefix(source, "file:") {
return source
}
// do not set anything for in-memory db
if strings.HasPrefix(source, "file::memory:") {
return source
}
if strings.Contains(source, "?") {
return source
}
return source + "?_journal=WAL&_busy_timeout=10000"
}

View File

@ -1,12 +1,17 @@
#!/bin/sh
dbx schema -d pgx multinodedb.dbx .
dbx golang -d pgx -p dbx -t templates multinodedb.dbx .
set -euo pipefail
dbx schema -d pgx -d sqlite3 multinodedb.dbx .
dbx golang -d pgx -d sqlite3 -p dbx -t templates multinodedb.dbx .
( printf '%s\n' '//lint:file-ignore U1000,ST1012 generated file'; cat multinodedb.dbx.go ) > multinodedb.dbx.go.tmp && mv multinodedb.dbx.go.tmp multinodedb.dbx.go
gofmt -r "*sql.Tx -> tagsql.Tx" -w multinodedb.dbx.go
gofmt -r "*sql.Rows -> tagsql.Rows" -w multinodedb.dbx.go
perl -0777 -pi \
-e 's,\t_ "github.com/jackc/pgx/v4/stdlib"\n\),\t_ "github.com/jackc/pgx/v4/stdlib"\n\n\t"storj.io/storj/private/tagsql"\n\),' \
-e 's,\t"fmt"\n,\t"fmt"\n\t"math/rand"\n,' \
multinodedb.dbx.go
perl -0777 -pi \
-e 's,\t"math/rand"\n\),\n\t"storj.io/storj/private/tagsql"\n\),' \
multinodedb.dbx.go
perl -0777 -pi \
-e 's/type DB struct \{\n\t\*sql\.DB/type DB struct \{\n\ttagsql.DB/' \

View File

@ -10,6 +10,7 @@ import (
"database/sql"
"errors"
"fmt"
"math/rand"
"reflect"
"strconv"
"strings"
@ -19,6 +20,7 @@ import (
"github.com/jackc/pgconn"
_ "github.com/jackc/pgx/v4/stdlib"
"github.com/mattn/go-sqlite3"
"storj.io/storj/private/tagsql"
)
@ -145,6 +147,8 @@ func Open(driver, source string) (db *DB, err error) {
switch driver {
case "pgx":
sql_db, err = openpgx(source)
case "sqlite3":
sql_db, err = opensqlite3(source)
default:
return nil, unsupportedDriver(driver)
}
@ -169,6 +173,8 @@ func Open(driver, source string) (db *DB, err error) {
switch driver {
case "pgx":
db.dbMethods = newpgx(db)
case "sqlite3":
db.dbMethods = newsqlite3(db)
default:
return nil, unsupportedDriver(driver)
}
@ -309,6 +315,84 @@ func pgxLogStmt(stmt string, args ...interface{}) {
}
}
type sqlite3Impl struct {
db *DB
dialect __sqlbundle_sqlite3
driver driver
}
func (obj *sqlite3Impl) Rebind(s string) string {
return obj.dialect.Rebind(s)
}
func (obj *sqlite3Impl) logStmt(stmt string, args ...interface{}) {
sqlite3LogStmt(stmt, args...)
}
func (obj *sqlite3Impl) makeErr(err error) error {
constraint, ok := obj.isConstraintError(err)
if ok {
return constraintViolation(err, constraint)
}
return makeErr(err)
}
type sqlite3DB struct {
db *DB
*sqlite3Impl
}
func newsqlite3(db *DB) *sqlite3DB {
return &sqlite3DB{
db: db,
sqlite3Impl: &sqlite3Impl{
db: db,
driver: db.DB,
},
}
}
func (obj *sqlite3DB) Schema() string {
return `CREATE TABLE members (
id BLOB NOT NULL,
email TEXT NOT NULL,
name TEXT NOT NULL,
password_hash BLOB NOT NULL,
created_at TIMESTAMP NOT NULL,
PRIMARY KEY ( id )
);
CREATE TABLE nodes (
id BLOB NOT NULL,
name TEXT NOT NULL,
public_address TEXT NOT NULL,
api_secret BLOB NOT NULL,
PRIMARY KEY ( id )
);`
}
func (obj *sqlite3DB) wrapTx(tx tagsql.Tx) txMethods {
return &sqlite3Tx{
dialectTx: dialectTx{tx: tx},
sqlite3Impl: &sqlite3Impl{
db: obj.db,
driver: tx,
},
}
}
type sqlite3Tx struct {
dialectTx
*sqlite3Impl
}
func sqlite3LogStmt(stmt string, args ...interface{}) {
// TODO: render placeholders
if Logger != nil {
out := fmt.Sprintf("stmt: %s\nargs: %v\n", stmt, pretty(args))
Logger(out)
}
}
type pretty []interface{}
func (p pretty) Format(f fmt.State, c rune) {
@ -1369,6 +1453,478 @@ func (obj *pgxImpl) deleteAll(ctx context.Context) (count int64, err error) {
}
func (obj *sqlite3Impl) Create_Node(ctx context.Context,
node_id Node_Id_Field,
node_name Node_Name_Field,
node_public_address Node_PublicAddress_Field,
node_api_secret Node_ApiSecret_Field) (
node *Node, err error) {
defer mon.Task()(&ctx)(&err)
__id_val := node_id.value()
__name_val := node_name.value()
__public_address_val := node_public_address.value()
__api_secret_val := node_api_secret.value()
var __embed_stmt = __sqlbundle_Literal("INSERT INTO nodes ( id, name, public_address, api_secret ) VALUES ( ?, ?, ?, ? )")
var __values []interface{}
__values = append(__values, __id_val, __name_val, __public_address_val, __api_secret_val)
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
__res, err := obj.driver.ExecContext(ctx, __stmt, __values...)
if err != nil {
return nil, obj.makeErr(err)
}
__pk, err := __res.LastInsertId()
if err != nil {
return nil, obj.makeErr(err)
}
return obj.getLastNode(ctx, __pk)
}
func (obj *sqlite3Impl) Create_Member(ctx context.Context,
member_id Member_Id_Field,
member_email Member_Email_Field,
member_name Member_Name_Field,
member_password_hash Member_PasswordHash_Field) (
member *Member, err error) {
defer mon.Task()(&ctx)(&err)
__now := obj.db.Hooks.Now().UTC()
__id_val := member_id.value()
__email_val := member_email.value()
__name_val := member_name.value()
__password_hash_val := member_password_hash.value()
__created_at_val := __now
var __embed_stmt = __sqlbundle_Literal("INSERT INTO members ( id, email, name, password_hash, created_at ) VALUES ( ?, ?, ?, ?, ? )")
var __values []interface{}
__values = append(__values, __id_val, __email_val, __name_val, __password_hash_val, __created_at_val)
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
__res, err := obj.driver.ExecContext(ctx, __stmt, __values...)
if err != nil {
return nil, obj.makeErr(err)
}
__pk, err := __res.LastInsertId()
if err != nil {
return nil, obj.makeErr(err)
}
return obj.getLastMember(ctx, __pk)
}
func (obj *sqlite3Impl) Get_Node_By_Id(ctx context.Context,
node_id Node_Id_Field) (
node *Node, err error) {
defer mon.Task()(&ctx)(&err)
var __embed_stmt = __sqlbundle_Literal("SELECT nodes.id, nodes.name, nodes.public_address, nodes.api_secret FROM nodes WHERE nodes.id = ?")
var __values []interface{}
__values = append(__values, node_id.value())
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
node = &Node{}
err = obj.driver.QueryRowContext(ctx, __stmt, __values...).Scan(&node.Id, &node.Name, &node.PublicAddress, &node.ApiSecret)
if err != nil {
return (*Node)(nil), obj.makeErr(err)
}
return node, nil
}
func (obj *sqlite3Impl) All_Node(ctx context.Context) (
rows []*Node, err error) {
defer mon.Task()(&ctx)(&err)
var __embed_stmt = __sqlbundle_Literal("SELECT nodes.id, nodes.name, nodes.public_address, nodes.api_secret FROM nodes")
var __values []interface{}
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
__rows, err := obj.driver.QueryContext(ctx, __stmt, __values...)
if err != nil {
return nil, obj.makeErr(err)
}
defer __rows.Close()
for __rows.Next() {
node := &Node{}
err = __rows.Scan(&node.Id, &node.Name, &node.PublicAddress, &node.ApiSecret)
if err != nil {
return nil, obj.makeErr(err)
}
rows = append(rows, node)
}
if err := __rows.Err(); err != nil {
return nil, obj.makeErr(err)
}
return rows, nil
}
func (obj *sqlite3Impl) Get_Member_By_Email(ctx context.Context,
member_email Member_Email_Field) (
member *Member, err error) {
defer mon.Task()(&ctx)(&err)
var __embed_stmt = __sqlbundle_Literal("SELECT members.id, members.email, members.name, members.password_hash, members.created_at FROM members WHERE members.email = ? LIMIT 2")
var __values []interface{}
__values = append(__values, member_email.value())
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
__rows, err := obj.driver.QueryContext(ctx, __stmt, __values...)
if err != nil {
return nil, obj.makeErr(err)
}
defer __rows.Close()
if !__rows.Next() {
if err := __rows.Err(); err != nil {
return nil, obj.makeErr(err)
}
return nil, makeErr(sql.ErrNoRows)
}
member = &Member{}
err = __rows.Scan(&member.Id, &member.Email, &member.Name, &member.PasswordHash, &member.CreatedAt)
if err != nil {
return nil, obj.makeErr(err)
}
if __rows.Next() {
return nil, tooManyRows("Member_By_Email")
}
if err := __rows.Err(); err != nil {
return nil, obj.makeErr(err)
}
return member, nil
}
func (obj *sqlite3Impl) Get_Member_By_Id(ctx context.Context,
member_id Member_Id_Field) (
member *Member, err error) {
defer mon.Task()(&ctx)(&err)
var __embed_stmt = __sqlbundle_Literal("SELECT members.id, members.email, members.name, members.password_hash, members.created_at FROM members WHERE members.id = ?")
var __values []interface{}
__values = append(__values, member_id.value())
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
member = &Member{}
err = obj.driver.QueryRowContext(ctx, __stmt, __values...).Scan(&member.Id, &member.Email, &member.Name, &member.PasswordHash, &member.CreatedAt)
if err != nil {
return (*Member)(nil), obj.makeErr(err)
}
return member, nil
}
func (obj *sqlite3Impl) Update_Node_By_Id(ctx context.Context,
node_id Node_Id_Field,
update Node_Update_Fields) (
node *Node, err error) {
defer mon.Task()(&ctx)(&err)
var __sets = &__sqlbundle_Hole{}
var __embed_stmt = __sqlbundle_Literals{Join: "", SQLs: []__sqlbundle_SQL{__sqlbundle_Literal("UPDATE nodes SET "), __sets, __sqlbundle_Literal(" WHERE nodes.id = ?")}}
__sets_sql := __sqlbundle_Literals{Join: ", "}
var __values []interface{}
var __args []interface{}
if update.Name._set {
__values = append(__values, update.Name.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("name = ?"))
}
if len(__sets_sql.SQLs) == 0 {
return nil, emptyUpdate()
}
__args = append(__args, node_id.value())
__values = append(__values, __args...)
__sets.SQL = __sets_sql
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
node = &Node{}
_, err = obj.driver.ExecContext(ctx, __stmt, __values...)
if err != nil {
return nil, obj.makeErr(err)
}
var __embed_stmt_get = __sqlbundle_Literal("SELECT nodes.id, nodes.name, nodes.public_address, nodes.api_secret FROM nodes WHERE nodes.id = ?")
var __stmt_get = __sqlbundle_Render(obj.dialect, __embed_stmt_get)
obj.logStmt("(IMPLIED) "+__stmt_get, __args...)
err = obj.driver.QueryRowContext(ctx, __stmt_get, __args...).Scan(&node.Id, &node.Name, &node.PublicAddress, &node.ApiSecret)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, obj.makeErr(err)
}
return node, nil
}
func (obj *sqlite3Impl) UpdateNoReturn_Node_By_Id(ctx context.Context,
node_id Node_Id_Field,
update Node_Update_Fields) (
err error) {
defer mon.Task()(&ctx)(&err)
var __sets = &__sqlbundle_Hole{}
var __embed_stmt = __sqlbundle_Literals{Join: "", SQLs: []__sqlbundle_SQL{__sqlbundle_Literal("UPDATE nodes SET "), __sets, __sqlbundle_Literal(" WHERE nodes.id = ?")}}
__sets_sql := __sqlbundle_Literals{Join: ", "}
var __values []interface{}
var __args []interface{}
if update.Name._set {
__values = append(__values, update.Name.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("name = ?"))
}
if len(__sets_sql.SQLs) == 0 {
return emptyUpdate()
}
__args = append(__args, node_id.value())
__values = append(__values, __args...)
__sets.SQL = __sets_sql
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
_, err = obj.driver.ExecContext(ctx, __stmt, __values...)
if err != nil {
return obj.makeErr(err)
}
return nil
}
func (obj *sqlite3Impl) Update_Member_By_Id(ctx context.Context,
member_id Member_Id_Field,
update Member_Update_Fields) (
member *Member, err error) {
defer mon.Task()(&ctx)(&err)
var __sets = &__sqlbundle_Hole{}
var __embed_stmt = __sqlbundle_Literals{Join: "", SQLs: []__sqlbundle_SQL{__sqlbundle_Literal("UPDATE members SET "), __sets, __sqlbundle_Literal(" WHERE members.id = ?")}}
__sets_sql := __sqlbundle_Literals{Join: ", "}
var __values []interface{}
var __args []interface{}
if update.Email._set {
__values = append(__values, update.Email.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("email = ?"))
}
if update.Name._set {
__values = append(__values, update.Name.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("name = ?"))
}
if update.PasswordHash._set {
__values = append(__values, update.PasswordHash.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("password_hash = ?"))
}
if len(__sets_sql.SQLs) == 0 {
return nil, emptyUpdate()
}
__args = append(__args, member_id.value())
__values = append(__values, __args...)
__sets.SQL = __sets_sql
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
member = &Member{}
_, err = obj.driver.ExecContext(ctx, __stmt, __values...)
if err != nil {
return nil, obj.makeErr(err)
}
var __embed_stmt_get = __sqlbundle_Literal("SELECT members.id, members.email, members.name, members.password_hash, members.created_at FROM members WHERE members.id = ?")
var __stmt_get = __sqlbundle_Render(obj.dialect, __embed_stmt_get)
obj.logStmt("(IMPLIED) "+__stmt_get, __args...)
err = obj.driver.QueryRowContext(ctx, __stmt_get, __args...).Scan(&member.Id, &member.Email, &member.Name, &member.PasswordHash, &member.CreatedAt)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, obj.makeErr(err)
}
return member, nil
}
func (obj *sqlite3Impl) Delete_Node_By_Id(ctx context.Context,
node_id Node_Id_Field) (
deleted bool, err error) {
defer mon.Task()(&ctx)(&err)
var __embed_stmt = __sqlbundle_Literal("DELETE FROM nodes WHERE nodes.id = ?")
var __values []interface{}
__values = append(__values, node_id.value())
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
__res, err := obj.driver.ExecContext(ctx, __stmt, __values...)
if err != nil {
return false, obj.makeErr(err)
}
__count, err := __res.RowsAffected()
if err != nil {
return false, obj.makeErr(err)
}
return __count > 0, nil
}
func (obj *sqlite3Impl) Delete_Member_By_Id(ctx context.Context,
member_id Member_Id_Field) (
deleted bool, err error) {
defer mon.Task()(&ctx)(&err)
var __embed_stmt = __sqlbundle_Literal("DELETE FROM members WHERE members.id = ?")
var __values []interface{}
__values = append(__values, member_id.value())
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
__res, err := obj.driver.ExecContext(ctx, __stmt, __values...)
if err != nil {
return false, obj.makeErr(err)
}
__count, err := __res.RowsAffected()
if err != nil {
return false, obj.makeErr(err)
}
return __count > 0, nil
}
func (obj *sqlite3Impl) getLastNode(ctx context.Context,
pk int64) (
node *Node, err error) {
defer mon.Task()(&ctx)(&err)
var __embed_stmt = __sqlbundle_Literal("SELECT nodes.id, nodes.name, nodes.public_address, nodes.api_secret FROM nodes WHERE _rowid_ = ?")
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, pk)
node = &Node{}
err = obj.driver.QueryRowContext(ctx, __stmt, pk).Scan(&node.Id, &node.Name, &node.PublicAddress, &node.ApiSecret)
if err != nil {
return (*Node)(nil), obj.makeErr(err)
}
return node, nil
}
func (obj *sqlite3Impl) getLastMember(ctx context.Context,
pk int64) (
member *Member, err error) {
defer mon.Task()(&ctx)(&err)
var __embed_stmt = __sqlbundle_Literal("SELECT members.id, members.email, members.name, members.password_hash, members.created_at FROM members WHERE _rowid_ = ?")
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, pk)
member = &Member{}
err = obj.driver.QueryRowContext(ctx, __stmt, pk).Scan(&member.Id, &member.Email, &member.Name, &member.PasswordHash, &member.CreatedAt)
if err != nil {
return (*Member)(nil), obj.makeErr(err)
}
return member, nil
}
func (impl sqlite3Impl) isConstraintError(err error) (
constraint string, ok bool) {
if e, ok := err.(sqlite3.Error); ok {
if e.Code == sqlite3.ErrConstraint {
msg := err.Error()
colon := strings.LastIndex(msg, ":")
if colon != -1 {
return strings.TrimSpace(msg[colon:]), true
}
return "", true
}
}
return "", false
}
func (obj *sqlite3Impl) deleteAll(ctx context.Context) (count int64, err error) {
defer mon.Task()(&ctx)(&err)
var __res sql.Result
var __count int64
__res, err = obj.driver.ExecContext(ctx, "DELETE FROM nodes;")
if err != nil {
return 0, obj.makeErr(err)
}
__count, err = __res.RowsAffected()
if err != nil {
return 0, obj.makeErr(err)
}
count += __count
__res, err = obj.driver.ExecContext(ctx, "DELETE FROM members;")
if err != nil {
return 0, obj.makeErr(err)
}
__count, err = __res.RowsAffected()
if err != nil {
return 0, obj.makeErr(err)
}
count += __count
return count, nil
}
type Rx struct {
db *DB
tx *Tx
@ -1617,3 +2173,36 @@ type dbMethods interface {
func openpgx(source string) (*sql.DB, error) {
return sql.Open("pgx", source)
}
var sqlite3DriverName = func() string {
var id [16]byte
rand.Read(id[:])
return fmt.Sprintf("sqlite3_%x", string(id[:]))
}()
func init() {
sql.Register(sqlite3DriverName, &sqlite3.SQLiteDriver{
ConnectHook: sqlite3SetupConn,
})
}
// SQLite3JournalMode controls the journal_mode pragma for all new connections.
// Since it is read without a mutex, it must be changed to the value you want
// before any Open calls.
var SQLite3JournalMode = "WAL"
func sqlite3SetupConn(conn *sqlite3.SQLiteConn) (err error) {
_, err = conn.Exec("PRAGMA foreign_keys = ON", nil)
if err != nil {
return makeErr(err)
}
_, err = conn.Exec("PRAGMA journal_mode = "+SQLite3JournalMode, nil)
if err != nil {
return makeErr(err)
}
return nil
}
func opensqlite3(source string) (*sql.DB, error) {
return sql.Open(sqlite3DriverName, source)
}

View File

@ -0,0 +1,17 @@
-- AUTOGENERATED BY storj.io/dbx
-- DO NOT EDIT
CREATE TABLE members (
id BLOB NOT NULL,
email TEXT NOT NULL,
name TEXT NOT NULL,
password_hash BLOB NOT NULL,
created_at TIMESTAMP NOT NULL,
PRIMARY KEY ( id )
);
CREATE TABLE nodes (
id BLOB NOT NULL,
name TEXT NOT NULL,
public_address TEXT NOT NULL,
api_secret BLOB NOT NULL,
PRIMARY KEY ( id )
);

View File

@ -104,38 +104,52 @@ func CreateMasterDBOnTopOf(ctx context.Context, log *zap.Logger, tempDB *dbutil.
// Run method will iterate over all supported databases. Will establish
// connection and will create tables for each DB.
func Run(t *testing.T, test func(ctx *testcontext.Context, t *testing.T, db multinode.DB)) {
masterDB := Database{
Name: "Postgres",
URL: pgtest.PickPostgres(ignoreSkip{}),
Message: "Postgres flag missing, example: -postgres-test-db=" + pgtest.DefaultPostgres + " or use STORJ_TEST_POSTGRES environment variable.",
databases := []Database{
{
Name: "Postgres",
URL: pgtest.PickPostgres(ignoreSkip{}),
Message: "Postgres flag missing, example: -postgres-test-db=" + pgtest.DefaultPostgres + " or use STORJ_TEST_POSTGRES environment variable.",
},
{
Name: "Sqlite3",
URL: "sqlite3://file::memory:",
},
}
t.Run(masterDB.Name, func(t *testing.T) {
t.Parallel()
for _, database := range databases {
dbConfig := database
ctx := testcontext.New(t)
defer ctx.Cleanup()
t.Run(dbConfig.Name, func(t *testing.T) {
t.Parallel()
if masterDB.URL == "" {
t.Skipf("Database %s connection string not provided. %s", masterDB.Name, masterDB.Message)
}
if dbConfig.URL == "" {
t.Skipf("Database %s connection string not provided. %s", dbConfig.Name, dbConfig.Message)
}
db, err := CreateMasterDB(ctx, zaptest.NewLogger(t), t.Name(), "T", 0, masterDB)
if err != nil {
t.Fatal(err)
}
defer func() {
err := db.Close()
log := zaptest.NewLogger(t)
ctx := testcontext.New(t)
defer ctx.Cleanup()
var db multinode.DB
var err error
if dbConfig.Name == "Postgres" {
db, err = CreateMasterDB(ctx, log, t.Name(), "T", 0, dbConfig)
} else {
db, err = multinodedb.Open(ctx, log, dbConfig.URL)
}
if err != nil {
t.Fatal(err)
}
}()
err = db.CreateSchema(ctx)
if err != nil {
t.Fatal(err)
}
defer ctx.Check(db.Close)
test(ctx, t, db)
})
err = db.CreateSchema(ctx)
if err != nil {
t.Fatal(err)
}
test(ctx, t, db)
})
}
}