You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1242 lines
36 KiB
Go
1242 lines
36 KiB
Go
package db
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rqlite/rqlite/v8/command/encoding"
|
|
command "github.com/rqlite/rqlite/v8/command/proto"
|
|
"github.com/rqlite/rqlite/v8/random"
|
|
)
|
|
|
|
// Test_OpenNonExistentDatabase tests that opening a non-existent database
|
|
// works OK. It should.
|
|
func Test_OpenNonExistentDatabase(t *testing.T) {
|
|
path := mustTempPath()
|
|
defer os.Remove(path)
|
|
_, err := Open(path, false, false)
|
|
if err != nil {
|
|
t.Fatalf("error opening non-existent database: %s", err.Error())
|
|
}
|
|
// Confirm a file was created.
|
|
if !fileExists(path) {
|
|
t.Fatalf("database file not created at %s", path)
|
|
}
|
|
}
|
|
|
|
func Test_WALRemovedOnClose(t *testing.T) {
|
|
path := mustTempPath()
|
|
defer os.Remove(path)
|
|
db, err := Open(path, false, true)
|
|
if err != nil {
|
|
t.Fatalf("error opening non-existent database")
|
|
}
|
|
defer db.Close()
|
|
if !db.WALEnabled() {
|
|
t.Fatalf("WAL mode not enabled")
|
|
}
|
|
|
|
_, err = db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
walPath := db.WALPath()
|
|
if !fileExists(walPath) {
|
|
t.Fatalf("WAL file does not exist after creating a table")
|
|
}
|
|
if err := db.Close(); err != nil {
|
|
t.Fatalf("error closing database: %s", err.Error())
|
|
}
|
|
|
|
if fileExists(db.WALPath()) {
|
|
t.Fatalf("WAL file not removed after closing the database")
|
|
}
|
|
}
|
|
|
|
func Test_RemoveFiles(t *testing.T) {
|
|
d := t.TempDir()
|
|
mustCreateClosedFile(fmt.Sprintf("%s/foo", d))
|
|
mustCreateClosedFile(fmt.Sprintf("%s/foo-wal", d))
|
|
|
|
if err := RemoveFiles(fmt.Sprintf("%s/foo", d)); err != nil {
|
|
t.Fatalf("failed to remove files: %s", err.Error())
|
|
}
|
|
|
|
files, err := os.ReadDir(d)
|
|
if err != nil {
|
|
t.Fatalf("failed to read directory: %s", err.Error())
|
|
}
|
|
if len(files) != 0 {
|
|
t.Fatalf("expected directory to be empty, but wasn't")
|
|
}
|
|
}
|
|
|
|
func Test_DBPaths(t *testing.T) {
|
|
dbWAL, pathWAL := mustCreateOnDiskDatabaseWAL()
|
|
defer dbWAL.Close()
|
|
defer os.Remove(pathWAL)
|
|
if exp, got := pathWAL, dbWAL.Path(); exp != got {
|
|
t.Fatalf("expected path %s, got %s", exp, got)
|
|
}
|
|
if exp, got := pathWAL+"-wal", dbWAL.WALPath(); exp != got {
|
|
t.Fatalf("expected WAL path %s, got %s", exp, got)
|
|
}
|
|
if p1, p2 := WALPath(pathWAL), dbWAL.WALPath(); p1 != p2 {
|
|
t.Fatalf("WAL paths are not equal (%s != %s)", p1, p2)
|
|
}
|
|
|
|
db, path := mustCreateOnDiskDatabase()
|
|
defer db.Close()
|
|
defer os.Remove(path)
|
|
if exp, got := path, db.Path(); exp != got {
|
|
t.Fatalf("expected path %s, got %s", exp, got)
|
|
}
|
|
if exp, got := "", db.WALPath(); exp != got {
|
|
t.Fatalf("expected WAL path %s, got %s", exp, got)
|
|
}
|
|
}
|
|
|
|
// Test_TableCreation tests basic operation of an database
|
|
func Test_TableCreation(t *testing.T) {
|
|
db, path := mustCreateOnDiskDatabaseWAL()
|
|
defer db.Close()
|
|
defer os.Remove(path)
|
|
|
|
r, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{}]`, asJSON(r); exp != got {
|
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
|
}
|
|
|
|
testQ := func() {
|
|
t.Helper()
|
|
q, err := db.QueryStringStmt("SELECT * FROM foo")
|
|
if err != nil {
|
|
t.Fatalf("failed to query empty table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]`, asJSON(q); exp != got {
|
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
|
}
|
|
}
|
|
|
|
_, err = db.ExecuteStringStmt(`INSERT INTO foo(name) VALUES("fiona")`)
|
|
if err != nil {
|
|
t.Fatalf("error executing insertion into table: %s", err.Error())
|
|
}
|
|
testQ()
|
|
|
|
// Confirm checkpoint works without error.
|
|
if err := db.Checkpoint(CheckpointRestart); err != nil {
|
|
t.Fatalf("failed to checkpoint database: %s", err.Error())
|
|
}
|
|
testQ()
|
|
}
|
|
|
|
func Test_DBSums(t *testing.T) {
|
|
db, path := mustCreateOnDiskDatabaseWAL()
|
|
defer db.Close()
|
|
defer os.Remove(path)
|
|
|
|
getSums := func() (string, string) {
|
|
sumDB, err := db.DBSum()
|
|
if err != nil {
|
|
t.Fatalf("failed to get DB checksum: %s", err.Error())
|
|
}
|
|
sumWAL, err := db.WALSum()
|
|
if err != nil {
|
|
t.Fatalf("failed to get WAL checksum: %s", err.Error())
|
|
}
|
|
return sumDB, sumWAL
|
|
}
|
|
|
|
r, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{}]`, asJSON(r); exp != got {
|
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
|
}
|
|
|
|
sumDBPre, sumWALPre := getSums()
|
|
_, err = db.ExecuteStringStmt(`INSERT INTO foo(name) VALUES("fiona")`)
|
|
if err != nil {
|
|
t.Fatalf("error executing insertion into table: %s", err.Error())
|
|
}
|
|
sumDBPost, sumWALPost := getSums()
|
|
if sumDBPost != sumDBPre {
|
|
t.Fatalf("DB sum changed after insertion")
|
|
}
|
|
if sumWALPost == sumWALPre {
|
|
t.Fatalf("WAL sum did not change after insertion")
|
|
}
|
|
|
|
if err := db.Checkpoint(CheckpointRestart); err != nil {
|
|
t.Fatalf("failed to checkpoint database: %s", err.Error())
|
|
}
|
|
|
|
sumDBPostChk, _ := getSums()
|
|
if sumDBPostChk == sumDBPost {
|
|
t.Fatalf("DB sum did not change after checkpoint")
|
|
}
|
|
}
|
|
|
|
func Test_DBLastModified(t *testing.T) {
|
|
path := mustTempFile()
|
|
defer os.Remove(path)
|
|
db, err := Open(path, false, true)
|
|
if err != nil {
|
|
t.Fatalf("failed to open database in WAL mode: %s", err.Error())
|
|
}
|
|
defer db.Close()
|
|
|
|
lm, err := db.LastModified()
|
|
if err != nil {
|
|
t.Fatalf("failed to get last modified time: %s", err.Error())
|
|
}
|
|
if lm.IsZero() {
|
|
t.Fatalf("last modified time is zero")
|
|
}
|
|
lmDB, err := db.DBLastModified()
|
|
if err != nil {
|
|
t.Fatalf("failed to get last modified time: %s", err.Error())
|
|
}
|
|
if lmDB.IsZero() {
|
|
t.Fatalf("last modified time is zero")
|
|
}
|
|
|
|
// Write some data, check times are later.
|
|
_, err = db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
|
|
lm2, err := db.LastModified()
|
|
if err != nil {
|
|
t.Fatalf("failed to get last modified time: %s", err.Error())
|
|
}
|
|
if lm2.Before(lm) {
|
|
t.Fatalf("last modified time not updated")
|
|
}
|
|
lmDB2, err := db.DBLastModified()
|
|
if err != nil {
|
|
t.Fatalf("failed to get last modified time: %s", err.Error())
|
|
}
|
|
if !lmDB2.Equal(lmDB) {
|
|
t.Fatalf("last modified time changed for DB even though only WAL should have changed")
|
|
}
|
|
|
|
// Checkpoint, and check time is later. On some platforms the time resolution isn't that
|
|
// high, so we sleep so the test won't suffer a false failure.
|
|
time.Sleep(1 * time.Second)
|
|
if err := db.Checkpoint(CheckpointRestart); err != nil {
|
|
t.Fatalf("failed to checkpoint database: %s", err.Error())
|
|
}
|
|
lm3, err := db.LastModified()
|
|
if err != nil {
|
|
t.Fatalf("failed to get last modified time: %s", err.Error())
|
|
}
|
|
if lm3.Before(lm2) {
|
|
t.Fatalf("last modified time not updated after checkpoint")
|
|
}
|
|
lmDB3, err := db.DBLastModified()
|
|
if err != nil {
|
|
t.Fatalf("failed to get last modified time: %s", err.Error())
|
|
}
|
|
if !lmDB3.After(lmDB2) {
|
|
t.Fatalf("last modified time not updated for DB after checkpoint")
|
|
}
|
|
|
|
// Call again, without changes, check time is same.
|
|
lm4, err := db.LastModified()
|
|
if err != nil {
|
|
t.Fatalf("failed to get last modified time: %s", err.Error())
|
|
}
|
|
if !lm4.Equal(lm3) {
|
|
t.Fatalf("last modified time updated without changes")
|
|
}
|
|
}
|
|
|
|
func Test_DBVacuum(t *testing.T) {
|
|
db, path := mustCreateOnDiskDatabaseWAL()
|
|
defer db.Close()
|
|
defer os.Remove(path)
|
|
|
|
r, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{}]`, asJSON(r); exp != got {
|
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
|
}
|
|
|
|
testQ := func() {
|
|
t.Helper()
|
|
q, err := db.QueryStringStmt("SELECT * FROM foo")
|
|
if err != nil {
|
|
t.Fatalf("failed to query empty table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]`, asJSON(q); exp != got {
|
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
|
}
|
|
}
|
|
|
|
_, err = db.ExecuteStringStmt(`INSERT INTO foo(name) VALUES("fiona")`)
|
|
if err != nil {
|
|
t.Fatalf("error executing insertion into table: %s", err.Error())
|
|
}
|
|
|
|
// Confirm VACUUM works without error and that only the WAL file is altered.
|
|
sumDBPre, err := db.DBSum()
|
|
if err != nil {
|
|
t.Fatalf("failed to get DB checksum: %s", err.Error())
|
|
}
|
|
sumWALPre, err := db.WALSum()
|
|
if err != nil {
|
|
t.Fatalf("failed to get WAL checksum: %s", err.Error())
|
|
}
|
|
|
|
if err := db.Vacuum(); err != nil {
|
|
t.Fatalf("failed to vacuum database: %s", err.Error())
|
|
}
|
|
testQ()
|
|
|
|
sumDBPost, err := db.DBSum()
|
|
if err != nil {
|
|
t.Fatalf("failed to get DB checksum: %s", err.Error())
|
|
}
|
|
sumWALPost, err := db.WALSum()
|
|
if err != nil {
|
|
t.Fatalf("failed to get WAL checksum: %s", err.Error())
|
|
}
|
|
|
|
if sumDBPost != sumDBPre {
|
|
t.Fatalf("DB sum changed after VACUUM")
|
|
}
|
|
if sumWALPost == sumWALPre {
|
|
t.Fatalf("WAL sum did not change after VACUUM")
|
|
}
|
|
}
|
|
|
|
func Test_DBVacuumInto(t *testing.T) {
|
|
db, path := mustCreateOnDiskDatabaseWAL()
|
|
defer db.Close()
|
|
defer os.Remove(path)
|
|
|
|
r, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{}]`, asJSON(r); exp != got {
|
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
|
}
|
|
for i := 0; i < 1000; i++ {
|
|
_, err = db.ExecuteStringStmt(`INSERT INTO foo(name) VALUES("fiona")`)
|
|
if err != nil {
|
|
t.Fatalf("error executing insertion into table: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
testQ := func(d *DB) {
|
|
t.Helper()
|
|
q, err := d.QueryStringStmt("SELECT COUNT(*) FROM foo")
|
|
if err != nil {
|
|
t.Fatalf("failed to query empty table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{"columns":["COUNT(*)"],"types":["integer"],"values":[[1000]]}]`, asJSON(q); exp != got {
|
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
|
}
|
|
}
|
|
testQ(db)
|
|
|
|
// VACUUM INTO an empty file, open the database, and check it's correct.
|
|
tmpPath := mustTempFile()
|
|
defer os.Remove(tmpPath)
|
|
if err := db.VacuumInto(tmpPath); err != nil {
|
|
t.Fatalf("failed to vacuum database: %s", err.Error())
|
|
}
|
|
vDB, err := Open(tmpPath, false, false)
|
|
if err != nil {
|
|
t.Fatalf("failed to open database: %s", err.Error())
|
|
}
|
|
defer vDB.Close()
|
|
testQ(vDB)
|
|
|
|
// VACUUM INTO an non-existing file, should still work.
|
|
tmpPath2 := mustTempPath()
|
|
defer os.Remove(tmpPath2)
|
|
if err := db.VacuumInto(tmpPath2); err != nil {
|
|
t.Fatalf("failed to vacuum database: %s", err.Error())
|
|
}
|
|
vDB2, err := Open(tmpPath2, false, false)
|
|
if err != nil {
|
|
t.Fatalf("failed to open database: %s", err.Error())
|
|
}
|
|
defer vDB2.Close()
|
|
testQ(vDB2)
|
|
|
|
// VACUUM into a file which is an existing SQLIte database. Should fail.
|
|
existDB, existPath := mustCreateOnDiskDatabaseWAL()
|
|
defer db.Close()
|
|
defer os.Remove(existPath)
|
|
_, err = existDB.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
if err := existDB.VacuumInto(existPath); err == nil {
|
|
t.Fatalf("expected error vacuuming into existing database file")
|
|
}
|
|
_, err = Open(existPath, false, false)
|
|
if err == nil {
|
|
t.Fatalf("expected error opening database: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
// Test_TableCreationFK ensures foreign key constraints work
|
|
func Test_TableCreationFK(t *testing.T) {
|
|
createTableFoo := "CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"
|
|
createTableBar := "CREATE TABLE bar (fooid INTEGER NOT NULL PRIMARY KEY, FOREIGN KEY(fooid) REFERENCES foo(id))"
|
|
insertIntoBar := "INSERT INTO bar(fooid) VALUES(1)"
|
|
|
|
db, path := mustCreateOnDiskDatabaseWAL()
|
|
defer db.Close()
|
|
defer os.Remove(path)
|
|
|
|
r, err := db.ExecuteStringStmt(createTableFoo)
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{}]`, asJSON(r); exp != got {
|
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
|
}
|
|
|
|
r, err = db.ExecuteStringStmt(createTableBar)
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{}]`, asJSON(r); exp != got {
|
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
|
}
|
|
|
|
r, err = db.ExecuteStringStmt(insertIntoBar)
|
|
if err != nil {
|
|
t.Fatalf("failed to insert record: %s", err.Error())
|
|
}
|
|
if exp, got := `[{"last_insert_id":1,"rows_affected":1}]`, asJSON(r); exp != got {
|
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
|
}
|
|
|
|
// Now, do same testing with FK constraints enabled.
|
|
dbFK, path := mustCreateOnDiskDatabaseWALFK()
|
|
defer dbFK.Close()
|
|
defer os.Remove(path)
|
|
|
|
if !dbFK.FKEnabled() {
|
|
t.Fatal("FK constraints not marked as enabled")
|
|
}
|
|
|
|
r, err = dbFK.ExecuteStringStmt(createTableFoo)
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{}]`, asJSON(r); exp != got {
|
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
|
}
|
|
|
|
r, err = dbFK.ExecuteStringStmt(createTableBar)
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{}]`, asJSON(r); exp != got {
|
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
|
}
|
|
|
|
r, err = dbFK.ExecuteStringStmt(insertIntoBar)
|
|
if err != nil {
|
|
t.Fatalf("failed to insert record: %s", err.Error())
|
|
}
|
|
if exp, got := `[{"error":"FOREIGN KEY constraint failed"}]`, asJSON(r); exp != got {
|
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
|
}
|
|
}
|
|
|
|
func Test_ConcurrentQueries(t *testing.T) {
|
|
db, path := mustCreateOnDiskDatabaseWAL()
|
|
defer db.Close()
|
|
defer os.Remove(path)
|
|
|
|
r, err := db.ExecuteStringStmt(`CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`)
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{}]`, asJSON(r); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
|
|
for i := 0; i < 5000; i++ {
|
|
r, err = db.ExecuteStringStmt(`INSERT INTO foo(name) VALUES("fiona")`)
|
|
if err != nil {
|
|
t.Fatalf("failed to insert record: %s", err.Error())
|
|
}
|
|
if exp, got := fmt.Sprintf(`[{"last_insert_id":%d,"rows_affected":1}]`, i+1), asJSON(r); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < 32; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
ro, err := db.QueryStringStmt(`SELECT COUNT(*) FROM foo`)
|
|
if err != nil {
|
|
t.Logf("failed to query table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{"columns":["COUNT(*)"],"types":["integer"],"values":[[5000]]}]`, asJSON(ro); exp != got {
|
|
t.Logf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
func Test_SimpleTransaction(t *testing.T) {
|
|
db, path := mustCreateOnDiskDatabaseWAL()
|
|
defer db.Close()
|
|
defer os.Remove(path)
|
|
|
|
_, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
|
|
req := &command.Request{
|
|
Transaction: true,
|
|
Statements: []*command.Statement{
|
|
{
|
|
Sql: `INSERT INTO foo(id, name) VALUES(1, "fiona")`,
|
|
},
|
|
{
|
|
Sql: `INSERT INTO foo(id, name) VALUES(2, "fiona")`,
|
|
},
|
|
{
|
|
Sql: `INSERT INTO foo(id, name) VALUES(3, "fiona")`,
|
|
},
|
|
{
|
|
Sql: `INSERT INTO foo(id, name) VALUES(4, "fiona")`,
|
|
},
|
|
},
|
|
}
|
|
r, err := db.Execute(req, false)
|
|
if err != nil {
|
|
t.Fatalf("failed to insert records: %s", err.Error())
|
|
}
|
|
if exp, got := `[{"last_insert_id":1,"rows_affected":1},{"last_insert_id":2,"rows_affected":1},{"last_insert_id":3,"rows_affected":1},{"last_insert_id":4,"rows_affected":1}]`, asJSON(r); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
ro, err := db.QueryStringStmt(`SELECT * FROM foo`)
|
|
if err != nil {
|
|
t.Fatalf("failed to query table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"],[2,"fiona"],[3,"fiona"],[4,"fiona"]]}]`, asJSON(ro); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
}
|
|
|
|
func Test_PartialFailTransaction(t *testing.T) {
|
|
db, path := mustCreateOnDiskDatabaseWAL()
|
|
defer db.Close()
|
|
defer os.Remove(path)
|
|
|
|
_, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
|
|
req := &command.Request{
|
|
Transaction: true,
|
|
Statements: []*command.Statement{
|
|
{
|
|
Sql: `INSERT INTO foo(id, name) VALUES(1, "fiona")`,
|
|
},
|
|
{
|
|
Sql: `INSERT INTO foo(id, name) VALUES(2, "fiona")`,
|
|
},
|
|
{
|
|
Sql: `INSERT INTO foo(id, name) VALUES(1, "fiona")`,
|
|
},
|
|
{
|
|
Sql: `INSERT INTO foo(id, name) VALUES(4, "fiona")`,
|
|
},
|
|
},
|
|
}
|
|
r, err := db.Execute(req, false)
|
|
if err != nil {
|
|
t.Fatalf("failed to insert records: %s", err.Error())
|
|
}
|
|
if exp, got := `[{"last_insert_id":1,"rows_affected":1},{"last_insert_id":2,"rows_affected":1},{"error":"UNIQUE constraint failed: foo.id"}]`, asJSON(r); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
ro, err := db.QueryStringStmt(`SELECT * FROM foo`)
|
|
if err != nil {
|
|
t.Fatalf("failed to query table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{"columns":["id","name"],"types":["integer","text"]}]`, asJSON(ro); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
}
|
|
|
|
func Test_CheckIntegrityOnDisk(t *testing.T) {
|
|
path := mustTempFile()
|
|
defer os.Remove(path)
|
|
|
|
dsn := fmt.Sprintf("file:%s", path)
|
|
db, err := sql.Open("sqlite3", dsn)
|
|
if err != nil {
|
|
t.Fatalf("failed to create SQLite database: %s", err.Error())
|
|
}
|
|
_, err = db.Exec("CREATE TABLE foo (name TEXT)")
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
if err := db.Close(); err != nil {
|
|
t.Fatalf("failed to close database: %s", err.Error())
|
|
}
|
|
|
|
for _, check := range []bool{true, false} {
|
|
ok, err := CheckIntegrity(path, check)
|
|
if err != nil {
|
|
t.Fatalf("failed to check integrity of database (full=%t): %s", check, err.Error())
|
|
}
|
|
if !ok {
|
|
t.Fatalf("database failed integrity check (full=%t)", check)
|
|
}
|
|
}
|
|
|
|
sz := int(mustFileSize(path))
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("failed to read SQLite file: %s", err.Error())
|
|
}
|
|
|
|
// Chop the file in half, integrity check shouldn't even be error-free
|
|
if err := os.WriteFile(path, b[:len(b)-sz/2], 0644); err != nil {
|
|
t.Fatalf("failed to write SQLite file: %s", err.Error())
|
|
}
|
|
for _, check := range []bool{true, false} {
|
|
_, err := CheckIntegrity(path, check)
|
|
if err == nil {
|
|
t.Fatalf("succeeded checking integrity of database (full=%t): %s", check, err.Error())
|
|
}
|
|
}
|
|
// Unable to create a data set that actually fails integrity check.
|
|
}
|
|
|
|
// Test_WALDatabaseCreatedOK tests that a WAL file is created, and that
|
|
// a checkpoint succeeds
|
|
func Test_WALDatabaseCreatedOK(t *testing.T) {
|
|
path := mustTempFile()
|
|
defer os.Remove(path)
|
|
|
|
db, err := Open(path, false, true)
|
|
if err != nil {
|
|
t.Fatalf("failed to open database in WAL mode: %s", err.Error())
|
|
}
|
|
defer db.Close()
|
|
|
|
if !db.WALEnabled() {
|
|
t.Fatalf("WAL mode not enabled")
|
|
}
|
|
|
|
if _, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"); err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
|
|
if !IsWALModeEnabledSQLiteFile(path) {
|
|
t.Fatalf("SQLite file not marked as WAL")
|
|
}
|
|
|
|
walPath := path + "-wal"
|
|
if _, err := os.Stat(walPath); os.IsNotExist(err) {
|
|
t.Fatalf("WAL file does not exist")
|
|
}
|
|
if mustFileSize(walPath) == 0 {
|
|
t.Fatalf("WAL file exists but is empty")
|
|
}
|
|
|
|
if err := db.Checkpoint(CheckpointTruncate); err != nil {
|
|
t.Fatalf("failed to checkpoint database in WAL mode: %s", err.Error())
|
|
}
|
|
if mustFileSize(walPath) != 0 {
|
|
t.Fatalf("WAL file exists but is non-empty")
|
|
}
|
|
// Checkpoint a second time, to ensure it's idempotent.
|
|
if err := db.Checkpoint(CheckpointTruncate); err != nil {
|
|
t.Fatalf("failed to checkpoint database in WAL mode: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
// Test_WALDatabaseCreatedOKFromDELETE tests that a WAL database is created properly,
|
|
// even when supplied with a DELETE-mode database.
|
|
func Test_WALDatabaseCreatedOKFromDELETE(t *testing.T) {
|
|
deletePath := mustTempFile()
|
|
defer os.Remove(deletePath)
|
|
deleteDB, err := Open(deletePath, false, false)
|
|
if err != nil {
|
|
t.Fatalf("failed to open database in WAL mode: %s", err.Error())
|
|
}
|
|
defer deleteDB.Close()
|
|
if _, err := deleteDB.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"); err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
_, err = deleteDB.ExecuteStringStmt(`INSERT INTO foo(name) VALUES("fiona")`)
|
|
if err != nil {
|
|
t.Fatalf("error executing insertion into table: %s", err.Error())
|
|
}
|
|
|
|
walDB, err := Open(deletePath, false, true)
|
|
if err != nil {
|
|
t.Fatalf("failed to open database in WAL mode: %s", err.Error())
|
|
}
|
|
defer walDB.Close()
|
|
if !IsWALModeEnabledSQLiteFile(deletePath) {
|
|
t.Fatalf("SQLite file not marked as WAL")
|
|
}
|
|
rows, err := walDB.QueryStringStmt("SELECT * FROM foo")
|
|
if err != nil {
|
|
t.Fatalf("failed to query WAL table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]`, asJSON(rows); exp != got {
|
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
|
}
|
|
}
|
|
|
|
// Test_DELETEDatabaseCreatedOKFromWAL tests that a DELETE database is created properly,
|
|
// even when supplied with a WAL-mode database.
|
|
func Test_DELETEDatabaseCreatedOKFromWAL(t *testing.T) {
|
|
walPath := mustTempFile()
|
|
defer os.Remove(walPath)
|
|
walDB, err := Open(walPath, false, true)
|
|
if err != nil {
|
|
t.Fatalf("failed to open database in WAL mode: %s", err.Error())
|
|
}
|
|
defer walDB.Close()
|
|
if _, err := walDB.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"); err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
_, err = walDB.ExecuteStringStmt(`INSERT INTO foo(name) VALUES("fiona")`)
|
|
if err != nil {
|
|
t.Fatalf("error executing insertion into table: %s", err.Error())
|
|
}
|
|
if err := walDB.Close(); err != nil {
|
|
// Closing the WAL database is required if it's to be opened in DELETE mode.
|
|
t.Fatalf("failed to close database: %s", err.Error())
|
|
}
|
|
|
|
deleteDB, err2 := Open(walPath, false, false)
|
|
if err2 != nil {
|
|
t.Fatalf("failed to open database in DELETE mode: %s", err2.Error())
|
|
}
|
|
defer deleteDB.Close()
|
|
if !IsDELETEModeEnabledSQLiteFile(walPath) {
|
|
t.Fatalf("SQLite file not marked as WAL")
|
|
}
|
|
rows, err := deleteDB.QueryStringStmt("SELECT * FROM foo")
|
|
if err != nil {
|
|
t.Fatalf("failed to query WAL table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]`, asJSON(rows); exp != got {
|
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
|
}
|
|
}
|
|
|
|
func Test_WALDisableCheckpointing(t *testing.T) {
|
|
path := mustTempFile()
|
|
defer os.Remove(path)
|
|
|
|
db, err := Open(path, false, true)
|
|
if err != nil {
|
|
t.Fatalf("failed to open database in WAL mode: %s", err.Error())
|
|
}
|
|
defer db.Close()
|
|
if !db.WALEnabled() {
|
|
t.Fatalf("WAL mode not enabled")
|
|
}
|
|
|
|
// Test that databases open with checkpoint disabled by default.
|
|
// This is critical.
|
|
n, err := db.GetCheckpointing()
|
|
if err != nil {
|
|
t.Fatalf("failed to get checkpoint value: %s", err.Error())
|
|
}
|
|
if exp, got := 0, n; exp != got {
|
|
t.Fatalf("unexpected checkpoint value, expected %d, got %d", exp, got)
|
|
}
|
|
|
|
if err := db.EnableCheckpointing(); err != nil {
|
|
t.Fatalf("failed to disable checkpointing: %s", err.Error())
|
|
}
|
|
n, err = db.GetCheckpointing()
|
|
if err != nil {
|
|
t.Fatalf("failed to get checkpoint value: %s", err.Error())
|
|
}
|
|
if exp, got := 1000, n; exp != got {
|
|
t.Fatalf("unexpected checkpoint value, expected %d, got %d", exp, got)
|
|
}
|
|
}
|
|
|
|
func test_FileCreationOnDisk(t *testing.T, db *DB) {
|
|
defer db.Close()
|
|
if db.FKEnabled() {
|
|
t.Fatal("FK constraints marked as enabled")
|
|
}
|
|
|
|
// Confirm checkpoint works on all types of on-disk databases. Worst case, this
|
|
// should be ignored.
|
|
if err := db.Checkpoint(CheckpointRestart); err != nil {
|
|
t.Fatalf("failed to checkpoint database in DELETE mode: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
// test_ConnectionIsolationOnDisk test that ISOLATION behavior of on-disk databases doesn't
|
|
// change unexpectedly.
|
|
func test_ConnectionIsolationOnDisk(t *testing.T, db *DB) {
|
|
r, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{}]`, asJSON(r); exp != got {
|
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
|
}
|
|
|
|
r, err = db.ExecuteStringStmt("BEGIN")
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{}]`, asJSON(r); exp != got {
|
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
|
}
|
|
|
|
r, err = db.ExecuteStringStmt(`INSERT INTO foo(name) VALUES("fiona")`)
|
|
if err != nil {
|
|
t.Fatalf("error executing insertion into table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{"last_insert_id":1,"rows_affected":1}]`, asJSON(r); exp != got {
|
|
t.Fatalf("unexpected results for execute, expected %s, got %s", exp, got)
|
|
}
|
|
|
|
q, err := db.QueryStringStmt("SELECT * FROM foo")
|
|
if err != nil {
|
|
t.Fatalf("failed to query empty table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{"columns":["id","name"],"types":["integer","text"]}]`, asJSON(q); exp != got {
|
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
|
}
|
|
|
|
_, err = db.ExecuteStringStmt("COMMIT")
|
|
if err != nil {
|
|
t.Fatalf("error executing insertion into table: %s", err.Error())
|
|
}
|
|
|
|
q, err = db.QueryStringStmt("SELECT * FROM foo")
|
|
if err != nil {
|
|
t.Fatalf("failed to query empty table: %s", err.Error())
|
|
}
|
|
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]`, asJSON(q); exp != got {
|
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
|
}
|
|
}
|
|
|
|
func Test_DatabaseCommonOnDiskOperations(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
testFunc func(*testing.T, *DB)
|
|
}{
|
|
{"FileCreationOnDisk", test_FileCreationOnDisk},
|
|
{"ConnectionIsolationOnDisk", test_ConnectionIsolationOnDisk},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
db, path := mustCreateOnDiskDatabase()
|
|
defer db.Close()
|
|
defer os.Remove(path)
|
|
t.Run(tc.name+":disk", func(t *testing.T) {
|
|
tc.testFunc(t, db)
|
|
})
|
|
|
|
db, path = mustCreateOnDiskDatabaseWAL()
|
|
defer db.Close()
|
|
defer os.Remove(path)
|
|
t.Run(tc.name+":wal", func(t *testing.T) {
|
|
tc.testFunc(t, db)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test_ParallelOperationsInMemory runs multiple accesses concurrently, ensuring
|
|
// that correct results are returned in every goroutine. It's not 100% that this
|
|
// test would bring out a bug, but it's almost 100%.
|
|
//
|
|
// See https://github.com/mattn/go-sqlite3/issues/959#issuecomment-890283264
|
|
func Test_ParallelOperationsInMemory(t *testing.T) {
|
|
db, path := mustCreateOnDiskDatabaseWAL()
|
|
defer db.Close()
|
|
defer os.Remove(path)
|
|
|
|
if _, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"); err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
|
|
if _, err := db.ExecuteStringStmt("CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"); err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
|
|
if _, err := db.ExecuteStringStmt("CREATE TABLE qux (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"); err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
|
|
// Confirm schema is as expected, when checked from same goroutine.
|
|
if rows, err := db.QueryStringStmt(`SELECT sql FROM sqlite_master`); err != nil {
|
|
t.Fatalf("failed to query for schema after creation: %s", err.Error())
|
|
} else {
|
|
if exp, got := `[{"columns":["sql"],"types":["text"],"values":[["CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE qux (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"]]}]`, asJSON(rows); exp != got {
|
|
t.Fatalf("schema not as expected during after creation, exp %s, got %s", exp, got)
|
|
}
|
|
}
|
|
|
|
var exWg sync.WaitGroup
|
|
exWg.Add(3)
|
|
|
|
foo := make(chan time.Time)
|
|
bar := make(chan time.Time)
|
|
qux := make(chan time.Time)
|
|
done := make(chan bool)
|
|
|
|
ticker := time.NewTicker(1 * time.Millisecond)
|
|
go func() {
|
|
for {
|
|
select {
|
|
case t := <-ticker.C:
|
|
foo <- t
|
|
bar <- t
|
|
qux <- t
|
|
case <-done:
|
|
close(foo)
|
|
close(bar)
|
|
close(qux)
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
defer exWg.Done()
|
|
for range foo {
|
|
if _, err := db.ExecuteStringStmt(`INSERT INTO foo(id, name) VALUES(1, "fiona")`); err != nil {
|
|
t.Logf("failed to insert records into foo: %s", err.Error())
|
|
}
|
|
}
|
|
}()
|
|
go func() {
|
|
defer exWg.Done()
|
|
for range bar {
|
|
if _, err := db.ExecuteStringStmt(`INSERT INTO bar(id, name) VALUES(1, "fiona")`); err != nil {
|
|
t.Logf("failed to insert records into bar: %s", err.Error())
|
|
}
|
|
}
|
|
}()
|
|
go func() {
|
|
defer exWg.Done()
|
|
for range qux {
|
|
if _, err := db.ExecuteStringStmt(`INSERT INTO qux(id, name) VALUES(1, "fiona")`); err != nil {
|
|
t.Logf("failed to insert records into qux: %s", err.Error())
|
|
}
|
|
}
|
|
}()
|
|
|
|
var qWg sync.WaitGroup
|
|
qWg.Add(3)
|
|
for i := 0; i < 3; i++ {
|
|
go func(j int) {
|
|
defer qWg.Done()
|
|
var n int
|
|
for {
|
|
if rows, err := db.QueryStringStmt(`SELECT sql FROM sqlite_master`); err != nil {
|
|
t.Logf("failed to query for schema during goroutine %d execution: %s", j, err.Error())
|
|
} else {
|
|
n++
|
|
if exp, got := `[{"columns":["sql"],"types":["text"],"values":[["CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE qux (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"]]}]`, asJSON(rows); exp != got {
|
|
t.Logf("schema not as expected during goroutine execution, exp %s, got %s, after %d queries", exp, got, n)
|
|
}
|
|
}
|
|
if n == 500000 {
|
|
break
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
qWg.Wait()
|
|
|
|
close(done)
|
|
exWg.Wait()
|
|
}
|
|
|
|
func mustSetupDBForTimeoutTests(t *testing.T, n int) (*DB, string) {
|
|
db, path := mustCreateOnDiskDatabase()
|
|
|
|
req := &command.Request{
|
|
Statements: []*command.Statement{
|
|
{
|
|
Sql: `CREATE TABLE IF NOT EXISTS test_table (
|
|
key1 VARCHAR(64) PRIMARY KEY,
|
|
key_id VARCHAR(64) NOT NULL,
|
|
key2 VARCHAR(64) NOT NULL,
|
|
key3 VARCHAR(64) NOT NULL,
|
|
key4 VARCHAR(64) NOT NULL,
|
|
key5 VARCHAR(64) NOT NULL,
|
|
key6 VARCHAR(64) NOT NULL,
|
|
data BLOB NOT NULL
|
|
);`,
|
|
},
|
|
},
|
|
}
|
|
|
|
for i := 0; i < n; i++ {
|
|
args := []any{
|
|
random.String(),
|
|
fmt.Sprint(i),
|
|
random.String(),
|
|
random.String(),
|
|
random.String(),
|
|
random.String(),
|
|
random.String(),
|
|
random.String(),
|
|
}
|
|
req.Statements = append(req.Statements, &command.Statement{
|
|
Sql: fmt.Sprintf(`INSERT INTO test_table
|
|
(key1, key_id, key2, key3, key4, key5, key6, data)
|
|
VALUES
|
|
(%q, %q, %q, %q, %q, %q, %q, %q);`, args...),
|
|
})
|
|
}
|
|
|
|
_, err := db.Execute(req, false)
|
|
if err != nil {
|
|
t.Fatalf("failed to insert records: %s", err.Error())
|
|
}
|
|
|
|
return db, path
|
|
}
|
|
|
|
func Test_ExecShouldTimeout(t *testing.T) {
|
|
db, path := mustSetupDBForTimeoutTests(t, 1000)
|
|
defer db.Close()
|
|
defer os.Remove(path)
|
|
|
|
q := `
|
|
INSERT INTO test_table (key1, key_id, key2, key3, key4, key5, key6, data)
|
|
SELECT t1.key1 || t2.key1, t1.key_id || t2.key_id, t1.key2 || t2.key2, t1.key3 || t2.key3, t1.key4 || t2.key4, t1.key5 || t2.key5, t1.key6 || t2.key6, t1.data || t2.data
|
|
FROM test_table t1 LEFT OUTER JOIN test_table t2`
|
|
r, err := db.ExecuteStringStmtWithTimeout(q, 1*time.Millisecond)
|
|
if err != nil {
|
|
t.Fatalf("failed to execute: %s", err.Error())
|
|
}
|
|
|
|
if len(r) != 1 {
|
|
t.Fatalf("expected one result, got %d: %s", len(r), asJSON(r))
|
|
}
|
|
|
|
res := r[0]
|
|
if !strings.Contains(res.Error, "context deadline exceeded") {
|
|
t.Fatalf("expected context.DeadlineExceeded, got %s", res.Error)
|
|
}
|
|
|
|
qr, err := db.QueryStringStmt("SELECT COUNT(*) FROM test_table")
|
|
if err != nil {
|
|
t.Fatalf("error counting rows: %s", err.Error())
|
|
}
|
|
if want, got := `[{"columns":["COUNT(*)"],"types":["integer"],"values":[[1000]]}]`, asJSON(qr); want != got {
|
|
t.Fatalf("want response %s, got %s", want, got)
|
|
}
|
|
}
|
|
|
|
func Test_QueryShouldTimeout(t *testing.T) {
|
|
db, path := mustSetupDBForTimeoutTests(t, 1000)
|
|
defer db.Close()
|
|
defer os.Remove(path)
|
|
|
|
q := `SELECT key1, key_id, key2, key3, key4, key5, key6, data
|
|
FROM test_table
|
|
ORDER BY key2 ASC`
|
|
r, err := db.QueryStringStmtWithTimeout(q, 1*time.Microsecond)
|
|
if err != nil {
|
|
t.Fatalf("failed to run query: %s", err.Error())
|
|
}
|
|
|
|
if len(r) != 1 {
|
|
t.Fatalf("expected one result, got %d: %s", len(r), asJSON(r))
|
|
}
|
|
|
|
res := r[0]
|
|
if !strings.Contains(res.Error, "context deadline exceeded") {
|
|
t.Fatalf("expected context.DeadlineExceeded, got %s", res.Error)
|
|
}
|
|
}
|
|
|
|
func Test_RequestShouldTimeout(t *testing.T) {
|
|
db, path := mustSetupDBForTimeoutTests(t, 1000)
|
|
defer db.Close()
|
|
defer os.Remove(path)
|
|
|
|
q := `SELECT key1, key_id, key2, key3, key4, key5, key6, data
|
|
FROM test_table
|
|
ORDER BY key2 ASC`
|
|
res, err := db.RequestStringStmtsWithTimeout([]string{q}, 1*time.Microsecond)
|
|
if err != nil {
|
|
t.Fatalf("failed to run query: %s", err.Error())
|
|
}
|
|
|
|
if len(res) != 1 {
|
|
t.Fatalf("expected one result, got %d: %s", len(res), asJSON(res))
|
|
}
|
|
|
|
r := res[0]
|
|
if !strings.Contains(r.GetQ().Error, "context deadline exceeded") {
|
|
t.Fatalf("expected context.DeadlineExceeded, got %s", r.GetQ().Error)
|
|
}
|
|
}
|
|
|
|
func mustCreateOnDiskDatabase() (*DB, string) {
|
|
var err error
|
|
f := mustTempFile()
|
|
db, err := Open(f, false, false)
|
|
if err != nil {
|
|
panic("failed to open database in DELETE mode")
|
|
}
|
|
return db, f
|
|
}
|
|
|
|
func mustCreateOnDiskDatabaseWAL() (*DB, string) {
|
|
var err error
|
|
f := mustTempFile()
|
|
db, err := Open(f, false, true)
|
|
if err != nil {
|
|
panic("failed to open database in WAL mode")
|
|
}
|
|
return db, f
|
|
}
|
|
|
|
func mustCreateOnDiskDatabaseWALFK() (*DB, string) {
|
|
var err error
|
|
f := mustTempFile()
|
|
db, err := Open(f, true, true)
|
|
if err != nil {
|
|
panic("failed to open database in WAL mode")
|
|
}
|
|
return db, f
|
|
}
|
|
|
|
// mustExecute executes a statement, and panics on failure. Used for statements
|
|
// that should never fail, even taking into account test setup.
|
|
func mustExecute(db *DB, stmt string) {
|
|
r, err := db.ExecuteStringStmt(stmt)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("failed to execute statement: %s", err.Error()))
|
|
}
|
|
if r[0].Error != "" {
|
|
panic(fmt.Sprintf("failed to execute statement: %s", r[0].Error))
|
|
}
|
|
}
|
|
|
|
func asJSON(v interface{}) string {
|
|
enc := encoding.Encoder{}
|
|
b, err := enc.JSONMarshal(v)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("failed to JSON marshal value: %s", err.Error()))
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
// mustTempPath returns a path which can be used for a temporary file or directory.
|
|
// No file will exist at the path.
|
|
func mustTempPath() string {
|
|
tmpfile, err := os.CreateTemp("", "rqlite-db-test")
|
|
if err != nil {
|
|
panic(err.Error())
|
|
}
|
|
tmpfile.Close()
|
|
if err := os.Remove(tmpfile.Name()); err != nil {
|
|
panic(err.Error())
|
|
}
|
|
return tmpfile.Name()
|
|
}
|
|
|
|
// mustTempFile returns a path to a temporary file in directory dir. It is up to the
|
|
// caller to remove the file once it is no longer needed.
|
|
func mustTempFile() string {
|
|
tmpfile, err := os.CreateTemp("", "rqlite-db-test")
|
|
if err != nil {
|
|
panic(err.Error())
|
|
}
|
|
tmpfile.Close()
|
|
return tmpfile.Name()
|
|
}
|
|
|
|
func mustTempDir() string {
|
|
tmpdir, err := os.MkdirTemp("", "rqlite-db-test")
|
|
if err != nil {
|
|
panic(err.Error())
|
|
}
|
|
return tmpdir
|
|
}
|
|
|
|
// function which copies a src file to a dst file, panics if any error
|
|
func mustCopyFile(dst, src string) {
|
|
srcFile, err := os.Open(src)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer srcFile.Close()
|
|
|
|
dstFile, err := os.Create(dst)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer dstFile.Close()
|
|
|
|
_, err = io.Copy(dstFile, srcFile)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func mustCreateClosedFile(path string) {
|
|
f, err := os.Create(path)
|
|
if err != nil {
|
|
panic("failed to create file")
|
|
}
|
|
if err := f.Close(); err != nil {
|
|
panic("failed to close file")
|
|
}
|
|
}
|
|
|
|
func mustStat(path string) os.FileInfo {
|
|
fi, err := os.Stat(path)
|
|
if err != nil {
|
|
panic("failed to stat file")
|
|
}
|
|
return fi
|
|
}
|
|
|
|
func mustFileSize(path string) int64 {
|
|
fi := mustStat(path)
|
|
return fi.Size()
|
|
}
|