mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
* serverless sqldb use same env for test config as normal * rip boltdb implementation of controller backend out it was replaced by postgres and no longer works properly. This involved migrating a number of tests which only worked with boltdb, which exposed several ways in which the postgres implementation had slightly different behavior from the bolt one: 1. ordering of results in some cases, and 2. (more importantly) erroring when a record to delete was not found. The bolt implementation silently ignored it when things to delete weren't found, so we make some changes to match that behavior. Also stopped propagating CreatedAt and UpdatedAt from DB tables into dax types. These were breaking existing tests. Perhaps it would be better to actually use them, but for now they will only exist at the DB level. This change set also moves the insertion of the directive_versions record out of migrations and into the startup/connection code. Having this in the migrations was a bit ugly because you couldn't just truncate all the tables and have everything work from scratch. Inserting it during startup is fairly innocuous, and will just continue on if it already exists. * update directive_version test I changed the initial value to 0 so that the first version that gets sent out is 1
113 lines
3.2 KiB
Go
113 lines
3.2 KiB
Go
package sqldb
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"database/sql"
|
|
|
|
"github.com/featurebasedb/featurebase/v3/dax"
|
|
"github.com/featurebasedb/featurebase/v3/dax/controller"
|
|
"github.com/featurebasedb/featurebase/v3/errors"
|
|
"github.com/featurebasedb/featurebase/v3/logger"
|
|
"github.com/gobuffalo/pop/v6"
|
|
)
|
|
|
|
// Transactor wraps a pop Connection to make it into a dax.Transactor
|
|
// which can be used by the controller agnostic of implementation.
|
|
type Transactor struct {
|
|
*pop.Connection
|
|
|
|
logger logger.Logger
|
|
}
|
|
|
|
func NewTransactor(cfg *controller.SQLDBConfig, log logger.Logger) (Transactor, error) {
|
|
conn, err := pop.NewConnection(&pop.ConnectionDetails{
|
|
Dialect: cfg.Dialect,
|
|
Database: cfg.Database,
|
|
Host: cfg.Host,
|
|
Port: cfg.Port,
|
|
User: cfg.User,
|
|
Password: cfg.Password,
|
|
URL: cfg.URL,
|
|
Pool: cfg.Pool,
|
|
IdlePool: cfg.IdlePool,
|
|
ConnMaxLifetime: cfg.ConnMaxLifetime,
|
|
ConnMaxIdleTime: cfg.ConnMaxIdleTime,
|
|
})
|
|
if err != nil {
|
|
return Transactor{Connection: nil}, errors.Wrap(err, "creating new connection")
|
|
}
|
|
|
|
return Transactor{
|
|
Connection: conn,
|
|
logger: log,
|
|
}, nil
|
|
}
|
|
|
|
// Start creates the database specified in the database connection, then runs
|
|
// any outstanding migrations.
|
|
func (t Transactor) Start() error {
|
|
conn := t.Connection
|
|
|
|
// Create the database if it doesn't exist.
|
|
if err := pop.CreateDB(conn); err != nil {
|
|
t.logger.Warnf("auto-creating database, got error '%v'", err)
|
|
}
|
|
|
|
// Open a connection to the database.
|
|
if err := conn.Open(); err != nil {
|
|
return errors.Wrap(err, "opening connection")
|
|
}
|
|
|
|
// Run migrations.
|
|
if mig, err := NewEmbedMigrator(dax.MigrationsFS, conn, t.logger); err != nil {
|
|
return errors.Wrap(err, "getting embedded migrator")
|
|
} else if err = mig.Up(); err != nil {
|
|
return errors.Wrap(err, "migrating DB")
|
|
}
|
|
|
|
err := t.RawQuery("INSERT INTO directive_versions (id, version, created_at, updated_at) VALUES (1, 0, '1970-01-01T00:00', '1970-01-01T00:00')").Exec()
|
|
if err != nil && !strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
|
|
return errors.Wrap(err, "unexpected error (re)inserting directive_version record")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t Transactor) BeginTx(ctx context.Context, writable bool) (dax.Transaction, error) {
|
|
cn, err := t.NewTransactionContextOptions(ctx, &sql.TxOptions{ReadOnly: !writable})
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "getting SQL transaction")
|
|
}
|
|
return &DaxTransaction{C: cn}, nil
|
|
}
|
|
|
|
func (t Transactor) Close() error {
|
|
return t.Connection.Close()
|
|
}
|
|
|
|
// DaxTransaction is a thin wrapper to create a dax.Transaction from a
|
|
// pop Transaction/Connection.
|
|
type DaxTransaction struct {
|
|
C *pop.Connection
|
|
}
|
|
|
|
func (w *DaxTransaction) Commit() error {
|
|
return w.C.TX.Commit()
|
|
}
|
|
|
|
func (w *DaxTransaction) Context() context.Context {
|
|
return w.C.Context()
|
|
}
|
|
func (w *DaxTransaction) Rollback() error {
|
|
return w.C.TX.Rollback()
|
|
}
|
|
|
|
// DropDatabase drops the database associated with the given
|
|
// Transactor (which embeds a live database connection). This is
|
|
// destructive, you will lose data.
|
|
func DropDatabase(trans Transactor) error {
|
|
conn := trans.Connection
|
|
return pop.DropDB(conn)
|
|
}
|