667 lines
20 KiB
Go
667 lines
20 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"database/sql/driver"
|
|
"io"
|
|
"sync"
|
|
"testing"
|
|
|
|
core "dappco.re/go/core"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// New — schema error path
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestCoverage_New_Bad_SchemaConflict(t *testing.T) {
|
|
// Pre-create a database with an INDEX named "entries". When New() runs
|
|
// CREATE TABLE IF NOT EXISTS entries, SQLite returns an error because the
|
|
// name "entries" is already taken by the index.
|
|
databasePath := testPath(t, "conflict.db")
|
|
|
|
database, err := sql.Open("sqlite", databasePath)
|
|
require.NoError(t, err)
|
|
database.SetMaxOpenConns(1)
|
|
_, err = database.Exec("PRAGMA journal_mode=WAL")
|
|
require.NoError(t, err)
|
|
_, err = database.Exec("CREATE TABLE dummy (id INTEGER)")
|
|
require.NoError(t, err)
|
|
_, err = database.Exec("CREATE INDEX entries ON dummy(id)")
|
|
require.NoError(t, err)
|
|
require.NoError(t, database.Close())
|
|
|
|
_, err = New(databasePath)
|
|
require.Error(t, err, "New should fail when an index named entries already exists")
|
|
assert.Contains(t, err.Error(), "store.New: ensure schema")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// GetAll — scan error path
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestCoverage_GetAll_Bad_ScanError(t *testing.T) {
|
|
// Trigger a scan error by inserting a row with a NULL key. The production
|
|
// code scans into plain strings, which cannot represent NULL.
|
|
storeInstance, err := New(":memory:")
|
|
require.NoError(t, err)
|
|
defer storeInstance.Close()
|
|
|
|
// Insert a normal row first so the query returns results.
|
|
require.NoError(t, storeInstance.Set("g", "good", "value"))
|
|
|
|
// Restructure the table to allow NULLs, then insert a NULL-key row.
|
|
_, err = storeInstance.database.Exec("ALTER TABLE entries RENAME TO entries_backup")
|
|
require.NoError(t, err)
|
|
_, err = storeInstance.database.Exec(`CREATE TABLE entries (
|
|
group_name TEXT,
|
|
entry_key TEXT,
|
|
entry_value TEXT,
|
|
expires_at INTEGER
|
|
)`)
|
|
require.NoError(t, err)
|
|
_, err = storeInstance.database.Exec("INSERT INTO entries SELECT * FROM entries_backup")
|
|
require.NoError(t, err)
|
|
_, err = storeInstance.database.Exec("INSERT INTO entries (group_name, entry_key, entry_value) VALUES ('g', NULL, 'null-key-val')")
|
|
require.NoError(t, err)
|
|
_, err = storeInstance.database.Exec("DROP TABLE entries_backup")
|
|
require.NoError(t, err)
|
|
|
|
_, err = storeInstance.GetAll("g")
|
|
require.Error(t, err, "GetAll should fail when a row contains a NULL key")
|
|
assert.Contains(t, err.Error(), "store.All: scan")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// GetAll — rows iteration error path
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestCoverage_GetAll_Bad_RowsError(t *testing.T) {
|
|
// Trigger rows.Err() by corrupting the database file so that iteration
|
|
// starts successfully but encounters a malformed page mid-scan.
|
|
databasePath := testPath(t, "corrupt-getall.db")
|
|
|
|
storeInstance, err := New(databasePath)
|
|
require.NoError(t, err)
|
|
|
|
// Insert enough rows to span multiple database pages.
|
|
const rows = 5000
|
|
for i := range rows {
|
|
require.NoError(t, storeInstance.Set("g",
|
|
core.Sprintf("key-%06d", i),
|
|
core.Sprintf("value-with-padding-%06d-xxxxxxxxxxxxxxxxxxxxxxxx", i)))
|
|
}
|
|
storeInstance.Close()
|
|
|
|
// Force a WAL checkpoint so all data is in the main database file.
|
|
rawDatabase, err := sql.Open("sqlite", databasePath)
|
|
require.NoError(t, err)
|
|
rawDatabase.SetMaxOpenConns(1)
|
|
_, err = rawDatabase.Exec("PRAGMA wal_checkpoint(TRUNCATE)")
|
|
require.NoError(t, err)
|
|
require.NoError(t, rawDatabase.Close())
|
|
|
|
// Corrupt data pages in the latter portion of the file (skip the first
|
|
// pages which hold the schema).
|
|
data := requireCoreReadBytes(t, databasePath)
|
|
garbage := make([]byte, 4096)
|
|
for i := range garbage {
|
|
garbage[i] = 0xFF
|
|
}
|
|
require.Greater(t, len(data), len(garbage)*2, "database file should be large enough to corrupt")
|
|
offset := len(data) * 3 / 4
|
|
maxOffset := len(data) - (len(garbage) * 2)
|
|
if offset > maxOffset {
|
|
offset = maxOffset
|
|
}
|
|
copy(data[offset:offset+len(garbage)], garbage)
|
|
copy(data[offset+len(garbage):offset+(len(garbage)*2)], garbage)
|
|
requireCoreWriteBytes(t, databasePath, data)
|
|
|
|
// Remove WAL/SHM so the reopened connection reads from the main file.
|
|
_ = testFilesystem().Delete(databasePath + "-wal")
|
|
_ = testFilesystem().Delete(databasePath + "-shm")
|
|
|
|
reopenedStore, err := New(databasePath)
|
|
require.NoError(t, err)
|
|
defer reopenedStore.Close()
|
|
|
|
_, err = reopenedStore.GetAll("g")
|
|
require.Error(t, err, "GetAll should fail on corrupted database pages")
|
|
assert.Contains(t, err.Error(), "store.All: rows")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Render — scan error path
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestCoverage_Render_Bad_ScanError(t *testing.T) {
|
|
// Same NULL-key technique as TestCoverage_GetAll_Bad_ScanError.
|
|
storeInstance, err := New(":memory:")
|
|
require.NoError(t, err)
|
|
defer storeInstance.Close()
|
|
|
|
require.NoError(t, storeInstance.Set("g", "good", "value"))
|
|
|
|
_, err = storeInstance.database.Exec("ALTER TABLE entries RENAME TO entries_backup")
|
|
require.NoError(t, err)
|
|
_, err = storeInstance.database.Exec(`CREATE TABLE entries (
|
|
group_name TEXT,
|
|
entry_key TEXT,
|
|
entry_value TEXT,
|
|
expires_at INTEGER
|
|
)`)
|
|
require.NoError(t, err)
|
|
_, err = storeInstance.database.Exec("INSERT INTO entries SELECT * FROM entries_backup")
|
|
require.NoError(t, err)
|
|
_, err = storeInstance.database.Exec("INSERT INTO entries (group_name, entry_key, entry_value) VALUES ('g', NULL, 'null-key-val')")
|
|
require.NoError(t, err)
|
|
_, err = storeInstance.database.Exec("DROP TABLE entries_backup")
|
|
require.NoError(t, err)
|
|
|
|
_, err = storeInstance.Render("{{ .good }}", "g")
|
|
require.Error(t, err, "Render should fail when a row contains a NULL key")
|
|
assert.Contains(t, err.Error(), "store.All: scan")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Render — rows iteration error path
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestCoverage_Render_Bad_RowsError(t *testing.T) {
|
|
// Same corruption technique as TestCoverage_GetAll_Bad_RowsError.
|
|
databasePath := testPath(t, "corrupt-render.db")
|
|
|
|
storeInstance, err := New(databasePath)
|
|
require.NoError(t, err)
|
|
|
|
const rows = 5000
|
|
for i := range rows {
|
|
require.NoError(t, storeInstance.Set("g",
|
|
core.Sprintf("key-%06d", i),
|
|
core.Sprintf("value-with-padding-%06d-xxxxxxxxxxxxxxxxxxxxxxxx", i)))
|
|
}
|
|
storeInstance.Close()
|
|
|
|
rawDatabase, err := sql.Open("sqlite", databasePath)
|
|
require.NoError(t, err)
|
|
rawDatabase.SetMaxOpenConns(1)
|
|
_, err = rawDatabase.Exec("PRAGMA wal_checkpoint(TRUNCATE)")
|
|
require.NoError(t, err)
|
|
require.NoError(t, rawDatabase.Close())
|
|
|
|
data := requireCoreReadBytes(t, databasePath)
|
|
garbage := make([]byte, 4096)
|
|
for i := range garbage {
|
|
garbage[i] = 0xFF
|
|
}
|
|
require.Greater(t, len(data), len(garbage)*2, "database file should be large enough to corrupt")
|
|
offset := len(data) * 3 / 4
|
|
maxOffset := len(data) - (len(garbage) * 2)
|
|
if offset > maxOffset {
|
|
offset = maxOffset
|
|
}
|
|
copy(data[offset:offset+len(garbage)], garbage)
|
|
copy(data[offset+len(garbage):offset+(len(garbage)*2)], garbage)
|
|
requireCoreWriteBytes(t, databasePath, data)
|
|
|
|
_ = testFilesystem().Delete(databasePath + "-wal")
|
|
_ = testFilesystem().Delete(databasePath + "-shm")
|
|
|
|
reopenedStore, err := New(databasePath)
|
|
require.NoError(t, err)
|
|
defer reopenedStore.Close()
|
|
|
|
_, err = reopenedStore.Render("{{ . }}", "g")
|
|
require.Error(t, err, "Render should fail on corrupted database pages")
|
|
assert.Contains(t, err.Error(), "store.All: rows")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// GroupsSeq — defensive error paths
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestCoverage_GroupsSeq_Bad_ScanError(t *testing.T) {
|
|
// Trigger a scan error by inserting a row with a NULL group name. The
|
|
// production code scans into a plain string, which cannot represent NULL.
|
|
storeInstance, err := New(":memory:")
|
|
require.NoError(t, err)
|
|
defer storeInstance.Close()
|
|
|
|
_, err = storeInstance.database.Exec("ALTER TABLE entries RENAME TO entries_backup")
|
|
require.NoError(t, err)
|
|
_, err = storeInstance.database.Exec(`CREATE TABLE entries (
|
|
group_name TEXT,
|
|
entry_key TEXT,
|
|
entry_value TEXT,
|
|
expires_at INTEGER
|
|
)`)
|
|
require.NoError(t, err)
|
|
_, err = storeInstance.database.Exec("INSERT INTO entries SELECT * FROM entries_backup")
|
|
require.NoError(t, err)
|
|
_, err = storeInstance.database.Exec("INSERT INTO entries (group_name, entry_key, entry_value) VALUES (NULL, 'k', 'v')")
|
|
require.NoError(t, err)
|
|
_, err = storeInstance.database.Exec("DROP TABLE entries_backup")
|
|
require.NoError(t, err)
|
|
|
|
for groupName, iterationErr := range storeInstance.GroupsSeq("") {
|
|
require.Error(t, iterationErr)
|
|
assert.Empty(t, groupName)
|
|
break
|
|
}
|
|
}
|
|
|
|
func TestCoverage_GroupsSeq_Bad_RowsError(t *testing.T) {
|
|
database, _ := openStubSQLiteDatabase(t, stubSQLiteScenario{
|
|
groupRows: [][]driver.Value{
|
|
{"group-a"},
|
|
},
|
|
groupRowsErr: core.E("stubSQLiteScenario", "rows iteration failed", nil),
|
|
groupRowsErrIndex: 0,
|
|
})
|
|
defer database.Close()
|
|
|
|
storeInstance := &Store{
|
|
database: database,
|
|
cancelPurge: func() {},
|
|
}
|
|
|
|
for groupName, iterationErr := range storeInstance.GroupsSeq("") {
|
|
require.Error(t, iterationErr, "GroupsSeq should fail on corrupted database pages")
|
|
assert.Empty(t, groupName)
|
|
break
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ScopedStore bulk helpers — defensive error paths
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestCoverage_ScopedStore_Bad_GroupsClosedStore(t *testing.T) {
|
|
storeInstance, _ := New(":memory:")
|
|
require.NoError(t, storeInstance.Close())
|
|
|
|
scopedStore := NewScoped(storeInstance, "tenant-a")
|
|
require.NotNil(t, scopedStore)
|
|
|
|
var err error
|
|
_, err = scopedStore.Groups("")
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "store.Groups")
|
|
}
|
|
|
|
func TestCoverage_ScopedStore_Bad_GroupsSeqRowsError(t *testing.T) {
|
|
database, _ := openStubSQLiteDatabase(t, stubSQLiteScenario{
|
|
groupRows: [][]driver.Value{
|
|
{"tenant-a:config"},
|
|
},
|
|
groupRowsErr: core.E("stubSQLiteScenario", "rows iteration failed", nil),
|
|
groupRowsErrIndex: 1,
|
|
})
|
|
defer database.Close()
|
|
|
|
scopedStore := &ScopedStore{
|
|
backingStore: &Store{
|
|
database: database,
|
|
cancelPurge: func() {},
|
|
},
|
|
namespace: "tenant-a",
|
|
}
|
|
|
|
var seen []string
|
|
for groupName, iterationErr := range scopedStore.GroupsSeq("") {
|
|
if iterationErr != nil {
|
|
require.Error(t, iterationErr)
|
|
assert.Empty(t, groupName)
|
|
break
|
|
}
|
|
seen = append(seen, groupName)
|
|
}
|
|
assert.Equal(t, []string{"config"}, seen)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Stubbed SQLite driver coverage
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestCoverage_EnsureSchema_Bad_TableExistsQueryError(t *testing.T) {
|
|
database, _ := openStubSQLiteDatabase(t, stubSQLiteScenario{
|
|
tableExistsErr: core.E("stubSQLiteScenario", "sqlite master query failed", nil),
|
|
})
|
|
defer database.Close()
|
|
|
|
err := ensureSchema(database)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "sqlite master query failed")
|
|
}
|
|
|
|
func TestCoverage_EnsureSchema_Good_ExistingEntriesAndLegacyMigration(t *testing.T) {
|
|
database, _ := openStubSQLiteDatabase(t, stubSQLiteScenario{
|
|
tableExistsFound: true,
|
|
tableInfoRows: [][]driver.Value{
|
|
{0, "expires_at", "INTEGER", 0, nil, 0},
|
|
},
|
|
})
|
|
defer database.Close()
|
|
|
|
require.NoError(t, ensureSchema(database))
|
|
}
|
|
|
|
func TestCoverage_EnsureSchema_Bad_ExpiryColumnQueryError(t *testing.T) {
|
|
database, _ := openStubSQLiteDatabase(t, stubSQLiteScenario{
|
|
tableExistsFound: true,
|
|
tableInfoErr: core.E("stubSQLiteScenario", "table_info query failed", nil),
|
|
})
|
|
defer database.Close()
|
|
|
|
err := ensureSchema(database)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "table_info query failed")
|
|
}
|
|
|
|
func TestCoverage_EnsureSchema_Bad_MigrationError(t *testing.T) {
|
|
database, _ := openStubSQLiteDatabase(t, stubSQLiteScenario{
|
|
tableExistsFound: true,
|
|
tableInfoRows: [][]driver.Value{
|
|
{0, "expires_at", "INTEGER", 0, nil, 0},
|
|
},
|
|
insertErr: core.E("stubSQLiteScenario", "insert failed", nil),
|
|
})
|
|
defer database.Close()
|
|
|
|
err := ensureSchema(database)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "insert failed")
|
|
}
|
|
|
|
func TestCoverage_EnsureSchema_Bad_MigrationCommitError(t *testing.T) {
|
|
database, _ := openStubSQLiteDatabase(t, stubSQLiteScenario{
|
|
tableExistsFound: true,
|
|
tableInfoRows: [][]driver.Value{
|
|
{0, "expires_at", "INTEGER", 0, nil, 0},
|
|
},
|
|
commitErr: core.E("stubSQLiteScenario", "commit failed", nil),
|
|
})
|
|
defer database.Close()
|
|
|
|
err := ensureSchema(database)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "commit failed")
|
|
}
|
|
|
|
func TestCoverage_TableHasColumn_Bad_QueryError(t *testing.T) {
|
|
database, _ := openStubSQLiteDatabase(t, stubSQLiteScenario{
|
|
tableInfoErr: core.E("stubSQLiteScenario", "table_info query failed", nil),
|
|
})
|
|
defer database.Close()
|
|
|
|
_, err := tableHasColumn(database, "entries", "expires_at")
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "table_info query failed")
|
|
}
|
|
|
|
func TestCoverage_EnsureExpiryColumn_Good_DuplicateColumn(t *testing.T) {
|
|
database, _ := openStubSQLiteDatabase(t, stubSQLiteScenario{
|
|
tableInfoRows: [][]driver.Value{
|
|
{0, "entry_key", "TEXT", 1, nil, 0},
|
|
},
|
|
alterTableErr: core.E("stubSQLiteScenario", "duplicate column name: expires_at", nil),
|
|
})
|
|
defer database.Close()
|
|
|
|
require.NoError(t, ensureExpiryColumn(database))
|
|
}
|
|
|
|
func TestCoverage_EnsureExpiryColumn_Bad_AlterTableError(t *testing.T) {
|
|
database, _ := openStubSQLiteDatabase(t, stubSQLiteScenario{
|
|
tableInfoRows: [][]driver.Value{
|
|
{0, "entry_key", "TEXT", 1, nil, 0},
|
|
},
|
|
alterTableErr: core.E("stubSQLiteScenario", "permission denied", nil),
|
|
})
|
|
defer database.Close()
|
|
|
|
err := ensureExpiryColumn(database)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "permission denied")
|
|
}
|
|
|
|
func TestCoverage_MigrateLegacyEntriesTable_Bad_InsertError(t *testing.T) {
|
|
database, _ := openStubSQLiteDatabase(t, stubSQLiteScenario{
|
|
tableInfoRows: [][]driver.Value{
|
|
{0, "grp", "TEXT", 1, nil, 0},
|
|
},
|
|
insertErr: core.E("stubSQLiteScenario", "insert failed", nil),
|
|
})
|
|
defer database.Close()
|
|
|
|
err := migrateLegacyEntriesTable(database)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "insert failed")
|
|
}
|
|
|
|
func TestCoverage_MigrateLegacyEntriesTable_Bad_BeginError(t *testing.T) {
|
|
database, _ := openStubSQLiteDatabase(t, stubSQLiteScenario{
|
|
beginErr: core.E("stubSQLiteScenario", "begin failed", nil),
|
|
})
|
|
defer database.Close()
|
|
|
|
err := migrateLegacyEntriesTable(database)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "begin failed")
|
|
}
|
|
|
|
func TestCoverage_MigrateLegacyEntriesTable_Good_CreatesAndMigratesLegacyRows(t *testing.T) {
|
|
database, _ := openStubSQLiteDatabase(t, stubSQLiteScenario{
|
|
tableInfoRows: [][]driver.Value{
|
|
{0, "grp", "TEXT", 1, nil, 0},
|
|
},
|
|
})
|
|
defer database.Close()
|
|
|
|
require.NoError(t, migrateLegacyEntriesTable(database))
|
|
}
|
|
|
|
func TestCoverage_MigrateLegacyEntriesTable_Bad_TableInfoError(t *testing.T) {
|
|
database, _ := openStubSQLiteDatabase(t, stubSQLiteScenario{
|
|
tableInfoErr: core.E("stubSQLiteScenario", "table_info query failed", nil),
|
|
})
|
|
defer database.Close()
|
|
|
|
err := migrateLegacyEntriesTable(database)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "table_info query failed")
|
|
}
|
|
|
|
type stubSQLiteScenario struct {
|
|
tableExistsErr error
|
|
tableExistsFound bool
|
|
tableInfoErr error
|
|
tableInfoRows [][]driver.Value
|
|
groupRows [][]driver.Value
|
|
groupRowsErr error
|
|
groupRowsErrIndex int
|
|
alterTableErr error
|
|
createTableErr error
|
|
insertErr error
|
|
dropTableErr error
|
|
beginErr error
|
|
commitErr error
|
|
rollbackErr error
|
|
}
|
|
|
|
type stubSQLiteDriver struct{}
|
|
|
|
type stubSQLiteConn struct {
|
|
scenario *stubSQLiteScenario
|
|
}
|
|
|
|
type stubSQLiteTx struct {
|
|
scenario *stubSQLiteScenario
|
|
}
|
|
|
|
type stubSQLiteRows struct {
|
|
columns []string
|
|
rows [][]driver.Value
|
|
index int
|
|
nextErr error
|
|
nextErrIndex int
|
|
}
|
|
|
|
type stubSQLiteResult struct{}
|
|
|
|
var (
|
|
stubSQLiteDriverOnce sync.Once
|
|
stubSQLiteScenarios sync.Map
|
|
)
|
|
|
|
const stubSQLiteDriverName = "stub-sqlite"
|
|
|
|
func openStubSQLiteDatabase(t *testing.T, scenario stubSQLiteScenario) (*sql.DB, string) {
|
|
t.Helper()
|
|
|
|
stubSQLiteDriverOnce.Do(func() {
|
|
sql.Register(stubSQLiteDriverName, stubSQLiteDriver{})
|
|
})
|
|
|
|
databasePath := t.Name()
|
|
stubSQLiteScenarios.Store(databasePath, &scenario)
|
|
t.Cleanup(func() {
|
|
stubSQLiteScenarios.Delete(databasePath)
|
|
})
|
|
|
|
database, err := sql.Open(stubSQLiteDriverName, databasePath)
|
|
require.NoError(t, err)
|
|
return database, databasePath
|
|
}
|
|
|
|
func (stubSQLiteDriver) Open(databasePath string) (driver.Conn, error) {
|
|
scenarioValue, ok := stubSQLiteScenarios.Load(databasePath)
|
|
if !ok {
|
|
return nil, core.E("stubSQLiteDriver.Open", "missing scenario", nil)
|
|
}
|
|
return &stubSQLiteConn{scenario: scenarioValue.(*stubSQLiteScenario)}, nil
|
|
}
|
|
|
|
func (conn *stubSQLiteConn) Prepare(query string) (driver.Stmt, error) {
|
|
return nil, core.E("stubSQLiteConn.Prepare", "not implemented", nil)
|
|
}
|
|
|
|
func (conn *stubSQLiteConn) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func (conn *stubSQLiteConn) Begin() (driver.Tx, error) {
|
|
return conn.BeginTx(context.Background(), driver.TxOptions{})
|
|
}
|
|
|
|
func (conn *stubSQLiteConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
|
if conn.scenario.beginErr != nil {
|
|
return nil, conn.scenario.beginErr
|
|
}
|
|
return &stubSQLiteTx{scenario: conn.scenario}, nil
|
|
}
|
|
|
|
func (conn *stubSQLiteConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
|
switch {
|
|
case core.Contains(query, "ALTER TABLE entries ADD COLUMN expires_at INTEGER"):
|
|
if conn.scenario.alterTableErr != nil {
|
|
return nil, conn.scenario.alterTableErr
|
|
}
|
|
case core.Contains(query, "CREATE TABLE IF NOT EXISTS entries"):
|
|
if conn.scenario.createTableErr != nil {
|
|
return nil, conn.scenario.createTableErr
|
|
}
|
|
case core.Contains(query, "INSERT OR IGNORE INTO entries"):
|
|
if conn.scenario.insertErr != nil {
|
|
return nil, conn.scenario.insertErr
|
|
}
|
|
case core.Contains(query, "DROP TABLE kv"):
|
|
if conn.scenario.dropTableErr != nil {
|
|
return nil, conn.scenario.dropTableErr
|
|
}
|
|
}
|
|
return stubSQLiteResult{}, nil
|
|
}
|
|
|
|
func (conn *stubSQLiteConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
|
|
switch {
|
|
case core.Contains(query, "sqlite_master"):
|
|
if conn.scenario.tableExistsErr != nil {
|
|
return nil, conn.scenario.tableExistsErr
|
|
}
|
|
if conn.scenario.tableExistsFound {
|
|
return &stubSQLiteRows{
|
|
columns: []string{"name"},
|
|
rows: [][]driver.Value{{"entries"}},
|
|
}, nil
|
|
}
|
|
return &stubSQLiteRows{columns: []string{"name"}}, nil
|
|
case core.Contains(query, "SELECT DISTINCT "+entryGroupColumn):
|
|
return &stubSQLiteRows{
|
|
columns: []string{entryGroupColumn},
|
|
rows: conn.scenario.groupRows,
|
|
nextErr: conn.scenario.groupRowsErr,
|
|
nextErrIndex: conn.scenario.groupRowsErrIndex,
|
|
}, nil
|
|
case core.HasPrefix(query, "PRAGMA table_info("):
|
|
if conn.scenario.tableInfoErr != nil {
|
|
return nil, conn.scenario.tableInfoErr
|
|
}
|
|
return &stubSQLiteRows{
|
|
columns: []string{"cid", "name", "type", "notnull", "dflt_value", "pk"},
|
|
rows: conn.scenario.tableInfoRows,
|
|
}, nil
|
|
}
|
|
return nil, core.E("stubSQLiteConn.QueryContext", "unexpected query", nil)
|
|
}
|
|
|
|
func (tx *stubSQLiteTx) Commit() error {
|
|
if tx.scenario.commitErr != nil {
|
|
return tx.scenario.commitErr
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (tx *stubSQLiteTx) Rollback() error {
|
|
if tx.scenario.rollbackErr != nil {
|
|
return tx.scenario.rollbackErr
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (rows *stubSQLiteRows) Columns() []string {
|
|
return rows.columns
|
|
}
|
|
|
|
func (rows *stubSQLiteRows) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func (rows *stubSQLiteRows) Next(dest []driver.Value) error {
|
|
if rows.nextErr != nil && rows.index == rows.nextErrIndex {
|
|
rows.index++
|
|
return rows.nextErr
|
|
}
|
|
if rows.index >= len(rows.rows) {
|
|
return io.EOF
|
|
}
|
|
row := rows.rows[rows.index]
|
|
rows.index++
|
|
for i := range dest {
|
|
dest[i] = nil
|
|
}
|
|
copy(dest, row)
|
|
return nil
|
|
}
|
|
|
|
func (stubSQLiteResult) LastInsertId() (int64, error) {
|
|
return 0, nil
|
|
}
|
|
|
|
func (stubSQLiteResult) RowsAffected() (int64, error) {
|
|
return 0, nil
|
|
}
|