1
0
Fork 0

Split up DB-level unit testing

master
Philip O'Toole 1 year ago
parent 16ae659175
commit ca6f071fb5

@ -0,0 +1,297 @@
package db
import (
"io/ioutil"
"os"
"sync"
"testing"
"time"
"github.com/rqlite/rqlite/command"
)
// Test_TableCreationInMemory tests basic operation of an in-memory database,
// ensuring that using different connection objects (as the Execute and Query
// will do) works properly i.e. that the connections object work on the same
// in-memory database.
func Test_TableCreationInMemory(t *testing.T) {
db := mustCreateInMemoryDatabase()
defer db.Close()
if !db.InMemory() {
t.Fatal("in-memory database marked as not in-memory")
}
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)
}
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)
}
}
func Test_LoadIntoMemory(t *testing.T) {
db, path := mustCreateDatabase()
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())
}
r, 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(r); exp != got {
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
}
inmem, err := LoadIntoMemory(path, false)
if err != nil {
t.Fatalf("failed to create loaded in-memory database: %s", err.Error())
}
// Ensure it has been loaded correctly into the database
r, err = inmem.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(r); exp != got {
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
}
}
func Test_DeserializeIntoMemory(t *testing.T) {
db, path := mustCreateDatabase()
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")`,
},
},
}
_, err = db.Execute(req, false)
if err != nil {
t.Fatalf("failed to insert records: %s", err.Error())
}
// Get byte representation of database on disk which, according to SQLite docs
// is the same as a serialized version.
b, err := ioutil.ReadFile(path)
if err != nil {
t.Fatalf("failed to read database on disk: %s", err.Error())
}
newDB, err := DeserializeIntoMemory(b, false)
if err != nil {
t.Fatalf("failed to deserialize database: %s", err.Error())
}
defer newDB.Close()
ro, err := newDB.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)
}
// Write a lot of records to the new database, to ensure it's fully functional.
req = &command.Request{
Statements: []*command.Statement{
{
Sql: `INSERT INTO foo(name) VALUES("fiona")`,
},
},
}
for i := 0; i < 5000; i++ {
_, err = newDB.Execute(req, false)
if err != nil {
t.Fatalf("failed to insert records: %s", err.Error())
}
}
ro, err = newDB.QueryStringStmt(`SELECT COUNT(*) FROM foo`)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := `[{"columns":["COUNT(*)"],"types":[""],"values":[[5004]]}]`, asJSON(ro); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
// 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 := mustCreateInMemoryDatabase()
defer db.Close()
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()
}
// Test_TableCreationLoadRawInMemory tests for https://sqlite.org/forum/forumpost/d443fb0730
func Test_TableCreationLoadRawInMemory(t *testing.T) {
db := mustCreateInMemoryDatabase()
defer db.Close()
_, err := db.ExecuteStringStmt("CREATE TABLE logs (entry TEXT)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
done := make(chan struct{})
defer close(done)
// Insert some records continually, as fast as possible. Do it from a goroutine.
go func() {
for {
select {
case <-done:
return
default:
_, err := db.ExecuteStringStmt(`INSERT INTO logs(entry) VALUES("hello")`)
if err != nil {
return
}
}
}
}()
// Get the count over and over again.
for i := 0; i < 5000; i++ {
rows, err := db.QueryStringStmt(`SELECT COUNT(*) FROM logs`)
if err != nil {
t.Fatalf("failed to query for count: %s", err)
}
if rows[0].Error != "" {
t.Fatalf("rows had error after %d queries: %s", i, rows[0].Error)
}
}
}

@ -0,0 +1,69 @@
package db
import (
"database/sql"
"fmt"
"os"
"path"
"testing"
)
func Test_IsValidSQLiteOnDisk(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())
}
if !IsValidSQLiteFile(path) {
t.Fatalf("good SQLite file marked as invalid")
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("failed to read SQLite file: %s", err.Error())
}
if !IsValidSQLiteData(data) {
t.Fatalf("good SQLite data marked as invalid")
}
}
func Test_FileCreationOnDisk(t *testing.T) {
dir := t.TempDir()
dbPath := path.Join(dir, "test_db")
db, err := Open(dbPath, false)
if err != nil {
t.Fatalf("failed to open new database: %s", err.Error())
}
if db == nil {
t.Fatal("database is nil")
}
if db.InMemory() {
t.Fatal("on-disk database marked as in-memory")
}
if db.FKEnabled() {
t.Fatal("FK constraints marked as enabled")
}
if db.Path() != dbPath {
t.Fatal("database path is incorrect")
}
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
t.Fatalf("%s does not exist after open", dbPath)
}
err = db.Close()
if err != nil {
t.Fatalf("failed to close database: %s", err.Error())
}
}

@ -1,16 +1,13 @@
package db
import (
"database/sql"
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"sync"
"testing"
"time"
"github.com/rqlite/rqlite/command"
"github.com/rqlite/rqlite/command/encoding"
@ -18,66 +15,6 @@ import (
"github.com/rqlite/rqlite/testdata/chinook"
)
func Test_IsValidSQLite(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())
}
if !IsValidSQLiteFile(path) {
t.Fatalf("good SQLite file marked as invalid")
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("failed to read SQLite file: %s", err.Error())
}
if !IsValidSQLiteData(data) {
t.Fatalf("good SQLite data marked as invalid")
}
}
func Test_DbFileCreation(t *testing.T) {
dir := t.TempDir()
dbPath := path.Join(dir, "test_db")
db, err := Open(dbPath, false)
if err != nil {
t.Fatalf("failed to open new database: %s", err.Error())
}
if db == nil {
t.Fatal("database is nil")
}
if db.InMemory() {
t.Fatal("on-disk database marked as in-memory")
}
if db.FKEnabled() {
t.Fatal("FK constraints marked as enabled")
}
if db.Path() != dbPath {
t.Fatal("database path is incorrect")
}
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
t.Fatalf("%s does not exist after open", dbPath)
}
err = db.Close()
if err != nil {
t.Fatalf("failed to close database: %s", err.Error())
}
}
func Test_CompileOptions(t *testing.T) {
db, path := mustCreateDatabase()
defer db.Close()
@ -125,35 +62,6 @@ func Test_TableCreation(t *testing.T) {
}
}
// Test_TableCreationInMemory tests basic operation of an in-memory database,
// ensuring that using different connection objects (as the Execute and Query
// will do) works properly i.e. that the connections object work on the same
// in-memory database.
func Test_TableCreationInMemory(t *testing.T) {
db := mustCreateInMemoryDatabase()
defer db.Close()
if !db.InMemory() {
t.Fatal("in-memory database marked as not in-memory")
}
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)
}
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)
}
}
// Test_TableCreationInMemoryFK ensures foreign key constraints work
func Test_TableCreationInMemoryFK(t *testing.T) {
createTableFoo := "CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"
@ -280,115 +188,6 @@ func Test_NotNULLField(t *testing.T) {
}
}
func Test_LoadIntoMemory(t *testing.T) {
db, path := mustCreateDatabase()
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())
}
r, 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(r); exp != got {
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
}
inmem, err := LoadIntoMemory(path, false)
if err != nil {
t.Fatalf("failed to create loaded in-memory database: %s", err.Error())
}
// Ensure it has been loaded correctly into the database
r, err = inmem.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(r); exp != got {
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
}
}
func Test_DeserializeIntoMemory(t *testing.T) {
db, path := mustCreateDatabase()
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")`,
},
},
}
_, err = db.Execute(req, false)
if err != nil {
t.Fatalf("failed to insert records: %s", err.Error())
}
// Get byte representation of database on disk which, according to SQLite docs
// is the same as a serialized version.
b, err := ioutil.ReadFile(path)
if err != nil {
t.Fatalf("failed to read database on disk: %s", err.Error())
}
newDB, err := DeserializeIntoMemory(b, false)
if err != nil {
t.Fatalf("failed to deserialize database: %s", err.Error())
}
defer newDB.Close()
ro, err := newDB.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)
}
// Write a lot of records to the new database, to ensure it's fully functional.
req = &command.Request{
Statements: []*command.Statement{
{
Sql: `INSERT INTO foo(name) VALUES("fiona")`,
},
},
}
for i := 0; i < 5000; i++ {
_, err = newDB.Execute(req, false)
if err != nil {
t.Fatalf("failed to insert records: %s", err.Error())
}
}
ro, err = newDB.QueryStringStmt(`SELECT COUNT(*) FROM foo`)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := `[{"columns":["COUNT(*)"],"types":[""],"values":[[5004]]}]`, asJSON(ro); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
func Test_EmptyStatements(t *testing.T) {
db, path := mustCreateDatabase()
defer db.Close()
@ -1888,113 +1687,6 @@ func Test_1GiBInMemory(t *testing.T) {
}
}
// 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 := mustCreateInMemoryDatabase()
defer db.Close()
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 Test_DBSTAT_table(t *testing.T) {
db := mustCreateInMemoryDatabase()
defer db.Close()
@ -2050,47 +1742,6 @@ func Test_JSON1(t *testing.T) {
}
}
// Test_TableCreationInMemoryLoadRaw tests for https://sqlite.org/forum/forumpost/d443fb0730
func Test_TableCreationInMemoryLoadRaw(t *testing.T) {
db := mustCreateInMemoryDatabase()
defer db.Close()
_, err := db.ExecuteStringStmt("CREATE TABLE logs (entry TEXT)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
done := make(chan struct{})
defer close(done)
// Insert some records continually, as fast as possible. Do it from a goroutine.
go func() {
for {
select {
case <-done:
return
default:
_, err := db.ExecuteStringStmt(`INSERT INTO logs(entry) VALUES("hello")`)
if err != nil {
return
}
}
}
}()
// Get the count over and over again.
for i := 0; i < 5000; i++ {
rows, err := db.QueryStringStmt(`SELECT COUNT(*) FROM logs`)
if err != nil {
t.Fatalf("failed to query for count: %s", err)
}
if rows[0].Error != "" {
t.Fatalf("rows had error after %d queries: %s", i, rows[0].Error)
}
}
}
func Test_StmtReadOnly(t *testing.T) {
db := mustCreateInMemoryDatabase()
defer db.Close()

Loading…
Cancel
Save