aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2026-04-17 14:53:52 +0200
committerStefan Majewsky <majewsky@gmx.net>2026-04-17 14:53:55 +0200
commit52f44287216b47149da9eb3f038408447f0e5981 (patch)
tree8b81794b83d11b26e6753f65d65641b34336995c
parente73aee05956d7917e7d76ab793d5e2291ace6416 (diff)
downloadgo-oblast-52f44287216b47149da9eb3f038408447f0e5981.tar.gz
improve test coverage, error reporting for Select()
-rw-r--r--errors.go68
-rw-r--r--internal/assert/assert.go8
-rw-r--r--internal/mock/mock.go12
-rw-r--r--oblast.go19
-rw-r--r--query.go6
-rw-r--r--select.go43
-rw-r--r--select_test.go192
7 files changed, 242 insertions, 106 deletions
diff --git a/errors.go b/errors.go
new file mode 100644
index 0000000..1e81060
--- /dev/null
+++ b/errors.go
@@ -0,0 +1,68 @@
+// SPDX-FileCopyrightText: 2026 Stefan Majewsky <majewsky@gmx.net>
+// SPDX-License-Identifier: Apache-2.0
+
+package oblast
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+)
+
+// MissingRecordError is returned by [Store.Update] if one of the rows to be updated does not exist in the DB.
+type MissingRecordError[R any] struct {
+ // The record that was provided to [Store.Update],
+ // but for which no row with the same primary key values could be located.
+ Record R
+ plan plan
+}
+
+// Error implements the builtin/error interface.
+func (e MissingRecordError[R]) Error() string {
+ keyDescs := make([]string, len(e.plan.PrimaryKeyColumnNames))
+ v := reflect.ValueOf(e.Record)
+ for idx, columnName := range e.plan.PrimaryKeyColumnNames {
+ keyDescs[idx] = fmt.Sprintf("%s = %#v", columnName, v.FieldByIndex(e.plan.IndexByColumnName[columnName]))
+ }
+ return "could not UPDATE record that does not exist in the database: " + strings.Join(keyDescs, ", ")
+}
+
+// An error type that optionally contains either one of the following or both:
+// - a core error from an IO operation (e.g. a database read)
+// - an auxiliary error from closing or otherwise cleaning up the respective IO handle
+type ioError struct {
+ MainError error
+ CleanupError error
+ CleanupOperation string
+}
+
+func newIOError(err error, cleanupOperation string, cleanupErr error) error {
+ if err == nil && cleanupErr == nil {
+ return nil
+ }
+ return ioError{err, cleanupErr, cleanupOperation}
+}
+
+// Error implements the builtin/error interface.
+func (e ioError) Error() string {
+ switch {
+ case e.CleanupError == nil:
+ return e.MainError.Error()
+ case e.MainError == nil:
+ return fmt.Sprintf("during %s(): %s", e.CleanupOperation, e.CleanupError.Error())
+ default:
+ return fmt.Sprintf("%s (additional error during %s(): %s)", e.MainError.Error(), e.CleanupOperation, e.CleanupError.Error())
+ }
+}
+
+// Unwrap implements the interface implied by the documentation of package errors.
+func (e ioError) Unwrap() []error {
+ result := make([]error, 0, 2)
+ if e.MainError != nil {
+ result = append(result, e.MainError)
+ }
+ if e.CleanupError != nil {
+ result = append(result, e.CleanupError)
+ }
+ return result
+}
diff --git a/internal/assert/assert.go b/internal/assert/assert.go
index 26f91ff..6e641ca 100644
--- a/internal/assert/assert.go
+++ b/internal/assert/assert.go
@@ -4,6 +4,8 @@
package assert
import (
+ "cmp"
+ "errors"
"reflect"
"testing"
)
@@ -26,6 +28,12 @@ func DeepEqual[V any](t testing.TB, actual, expected V) {
}
}
+// ErrEqual is a test assertion.
+func ErrEqual(t testing.TB, actual error, expected string) {
+ t.Helper()
+ Equal(t, cmp.Or(actual, errors.New("<success>")).Error(), expected)
+}
+
// SliceEqual is a test assertion.
func SliceEqual[V comparable](t testing.TB, actual []V, expected ...V) {
t.Helper()
diff --git a/internal/mock/mock.go b/internal/mock/mock.go
index d3358c4..ecbb03e 100644
--- a/internal/mock/mock.go
+++ b/internal/mock/mock.go
@@ -245,8 +245,9 @@ func (r result) RowsAffected() (int64, error) {
// Rows is a mock response for a Query() or QueryRow() call.
// It is constructed by [ResponseSet.ExpectQuery].
type Rows struct {
- columns []string
- results [][]any
+ columns []string
+ results [][]any
+ closeError error
}
// AndReturnColumns configures the set of column names that will be returend by this query.
@@ -272,6 +273,11 @@ func (r *Rows) WithRow(values ...any) *Rows {
return r
}
+// AndCloseFailsWith sets up Close() for this Rows to fail with the provided error message.
+func (r *Rows) AndCloseFailsWith(err error) {
+ r.closeError = err
+}
+
type rows struct {
r Rows
closed bool
@@ -285,7 +291,7 @@ func (r *rows) Columns() []string {
// Close implements the [driver.Rows] interface.
func (r *rows) Close() error {
r.closed = true
- return nil
+ return r.r.closeError
}
// Next implements the [driver.Rows] interface.
diff --git a/oblast.go b/oblast.go
index 9a68606..52c0cfd 100644
--- a/oblast.go
+++ b/oblast.go
@@ -100,7 +100,6 @@ import (
"database/sql/driver"
"fmt"
"reflect"
- "strings"
)
var (
@@ -171,21 +170,3 @@ func MustNewStore[R any](dialect Dialect, opts ...PlanOption) Store[R] {
}
return store
}
-
-// MissingRecordError is returned by [Store.Update] if one of the rows to be updated does not exist in the DB.
-type MissingRecordError[R any] struct {
- // The record that was provided to [Store.Update],
- // but for which no row with the same primary key values could be located.
- Record R
- plan plan
-}
-
-// Error implements the builtin/error interface.
-func (e MissingRecordError[R]) Error() string {
- keyDescs := make([]string, len(e.plan.PrimaryKeyColumnNames))
- v := reflect.ValueOf(e.Record)
- for idx, columnName := range e.plan.PrimaryKeyColumnNames {
- keyDescs[idx] = fmt.Sprintf("%s = %#v", columnName, v.FieldByIndex(e.plan.IndexByColumnName[columnName]))
- }
- return "could not UPDATE record that does not exist in the database: " + strings.Join(keyDescs, ", ")
-}
diff --git a/query.go b/query.go
index 41d61c7..1b9ae46 100644
--- a/query.go
+++ b/query.go
@@ -56,7 +56,7 @@ func (s Store[R]) Insert(db Handle, records ...R) (returnedRecords []R, returned
return nil, fmt.Errorf("during Prepare(): %w", err)
}
defer func() {
- returnedError = mergeCloseError("Stmt", returnedError, stmt.Close())
+ returnedError = newIOError(returnedError, "Stmt.Close", stmt.Close())
}()
}
@@ -127,7 +127,7 @@ func (s Store[R]) Update(db Handle, records ...R) (returnedError error) {
return fmt.Errorf("during Prepare(): %w", err)
}
defer func() {
- returnedError = mergeCloseError("Stmt", returnedError, stmt.Close())
+ returnedError = newIOError(returnedError, "Stmt.Close", stmt.Close())
}()
}
@@ -184,7 +184,7 @@ func (s Store[R]) Delete(db Handle, records ...R) (returnedError error) {
return fmt.Errorf("during Prepare(): %w", err)
}
defer func() {
- returnedError = mergeCloseError("Stmt", returnedError, stmt.Close())
+ returnedError = newIOError(returnedError, "Stmt.Close", stmt.Close())
}()
}
diff --git a/select.go b/select.go
index 9de6e13..6073ce6 100644
--- a/select.go
+++ b/select.go
@@ -14,7 +14,7 @@ import (
// according to the column names reported by the database as part of the result set.
//
// An error is returned if any column name in the result set does not correspond to an addressable field in R.
-func (s Store[R]) Select(db Handle, query string, args ...any) (result []R, returnedError error) {
+func (s Store[R]) Select(db Handle, query string, args ...any) ([]R, error) {
// NOTE: This function body should be as short as possible to reduce the binary size after monomorphization.
// Any expression that does not depend on type R should be factored out into a reusable function.
@@ -22,10 +22,8 @@ func (s Store[R]) Select(db Handle, query string, args ...any) (result []R, retu
if err != nil {
return nil, err
}
- defer func() {
- returnedError = mergeCloseError("Rows", returnedError, rows.Close())
- }()
+ var result []R
slots := make([]any, len(indexes))
for rows.Next() {
var target R
@@ -36,7 +34,7 @@ func (s Store[R]) Select(db Handle, query string, args ...any) (result []R, retu
result = append(result, target)
}
- return result, nil
+ return result, newIOError(err, "Rows.Err", rows.Err())
}
// SelectWhere is like [Store.Select], but you only provide the part of the SELECT query that comes after the WHERE.
@@ -52,7 +50,7 @@ func (s Store[R]) Select(db Handle, query string, args ...any) (result []R, retu
// Besides a condition for the WHERE clause, it may contain additional clauses, such as ORDER BY or LIMIT.
//
// Returns an error if [NewStore] was called without the [TableNameIs] option, which is required to generate a query for this method.
-func (s Store[R]) SelectWhere(db Handle, partialQuery string, args ...any) (result []R, returnedError error) {
+func (s Store[R]) SelectWhere(db Handle, partialQuery string, args ...any) ([]R, error) {
// NOTE: This function body should be as short as possible to reduce the binary size after monomorphization.
// Any expression that does not depend on type R should be factored out into a reusable function.
@@ -60,10 +58,8 @@ func (s Store[R]) SelectWhere(db Handle, partialQuery string, args ...any) (resu
if err != nil {
return nil, err
}
- defer func() {
- returnedError = mergeCloseError("Rows", returnedError, rows.Close())
- }()
+ var result []R
slots := make([]any, len(indexes))
for rows.Next() {
var target R
@@ -74,7 +70,7 @@ func (s Store[R]) SelectWhere(db Handle, partialQuery string, args ...any) (resu
result = append(result, target)
}
- return result, nil
+ return result, newIOError(err, "Rows.Err", rows.Err())
}
func startSelectQuery(db Handle, plan plan, query string, args ...any) (returnedRows *sql.Rows, indexes [][]int, returnedError error) {
@@ -84,10 +80,8 @@ func startSelectQuery(db Handle, plan plan, query string, args ...any) (returned
}
defer func() {
if returnedError != nil {
- closeErr := rows.Close() // NOTE: Not `returnedRows.Close()`! We may have `rows != nil && returnedRows == nil`.
- if closeErr != nil {
- returnedError = fmt.Errorf("%w (additional error during rows.Close(): %s)", returnedError, closeErr.Error())
- }
+ // NOTE: Not `returnedRows.Close()`! We may have `rows != nil && returnedRows == nil`.
+ returnedError = newIOError(returnedError, "Rows.Close", rows.Close())
}
}()
@@ -116,6 +110,9 @@ func startSelectWhereQuery(db Handle, plan plan, partialQuery string, args ...an
}
query := plan.Select.Query + partialQuery
rows, err = db.Query(query, args...)
+ if err != nil {
+ err = fmt.Errorf("during Query(): %w", err)
+ }
return rows, plan.Select.ScanIndexes, err
}
@@ -127,18 +124,11 @@ func collectRow(rows *sql.Rows, plan plan, v reflect.Value, slots []any, indexes
for idx, index := range indexes {
slots[idx] = v.FieldByIndex(index).Addr().Interface()
}
- return rows.Scan(slots...)
-}
-
-func mergeCloseError(typeName string, err, closeErr error) error {
- switch {
- case closeErr == nil:
- return err
- case err == nil:
- return fmt.Errorf("during %s.Close(): %w", typeName, closeErr)
- default:
- return fmt.Errorf("%w (additional error during %s.Close(): %s)", err, typeName, closeErr.Error())
+ err := rows.Scan(slots...)
+ if err != nil {
+ return newIOError(err, "Rows.Close", rows.Close())
}
+ return nil
}
// SelectOne executes the provided SQL query and fills an instance of the record type R if there is exactly one row in the result set,
@@ -167,8 +157,7 @@ func (s Store[R]) SelectOne(db Handle, query string, args ...any) (result R, err
// SelectOneWhere is like [Store.SelectOne], but you only provide the part of the SELECT query that comes after the WHERE.
// See [Store.SelectWhere] for an explanation of how the full query is constructed from this partial query.
//
-// This method is significantly more efficient than [Store.SelectOne].
-// Prefer using it instaed of [Store.SelectOne] whenever possible.
+// This method is more efficient than [Store.SelectOne] on CPU runtime, but has a slight memory allocation overhead.
func (s Store[R]) SelectOneWhere(db Handle, partialQuery string, args ...any) (result R, err error) {
// NOTE: This function body should be as short as possible to reduce the binary size after monomorphization.
// Any expression that does not depend on type R should be factored out into a reusable function.
diff --git a/select_test.go b/select_test.go
index f364e1c..e985548 100644
--- a/select_test.go
+++ b/select_test.go
@@ -5,6 +5,7 @@ package oblast_test
import (
"database/sql"
+ "errors"
"testing"
"time"
@@ -110,7 +111,7 @@ func TestSelectReturningNoRecords(t *testing.T) {
ExpectQueryWithArgs(3).
AndReturnColumns("name", "id")
_, err := store.SelectOne(db, `SELECT * FROM basic_records WHERE id < ?`, 3)
- assert.Equal(t, err.Error(), sql.ErrNoRows.Error())
+ assert.ErrEqual(t, err, sql.ErrNoRows.Error())
})
t.Run("using Store.SelectOneWhere", func(t *testing.T) {
@@ -118,7 +119,7 @@ func TestSelectReturningNoRecords(t *testing.T) {
ExpectQueryWithArgs(3).
AndReturnColumns("id", "name")
_, err := store.SelectOneWhere(db, `id < ?`, 3)
- assert.Equal(t, err.Error(), sql.ErrNoRows.Error())
+ assert.ErrEqual(t, err, sql.ErrNoRows.Error())
})
}
@@ -137,27 +138,26 @@ func TestSelectIntoUnexpectedField(t *testing.T) {
)
expectedError := "result has column \"name\" in position 0, but no field in type basicRecord has `db:\"name\"`"
-
- // NOTE: This problem cannot occur with SelectWhere() and SelectOneWhere() because of their use of query generation.
-
- t.Run("using Store.Select", func(t *testing.T) {
+ commonSetup := func() {
md.ForQuery(`SELECT * FROM basic_records WHERE id < ?`).
ExpectQueryWithArgs(3).
AndReturnColumns("name", "id").
WithRow("foo", 1).
WithRow("bar", 2)
+ }
+
+ // NOTE: This problem cannot occur with SelectWhere() and SelectOneWhere() because of their use of query generation.
+
+ t.Run("using Store.Select", func(t *testing.T) {
+ commonSetup()
_, err := store.Select(db, `SELECT * FROM basic_records WHERE id < ?`, 3)
- assert.Equal(t, err.Error(), expectedError)
+ assert.ErrEqual(t, err, expectedError)
})
t.Run("using Store.SelectOne", func(t *testing.T) {
- md.ForQuery(`SELECT * FROM basic_records WHERE id < ?`).
- ExpectQueryWithArgs(3).
- AndReturnColumns("name", "id").
- WithRow("ffoo", 1).
- WithRow("bbar", 2)
+ commonSetup()
_, err := store.SelectOne(db, `SELECT * FROM basic_records WHERE id < ?`, 3)
- assert.Equal(t, err.Error(), expectedError)
+ assert.ErrEqual(t, err, expectedError)
})
}
@@ -176,45 +176,36 @@ func TestSelectWithScanError(t *testing.T) {
)
expectedError := `sql: Scan error on column index 1, name "created_at": unsupported Scan, storing driver.Value type string into type *time.Time`
-
- t.Run("using Store.Select", func(t *testing.T) {
- md.ForQuery(`SELECT * FROM basic_records WHERE id < ?`).
+ commonSetup := func(query string) {
+ md.ForQuery(query).
ExpectQueryWithArgs(3).
AndReturnColumns("id", "created_at").
WithRow(1, "foo").
WithRow(2, "bar")
+ }
+
+ t.Run("using Store.Select", func(t *testing.T) {
+ commonSetup(`SELECT * FROM basic_records WHERE id < ?`)
_, err := store.Select(db, `SELECT * FROM basic_records WHERE id < ?`, 3)
- assert.Equal(t, err.Error(), expectedError)
+ assert.ErrEqual(t, err, expectedError)
})
t.Run("using Store.SelectWhere", func(t *testing.T) {
- md.ForQuery(`SELECT "id", "created_at" FROM "basic_records" WHERE id < ?`).
- ExpectQueryWithArgs(3).
- AndReturnColumns("id", "created_at").
- WithRow(1, "ffoo").
- WithRow(2, "bbar")
+ commonSetup(`SELECT "id", "created_at" FROM "basic_records" WHERE id < ?`)
_, err := store.SelectWhere(db, `id < ?`, 3)
- assert.Equal(t, err.Error(), expectedError)
+ assert.ErrEqual(t, err, expectedError)
})
t.Run("using Store.SelectOne", func(t *testing.T) {
- md.ForQuery(`SELECT * FROM basic_records WHERE id < ?`).
- ExpectQueryWithArgs(3).
- AndReturnColumns("id", "created_at").
- WithRow(1, "fffoo").
- WithRow(2, "bbbar")
+ commonSetup(`SELECT * FROM basic_records WHERE id < ?`)
_, err := store.SelectOne(db, `SELECT * FROM basic_records WHERE id < ?`, 3)
- assert.Equal(t, err.Error(), expectedError)
+ assert.ErrEqual(t, err, expectedError)
})
t.Run("using Store.SelectOneWhere", func(t *testing.T) {
- md.ForQuery(`SELECT "id", "created_at" FROM "basic_records" WHERE id < ?`).
- ExpectQueryWithArgs(3).
- AndReturnColumns("id", "created_at").
- WithRow(1, "ffffoo").
- WithRow(2, "bbbbar")
+ commonSetup(`SELECT "id", "created_at" FROM "basic_records" WHERE id < ?`)
_, err := store.SelectOneWhere(db, `id < ?`, 3)
- assert.Equal(t, err.Error(), expectedError)
+ assert.ErrEqual(t, err, expectedError)
})
}
@@ -241,12 +232,16 @@ func TestSelectIntoEmbeddedTypes(t *testing.T) {
oblast.PrimaryKeyIs("id"),
)
- t.Run("using Store.Select", func(t *testing.T) {
- md.ForQuery(`SELECT * FROM composite_records`).
+ commonSetup := func(query string) {
+ md.ForQuery(query).
ExpectQueryWithArgs(nil...).
AndReturnColumns("id", "created_at", "updated_at").
WithRow(1, time.Unix(1, 0), time.Unix(3, 0)).
WithRow(2, time.Unix(2, 0), nil)
+ }
+
+ t.Run("using Store.Select", func(t *testing.T) {
+ commonSetup(`SELECT * FROM composite_records`)
records := must.Return(store.Select(db, `SELECT * FROM composite_records`))(t)
assert.SliceDeepEqual(t, records,
compositeRecord{1, HasCreatedAt{time.Unix(1, 0)}, &HasUpdatedAt{new(time.Unix(3, 0))}},
@@ -255,11 +250,7 @@ func TestSelectIntoEmbeddedTypes(t *testing.T) {
})
t.Run("using Store.SelectWhere", func(t *testing.T) {
- md.ForQuery(`SELECT "id", "created_at", "updated_at" FROM "composite_records" WHERE TRUE`).
- ExpectQueryWithArgs(nil...).
- AndReturnColumns("id", "created_at", "updated_at").
- WithRow(1, time.Unix(1, 0), time.Unix(3, 0)).
- WithRow(2, time.Unix(2, 0), nil)
+ commonSetup(`SELECT "id", "created_at", "updated_at" FROM "composite_records" WHERE TRUE`)
records := must.Return(store.SelectWhere(db, `TRUE`))(t)
assert.SliceDeepEqual(t, records,
compositeRecord{1, HasCreatedAt{time.Unix(1, 0)}, &HasUpdatedAt{new(time.Unix(3, 0))}},
@@ -268,11 +259,7 @@ func TestSelectIntoEmbeddedTypes(t *testing.T) {
})
t.Run("using Store.SelectOne", func(t *testing.T) {
- md.ForQuery(`SELECT * FROM composite_records`).
- ExpectQueryWithArgs(nil...).
- AndReturnColumns("id", "created_at", "updated_at").
- WithRow(1, time.Unix(1, 0), time.Unix(3, 0)).
- WithRow(2, time.Unix(2, 0), nil)
+ commonSetup(`SELECT * FROM composite_records`)
record := must.Return(store.SelectOne(db, `SELECT * FROM composite_records`))(t)
assert.DeepEqual(t, record,
compositeRecord{1, HasCreatedAt{time.Unix(1, 0)}, &HasUpdatedAt{new(time.Unix(3, 0))}},
@@ -280,11 +267,7 @@ func TestSelectIntoEmbeddedTypes(t *testing.T) {
})
t.Run("using Store.SelectOneWhere", func(t *testing.T) {
- md.ForQuery(`SELECT "id", "created_at", "updated_at" FROM "composite_records" WHERE TRUE`).
- ExpectQueryWithArgs(nil...).
- AndReturnColumns("id", "created_at", "updated_at").
- WithRow(1, time.Unix(1, 0), time.Unix(3, 0)).
- WithRow(2, time.Unix(2, 0), nil)
+ commonSetup(`SELECT "id", "created_at", "updated_at" FROM "composite_records" WHERE TRUE`)
record := must.Return(store.SelectOneWhere(db, `TRUE`))(t)
assert.DeepEqual(t, record,
compositeRecord{1, HasCreatedAt{time.Unix(1, 0)}, &HasUpdatedAt{new(time.Unix(3, 0))}},
@@ -292,5 +275,106 @@ func TestSelectIntoEmbeddedTypes(t *testing.T) {
})
}
-// TODO: test error capture during Rows.Close()
-// TODO: check for maximum test coverage in select.go
+func TestSelectCapturingQueryError(t *testing.T) {
+ md := mock.NewDriver()
+ db := sql.OpenDB(md)
+
+ type basicRecord struct {
+ ID int64 `db:"id"`
+ Name string `db:"name"`
+ }
+ store := oblast.MustNewStore[basicRecord](
+ oblast.SqliteDialect(),
+ oblast.TableNameIs("basic_records"),
+ oblast.PrimaryKeyIs("id"),
+ )
+
+ t.Run("using Store.Select", func(t *testing.T) {
+ _, err := store.Select(db, `SELECT * FROM basic_records WHERE id < ?`, 3)
+ assert.ErrEqual(t, err, "during Query(): unexpected query: SELECT * FROM basic_records WHERE id < ?")
+ })
+
+ t.Run("using Store.SelectOne", func(t *testing.T) {
+ _, err := store.SelectOne(db, `SELECT * FROM basic_records WHERE id < ?`, 3)
+ assert.ErrEqual(t, err, "during Query(): unexpected query: SELECT * FROM basic_records WHERE id < ?")
+ })
+
+ t.Run("using Store.SelectWhere", func(t *testing.T) {
+ _, err := store.SelectWhere(db, `id < ?`, 3)
+ assert.ErrEqual(t, err, `during Query(): unexpected query: SELECT "id", "name" FROM "basic_records" WHERE id < ?`)
+ })
+
+ t.Run("using Store.SelectOneWhere", func(t *testing.T) {
+ _, err := store.SelectOneWhere(db, `id < ?`, 3)
+ assert.ErrEqual(t, err, `unexpected query: SELECT "id", "name" FROM "basic_records" WHERE id < ?`)
+ })
+}
+
+func TestSelectCapturingCloseError(t *testing.T) {
+ md := mock.NewDriver()
+ db := sql.OpenDB(md)
+
+ type basicRecord struct {
+ ID int64 `db:"id"`
+ Name string `db:"name"`
+ }
+ store := oblast.MustNewStore[basicRecord](
+ oblast.SqliteDialect(),
+ oblast.TableNameIs("basic_records"),
+ oblast.PrimaryKeyIs("id"),
+ )
+
+ commonSetup := func(query string) {
+ md.ForQuery(query).
+ ExpectQueryWithArgs(3).
+ AndReturnColumns("id", "name").
+ WithRow(1, "foo").
+ WithRow(2, "bar").
+ AndCloseFailsWith(errors.New("datacenter on fire"))
+ }
+
+ t.Run("using Store.Select", func(t *testing.T) {
+ commonSetup(`SELECT * FROM basic_records WHERE id < ?`)
+ _, err := store.Select(db, `SELECT * FROM basic_records WHERE id < ?`, 3)
+ assert.ErrEqual(t, err, "during Rows.Err(): datacenter on fire")
+ })
+
+ t.Run("using Store.SelectOne", func(t *testing.T) {
+ commonSetup(`SELECT * FROM basic_records WHERE id < ?`)
+ _, err := store.SelectOne(db, `SELECT * FROM basic_records WHERE id < ?`, 3)
+ assert.ErrEqual(t, err, "during Rows.Err(): datacenter on fire")
+ })
+
+ t.Run("using Store.SelectWhere", func(t *testing.T) {
+ commonSetup(`SELECT "id", "name" FROM "basic_records" WHERE id < ?`)
+ _, err := store.SelectWhere(db, `id < ?`, 3)
+ assert.ErrEqual(t, err, "during Rows.Err(): datacenter on fire")
+ })
+
+ t.Run("using Store.SelectOneWhere", func(t *testing.T) {
+ commonSetup(`SELECT "id", "name" FROM "basic_records" WHERE id < ?`)
+ _, err := store.SelectOneWhere(db, `id < ?`, 3)
+ assert.ErrEqual(t, err, "datacenter on fire")
+ })
+}
+
+func TestSelectNotPossibleWithoutTableName(t *testing.T) {
+ md := mock.NewDriver()
+ db := sql.OpenDB(md)
+
+ type basicRecord struct {
+ ID int64 `db:"id"`
+ Name string `db:"name"`
+ }
+ store := oblast.MustNewStore[basicRecord](oblast.SqliteDialect())
+
+ t.Run("using Store.SelectWhere", func(t *testing.T) {
+ _, err := store.SelectWhere(db, `id < ?`, 3)
+ assert.ErrEqual(t, err, "cannot execute SelectWhere() because query could not be autogenerated")
+ })
+
+ t.Run("using Store.SelectOneWhere", func(t *testing.T) {
+ _, err := store.SelectOneWhere(db, `id < ?`, 3)
+ assert.ErrEqual(t, err, "cannot execute SelectOneWhere() because query could not be autogenerated")
+ })
+}