diff options
| -rw-r--r-- | CHANGELOG.md | 7 | ||||
| -rw-r--r-- | benchmark/benchmark_test.go | 44 | ||||
| -rw-r--r-- | benchmark/postgres_test.go | 26 | ||||
| -rw-r--r-- | handle.go | 130 | ||||
| -rw-r--r-- | oblast.go | 12 | ||||
| -rw-r--r-- | query_test.go | 22 | ||||
| -rw-r--r-- | runtimeindex_test.go | 2 | ||||
| -rw-r--r-- | select_test.go | 16 |
8 files changed, 168 insertions, 91 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 315d337..9d2a141 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ SPDX-FileCopyrightText: 2026 Stefan Majewsky <majewsky@gmx.net> SPDX-License-Identifier: Apache-2.0 --> +# v0.9.0 (TBD) + +API changes: + +- The generic function `Wrap` is replaced with explicit functions `NewDB`, `NewConn` and `NewTx` that yield separate types. + This allows our wrapped types to carry the original types from database/sql as embedded types, thus making their use more ergonomic. + # v0.8.0 (2026-05-13) API changes: diff --git a/benchmark/benchmark_test.go b/benchmark/benchmark_test.go index 4bc7950..f779b08 100644 --- a/benchmark/benchmark_test.go +++ b/benchmark/benchmark_test.go @@ -43,14 +43,14 @@ var ( batchSizesForUpdate = []int{1, 2, 4, 8, 16, 100} ) -func makeSqliteTestDB(t testing.TB, recordCount int) (db oblast.SqlHandle[*sql.DB], dsn string) { +func makeSqliteTestDB(t testing.TB, recordCount int) (db *oblast.DB, dsn string) { dsn = fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name()) - db = oblast.Wrap(must.Return(sql.Open("sqlite3", dsn))(t)) - _ = must.Return(db.Base.Exec(`CREATE TABLE entries (id INTEGER, message TEXT, PRIMARY KEY (id AUTOINCREMENT))`))(t) + db = oblast.NewDB(must.Return(sql.Open("sqlite3", dsn))(t)) + _ = must.Return(db.Exec(`CREATE TABLE entries (id INTEGER, message TEXT, PRIMARY KEY (id AUTOINCREMENT))`))(t) if recordCount > 0 { // fill in some random-looking, but deterministic data - stmt := must.Return(db.Base.Prepare(`INSERT INTO entries (id, message) VALUES (?, ?)`))(t) + stmt := must.Return(db.Prepare(`INSERT INTO entries (id, message) VALUES (?, ?)`))(t) for idx := range recordCount { buf := sha256.Sum256([]byte(strconv.Itoa(idx))) _ = must.Return(stmt.Exec(idx, fmt.Sprintf("sha256:%x", buf[:])))(t) @@ -91,7 +91,7 @@ func BenchmarkORMSelectMany(b *testing.B) { oblast.TableNameIs("entries"), oblast.PrimaryKeyIs("id"), ) - gorpDB := gorp.DbMap{Db: db.Base, Dialect: gorp.SqliteDialect{}} + gorpDB := gorp.DbMap{Db: db.DB, Dialect: gorp.SqliteDialect{}} gormDB := must.Return(gorm.Open(sqlite.Open(dsn), &gorm.Config{}))(b) partialQuery := `id < ` + strconv.Itoa(batchSize) query := `SELECT * FROM entries WHERE ` + partialQuery @@ -120,7 +120,7 @@ func BenchmarkORMSelectMany(b *testing.B) { selectWithSqlite := func(b *testing.B) { var count int - rows := must.Return(db.Base.Query(query))(b) //nolint:rowserrcheck // false positive + rows := must.Return(db.Query(query))(b) //nolint:rowserrcheck // false positive var ( id int64 message string @@ -185,7 +185,7 @@ func BenchmarkORMSelectOne(b *testing.B) { oblast.TableNameIs("entries"), oblast.PrimaryKeyIs("id"), ) - gorpDB := gorp.DbMap{Db: db.Base, Dialect: gorp.SqliteDialect{}} + gorpDB := gorp.DbMap{Db: db.DB, Dialect: gorp.SqliteDialect{}} gormDB := must.Return(gorm.Open(sqlite.Open(dsn), &gorm.Config{}))(b) partialQuery := `id = ` + strconv.Itoa(recordID) query := `SELECT * FROM entries WHERE ` + partialQuery @@ -217,7 +217,7 @@ func BenchmarkORMSelectOne(b *testing.B) { id int64 message string ) - must.Succeed(b, db.Base.QueryRow(query).Scan(&id, &message)) + must.Succeed(b, db.QueryRow(query).Scan(&id, &message)) assert.Equal(b, id, int64(recordID)) } @@ -265,7 +265,7 @@ func BenchmarkORMInsertAndDelete(b *testing.B) { oblast.TableNameIs("entries"), oblast.PrimaryKeyIs("id"), ) - gorpDB := gorp.DbMap{Db: db.Base, Dialect: gorp.SqliteDialect{}} + gorpDB := gorp.DbMap{Db: db.DB, Dialect: gorp.SqliteDialect{}} gorpDB.AddTableWithName(GorpEntry{}, "entries").SetKeys(true, "id") gormDB := must.Return(gorm.Open(sqlite.Open(dsn), &gorm.Config{}))(b) @@ -351,23 +351,23 @@ func BenchmarkORMInsertAndDelete(b *testing.B) { insertAndDeleteWithStraightExec := func(b *testing.B) { ids := make([]int64, batchSize) for idx := range ids { - result := must.Return(db.Base.Exec(`INSERT INTO entries (message) VALUES (?)`, "hello"))(b) + result := must.Return(db.Exec(`INSERT INTO entries (message) VALUES (?)`, "hello"))(b) ids[idx] = must.Return(result.LastInsertId())(b) } for _, id := range ids { - _ = must.Return(db.Base.Exec(`DELETE FROM entries WHERE id = ?`, id))(b) + _ = must.Return(db.Exec(`DELETE FROM entries WHERE id = ?`, id))(b) } } insertAndDeleteWithPreparedExec := func(b *testing.B) { ids := make([]int64, batchSize) - stmtInsert := must.Return(db.Base.Prepare(`INSERT INTO entries (message) VALUES (?)`))(b) + stmtInsert := must.Return(db.Prepare(`INSERT INTO entries (message) VALUES (?)`))(b) defer stmtInsert.Close() for idx := range ids { result := must.Return(stmtInsert.Exec("hello"))(b) ids[idx] = must.Return(result.LastInsertId())(b) } - stmtDelete := must.Return(db.Base.Prepare(`DELETE FROM entries WHERE id = ?`))(b) + stmtDelete := must.Return(db.Prepare(`DELETE FROM entries WHERE id = ?`))(b) defer stmtDelete.Close() for _, id := range ids { _ = must.Return(stmtDelete.Exec(id))(b) @@ -377,21 +377,21 @@ func BenchmarkORMInsertAndDelete(b *testing.B) { insertAndDeleteWithStraightQueryRow := func(b *testing.B) { ids := make([]int64, batchSize) for idx := range ids { - must.Succeed(b, db.Base.QueryRow(`INSERT INTO entries (message) VALUES (?) RETURNING id`, "hello").Scan(&ids[idx])) + must.Succeed(b, db.QueryRow(`INSERT INTO entries (message) VALUES (?) RETURNING id`, "hello").Scan(&ids[idx])) } for _, id := range ids { - _ = must.Return(db.Base.Exec(`DELETE FROM entries WHERE id = ?`, id))(b) + _ = must.Return(db.Exec(`DELETE FROM entries WHERE id = ?`, id))(b) } } insertAndDeleteWithPreparedQueryRow := func(b *testing.B) { ids := make([]int64, batchSize) - stmtInsert := must.Return(db.Base.Prepare(`INSERT INTO entries (message) VALUES (?) RETURNING id`))(b) + stmtInsert := must.Return(db.Prepare(`INSERT INTO entries (message) VALUES (?) RETURNING id`))(b) defer stmtInsert.Close() for idx := range ids { must.Succeed(b, stmtInsert.QueryRow("hello").Scan(&ids[idx])) } - stmtDelete := must.Return(db.Base.Prepare(`DELETE FROM entries WHERE id = ?`))(b) + stmtDelete := must.Return(db.Prepare(`DELETE FROM entries WHERE id = ?`))(b) defer stmtDelete.Close() for _, id := range ids { _ = must.Return(stmtDelete.Exec(id))(b) @@ -450,7 +450,7 @@ func BenchmarkORMUpdate(b *testing.B) { oblast.TableNameIs("entries"), oblast.PrimaryKeyIs("id"), ) - gorpDB := gorp.DbMap{Db: db.Base, Dialect: gorp.SqliteDialect{}} + gorpDB := gorp.DbMap{Db: db.DB, Dialect: gorp.SqliteDialect{}} gorpDB.AddTableWithName(GorpEntry{}, "entries").SetKeys(true, "id") gormDB := must.Return(gorm.Open(sqlite.Open(dsn), &gorm.Config{}))(b) @@ -458,7 +458,7 @@ func BenchmarkORMUpdate(b *testing.B) { for _, batchSize := range batchSizesForUpdate { b.Run("N="+strconv.Itoa(batchSize), func(b *testing.B) { // prepare a bunch of records that we can update, in a reproducible way - _ = must.Return(db.Base.Exec(`DELETE FROM entries`)) + _ = must.Return(db.Exec(`DELETE FROM entries`)) recordsForOblast := make([]OblastEntry, batchSize) recordsForOblastForInsert := make([]*OblastEntry, batchSize) for idx := range recordsForOblast { @@ -498,11 +498,11 @@ func BenchmarkORMUpdate(b *testing.B) { } updateWithStraightSqlite := func(b *testing.B, message string) { for _, r := range recordsForOblast { - _ = must.Return(db.Base.Exec(`UPDATE entries SET message = ? WHERE id = ?`, message, r.ID))(b) + _ = must.Return(db.Exec(`UPDATE entries SET message = ? WHERE id = ?`, message, r.ID))(b) } } updateWithPreparedSqlite := func(b *testing.B, message string) { - stmt := must.Return(db.Base.Prepare(`UPDATE entries SET message = ? WHERE id = ?`))(b) + stmt := must.Return(db.Prepare(`UPDATE entries SET message = ? WHERE id = ?`))(b) for _, r := range recordsForOblast { _ = must.Return(stmt.Exec(message, r.ID))(b) } @@ -510,7 +510,7 @@ func BenchmarkORMUpdate(b *testing.B) { } checkRecordsUpdated := func(b *testing.B, message string) { var count int64 - must.Succeed(b, db.Base.QueryRow(`SELECT COUNT(*) FROM entries WHERE message = ?`, message).Scan(&count)) + must.Succeed(b, db.QueryRow(`SELECT COUNT(*) FROM entries WHERE message = ?`, message).Scan(&count)) assert.Equal(b, count, int64(batchSize)) } diff --git a/benchmark/postgres_test.go b/benchmark/postgres_test.go index 02c2c43..8f1b7e8 100644 --- a/benchmark/postgres_test.go +++ b/benchmark/postgres_test.go @@ -36,14 +36,14 @@ func BenchmarkPostgresHeadingHeadingHeadingHeadingHeadingHeadingHeadingHeading(b const defaultPostgresDSN = "host=localhost user=postgres dbname=oblast_benchmark sslmode=disable" -func connectToPostgresTestDB(t testing.TB, recordCount int) oblast.SqlHandle[*sql.DB] { +func connectToPostgresTestDB(t testing.TB, recordCount int) *oblast.DB { dsn := cmp.Or(os.Getenv("BENCHMARK_POSTGRES_DSN"), defaultPostgresDSN) - db := oblast.Wrap(must.Return(sql.Open("postgres", dsn))(t)) - _ = must.Return(db.Base.Exec(`CREATE TEMPORARY TABLE entries (id BIGSERIAL, message TEXT)`))(t) + db := oblast.NewDB(must.Return(sql.Open("postgres", dsn))(t)) + _ = must.Return(db.Exec(`CREATE TEMPORARY TABLE entries (id BIGSERIAL, message TEXT)`))(t) if recordCount > 0 { // fill in some random-looking, but deterministic data - stmt := must.Return(db.Base.Prepare(`INSERT INTO entries (id, message) VALUES ($1, $2)`))(t) + stmt := must.Return(db.Prepare(`INSERT INTO entries (id, message) VALUES ($1, $2)`))(t) for idx := range recordCount { buf := sha256.Sum256([]byte(strconv.Itoa(idx))) _ = must.Return(stmt.Exec(idx, fmt.Sprintf("sha256:%x", buf[:])))(t) @@ -107,7 +107,7 @@ func BenchmarkPostgresSelect(b *testing.B) { b.Run("driver=pq/strategy=straight", func(b *testing.B) { for b.Loop() { var records []OblastEntry - rows := must.Return(pqDB.Base.Query(query))(b) //nolint:rowserrcheck // false positive + rows := must.Return(pqDB.Query(query))(b) //nolint:rowserrcheck // false positive for rows.Next() { var e OblastEntry must.Succeed(b, rows.Scan(&e.ID, &e.Message)) @@ -173,7 +173,7 @@ func BenchmarkPostgresSelectOne(b *testing.B) { id int64 message string ) - must.Succeed(b, pqDB.Base.QueryRow(query).Scan(&id, &message)) + must.Succeed(b, pqDB.QueryRow(query).Scan(&id, &message)) assert.Equal(b, id, int64(recordID)) } }) @@ -239,10 +239,10 @@ func BenchmarkPostgresInsertAndDelete(b *testing.B) { for b.Loop() { ids := make([]int64, batchSize) for idx := range ids { - must.Succeed(b, pqDB.Base.QueryRow(insertQuery, "hello").Scan(&ids[idx])) + must.Succeed(b, pqDB.QueryRow(insertQuery, "hello").Scan(&ids[idx])) } for _, id := range ids { - _ = must.Return(pqDB.Base.Exec(deleteQuery, id))(b) + _ = must.Return(pqDB.Exec(deleteQuery, id))(b) } } }) @@ -262,12 +262,12 @@ func BenchmarkPostgresInsertAndDelete(b *testing.B) { b.Run("driver=pq/strategy=prepared", func(b *testing.B) { for b.Loop() { ids := make([]int64, batchSize) - stmtInsert := must.Return(pqDB.Base.Prepare(insertQuery))(b) + stmtInsert := must.Return(pqDB.Prepare(insertQuery))(b) defer stmtInsert.Close() for idx := range ids { must.Succeed(b, stmtInsert.QueryRow("hello").Scan(&ids[idx])) } - stmtDelete := must.Return(pqDB.Base.Prepare(deleteQuery))(b) + stmtDelete := must.Return(pqDB.Prepare(deleteQuery))(b) defer stmtDelete.Close() for _, id := range ids { _ = must.Return(stmtDelete.Exec(id))(b) @@ -309,7 +309,7 @@ func BenchmarkPostgresUpdate(b *testing.B) { for _, batchSize := range batchSizesForInsertDelete { b.Run("N="+strconv.Itoa(batchSize), func(b *testing.B) { // prepare a bunch of records that we can update, in a reproducible way - _ = must.Return(pqDB.Base.Exec(`DELETE FROM entries`)) + _ = must.Return(pqDB.Exec(`DELETE FROM entries`)) _ = must.Return(pgxConn.Exec(noctx, `DELETE FROM entries`)) pqRecords := make([]OblastEntry, batchSize) pqRecordsForInsert := make([]*OblastEntry, batchSize) @@ -356,7 +356,7 @@ func BenchmarkPostgresUpdate(b *testing.B) { b.Run("driver=pq/strategy=straight", func(b *testing.B) { loop(b, func(message string) { for _, r := range pqRecords { - _ = must.Return(pqDB.Base.Exec(updateQuery, message, r.ID))(b) + _ = must.Return(pqDB.Exec(updateQuery, message, r.ID))(b) } }) }) @@ -371,7 +371,7 @@ func BenchmarkPostgresUpdate(b *testing.B) { b.Run("driver=pq/strategy=prepared", func(b *testing.B) { loop(b, func(message string) { - stmt := must.Return(pqDB.Base.Prepare(updateQuery))(b) + stmt := must.Return(pqDB.Prepare(updateQuery))(b) for _, r := range pqRecords { _ = must.Return(stmt.Exec(message, r.ID))(b) } @@ -12,47 +12,120 @@ import ( ) // Handle contains behavior that database handles must offer to Oblast. -// The standard-library types [*sql.DB] and [*sql.Tx] can satisfy this interface through the [Wrap] function. // Custom implementations of this interface can be used to connect non-std database drivers to Oblast. type Handle = handle.Handle -// SqlHandle wraps types like [*sql.DB] or [*sql.Tx] into a [Handle] that can be used with Oblast. -// TODO: separate types for wrapped *sql.DB and wrapped *sql.Tx, so we can have those types as embedded fields and forward method implementations -type SqlHandle[T SqlExecutor] struct { - // The original database or transaction handle. - // It is safe to read this field to execute operations that Oblast does not handle (e.g. transactions, savepoints or OLAP queries). - Base T +//////////////////////////////////////////////////////////////////////////////// +// public API for database/sql compatibility +// +// NOTE: The internal structure of these types looks weird at first glance, with +// the pointer to the underlying instance duplicated, but of course that's deliberate. +// +// If our types implemented [Handle] directly, every function call taking them as an argument +// of type [Handle] (e.g. any of the methods on [Store]) would allocate a new fat pointer +// when converting from e.g. [*DB] at the callsite to [Handle] in the argument value. +// +// To circumvent this, our types only _have_ [Handle] instances within them within them +// as an embedded field, thus implementing [Handle] indirectly instead of directly. + +// DB wraps [*sql.DB] into a [Handle] that can be used with Oblast. +// +// Because this type has [*sql.DB] as an embedded field, +// all methods from that type work on this type as well. +type DB struct { + *sql.DB + Handle +} + +// NewDB wraps an instance of [*sql.DB] into Oblast's own [DB] type. +func NewDB(db *sql.DB) *DB { + return &DB{db, sqlHandle[*sql.DB]{db}} +} + +// Begin is like [sql.DB.Begin], but wraps the resulting transaction for use with Oblast. +func (db *DB) Begin() (*Tx, error) { + tx, err := db.DB.Begin() + return maybe(NewTx, tx), err +} + +// BeginTx is like [sql.DB.BeginTx], but wraps the resulting transaction for use with Oblast. +func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { + tx, err := db.DB.BeginTx(ctx, opts) + return maybe(NewTx, tx), err +} + +// Conn is like [sql.DB.Conn], but wraps the resulting connection for use with Oblast. +func (db *DB) Conn(ctx context.Context) (*Conn, error) { + conn, err := db.DB.Conn(ctx) + return maybe(NewConn, conn), err +} + +// Conn wraps [*sql.Conn] into a [Handle] that can be used with Oblast. +// +// Because this type has [*sql.Conn] as an embedded field, +// all methods from that type work on this type as well. +type Conn struct { + *sql.Conn + Handle +} + +// NewConn wraps an instance of [*sql.Conn] into Oblast's own [Conn] type. +func NewConn(db *sql.Conn) *Conn { + return &Conn{db, sqlHandle[*sql.Conn]{db}} +} - // If this is not true, then any methods on this type will panic. - // This is just to enforce that the handle is constructed with Wrap(), thus guaranteeing future compatibility if actual important private struct fields are added later. - ok bool +// BeginTx is like [sql.DB.BeginTx], but wraps the resulting transaction for use with Oblast. +func (conn *Conn) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { + tx, err := conn.Conn.BeginTx(ctx, opts) + return maybe(NewTx, tx), err } -// Wrap converts an [*sql.DB] or [*sql.Tx] into a [Handle] that can be used with Oblast functions. -func Wrap[T SqlExecutor](dbOrTx T) SqlHandle[T] { - return SqlHandle[T]{Base: dbOrTx, ok: true} +// Tx wraps [*sql.Tx] into a [Handle] that can be used with Oblast. +// +// Because this type has [*sql.Tx] as an embedded field, +// all methods from that type work on this type as well. +type Tx struct { + *sql.Tx + Handle } -// SqlExecutor is an interface covered by both [*sql.DB] and [*sql.Tx]. -// It appears in the signature of function [Wrap]. -type SqlExecutor interface { +// NewTx wraps an instance of [*sql.Tx] into Oblast's own [Tx] type. +func NewTx(db *sql.Tx) *Tx { + return &Tx{db, sqlHandle[*sql.Tx]{db}} +} + +func maybe[T, U any](wrap func(*T) *U, value *T) *U { + if value == nil { + return nil + } + return wrap(value) +} + +// prove that we implement the interfaces that we claim +var ( + _ Handle = &DB{} + _ Handle = &Conn{} + _ Handle = &Tx{} +) + +//////////////////////////////////////////////////////////////////////////////// +// Handle implementation for database/sql types + +// sqlExecutor is an interface covered by both [*sql.DB], [*sql.Conn] and [*sql.Tx]. +type sqlExecutor interface { ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row } -// static assertion that the respective types implement the interface -var ( - _ SqlExecutor = &sql.DB{} - _ SqlExecutor = &sql.Tx{} -) +// sqlHandle provides the [Handle] implementation for any type that implements [sqlExecutor]. +type sqlHandle[T sqlExecutor] struct { + Base T +} // OblastPrepare implements the [Handle] interface. -func (h SqlHandle[T]) OblastPrepare(ctx context.Context, query string, repeated bool) (handle.Statement, error) { - if !h.ok { - panic("SqlHandle was not constructed through oblast.Wrap()!") - } +func (h sqlHandle[T]) OblastPrepare(ctx context.Context, query string, repeated bool) (handle.Statement, error) { if !repeated { return wrappedStatement{h.Base, query, nil}, nil } @@ -64,15 +137,12 @@ func (h SqlHandle[T]) OblastPrepare(ctx context.Context, query string, repeated } // OblastQuery implements the [Handle] interface. -func (h SqlHandle[T]) OblastQuery(ctx context.Context, query string, args []any) (handle.Rows, error) { - if !h.ok { - panic("SqlHandle was not constructed through oblast.Wrap()!") - } +func (h sqlHandle[T]) OblastQuery(ctx context.Context, query string, args []any) (handle.Rows, error) { return h.Base.QueryContext(ctx, query, args...) //nolint:rowserrcheck // the caller does the check } type wrappedStatement struct { - db SqlExecutor + db sqlExecutor query string stmt *sql.Stmt // nil if repeated = false } @@ -24,9 +24,7 @@ // // Then use it many times to perform load and store operations: // -// func doStuff(db *sql.DB) error { -// dbh := oblast.Wrap(db) -// +// func doStuff(db *oblast.DB) error { // newEntry := LogEntry{ // CreatedAt: time.Now(), // Message: "Hello World.", @@ -44,6 +42,10 @@ // fmt.Printf("there are %d log entries so far", len(allEntries)) // } // +// In this example, "oblast.DB" is a thin wrapper around [*sql.DB], which can be obtained with the [NewDB] function. +// A [*DB] can be used in the same way as an [*sql.DB], but if Oblast is only to be used for specific functions, +// then individual [*sql.Conn] or [*sql.Tx] instances can also be wrapped with the [NewConn] and [NewTx] functions. +// // # Mapping rules for record types // // If the database column has a different name (or casing, e.g. "id" vs. "ID") than the field name, provide it in the field tag "db". @@ -133,9 +135,7 @@ func StructTagKeyIs(key string) PlanOption { return func(opts *planOpts) { opts.StructTagKey = key } } -// Store is the main interface of this library. -// -// It holds information on how to read and write data into record type R, +// Store holds information on how to read and write data into record type R, // and can also be used to execute autogenerated queries if the respective [PlanOption] values were provided during [NewStore]. type Store[R any] struct { dialect Dialect diff --git a/query_test.go b/query_test.go index 382a463..a67dade 100644 --- a/query_test.go +++ b/query_test.go @@ -18,7 +18,7 @@ import ( func TestInsertBasic(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) type basicRecord struct { ID int64 `oblast:"id,auto"` @@ -52,7 +52,7 @@ func TestInsertBasic(t *testing.T) { func TestUpdateBasic(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) type basicRecord struct { ID int64 `db:"id,auto"` @@ -82,7 +82,7 @@ func TestUpdateBasic(t *testing.T) { func TestDeleteBasic(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) type basicRecord struct { ID int64 `db:"id,auto"` @@ -112,7 +112,7 @@ func TestDeleteBasic(t *testing.T) { func TestUpsertBasicWithAutoColumn(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) type basicRecord struct { ID int64 `db:"id,auto"` @@ -158,7 +158,7 @@ func TestUpsertBasicWithAutoColumn(t *testing.T) { func TestWriteQueriesNotPossible(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) type basicRecord struct { ID int64 `db:"id,auto"` @@ -187,7 +187,7 @@ func TestWriteQueriesNotPossible(t *testing.T) { func TestWriteQueriesFailDuringPrepare(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) type basicRecord struct { ID int64 `db:"id,auto"` @@ -236,7 +236,7 @@ func TestWriteQueriesFailDuringPrepare(t *testing.T) { func TestUpdateOrUpsertFailsOnMissingRecord(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) type basicRecord struct { ID int64 `db:"id,auto"` @@ -271,7 +271,7 @@ func TestUpdateOrUpsertFailsOnMissingRecord(t *testing.T) { func TestInsertFailsOnFilledAutoField(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) type basicRecord struct { ID int64 `db:"id,auto"` @@ -294,7 +294,7 @@ func TestInsertFailsOnFilledAutoField(t *testing.T) { func TestInsertAndUpsertWithNoAutoColumns(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) type relation struct { FooID int64 `db:"foo_id"` @@ -325,7 +325,7 @@ func TestInsertAndUpsertWithNoAutoColumns(t *testing.T) { func TestUpsertFailsOnMixedAutoFieldState(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) type complexRecord struct { ID int64 `db:"id,auto"` @@ -350,7 +350,7 @@ func TestUpsertFailsOnMixedAutoFieldState(t *testing.T) { func TestUninitializedTransparentPointerStructs(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) // declare a record type that has a transparent pointer struct containing non-primary-key fields type timestamps struct { diff --git a/runtimeindex_test.go b/runtimeindex_test.go index 8e0b68f..ea77560 100644 --- a/runtimeindex_test.go +++ b/runtimeindex_test.go @@ -16,7 +16,7 @@ import ( func TestRuntimeIndex(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) type basicRecord struct { ID int64 `db:"id"` diff --git a/select_test.go b/select_test.go index 7b4191a..c55fe42 100644 --- a/select_test.go +++ b/select_test.go @@ -20,7 +20,7 @@ import ( func TestSelectReturningSomeRecords(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) type basicRecord struct { ID int64 `db:"id"` @@ -138,7 +138,7 @@ func TestSelectReturningSomeRecords(t *testing.T) { func TestSelectReturningNoRecords(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) type basicRecord struct { ID int64 `db:"id"` @@ -229,7 +229,7 @@ func TestSelectReturningNoRecords(t *testing.T) { func TestSelectIntoUnexpectedField(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) type basicRecord struct { ID int64 `db:"id"` @@ -268,7 +268,7 @@ func TestSelectIntoUnexpectedField(t *testing.T) { func TestSelectWithScanError(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) type basicRecord struct { ID int64 `db:"id"` @@ -331,7 +331,7 @@ func TestSelectWithScanError(t *testing.T) { func TestSelectIntoEmbeddedTypes(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) type HasCreatedAt struct { CreatedAt time.Time `db:"created_at"` @@ -442,7 +442,7 @@ func TestSelectIntoEmbeddedTypes(t *testing.T) { func TestSelectCapturingQueryError(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) type basicRecord struct { ID int64 `db:"id"` @@ -490,7 +490,7 @@ func TestSelectCapturingQueryError(t *testing.T) { func TestSelectCapturingCloseError(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) type basicRecord struct { ID int64 `db:"id"` @@ -553,7 +553,7 @@ func TestSelectCapturingCloseError(t *testing.T) { func TestSelectNotPossibleWithoutTableName(t *testing.T) { ctx := t.Context() md := mock.NewDriver() - db := oblast.Wrap(sql.OpenDB(md)) + db := oblast.NewDB(sql.OpenDB(md)) type basicRecord struct { ID int64 `db:"id"` |
