1
0
Fork 0
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.

1643 lines
52 KiB
Go

package db
import (
"errors"
"os"
"strings"
"testing"
9 months ago
command "github.com/rqlite/rqlite/v8/command/proto"
"github.com/rqlite/rqlite/v8/random"
"github.com/rqlite/rqlite/v8/testdata/chinook"
)
func testBusyTimeout(t *testing.T, db *DB) {
wantRw := random.Intn(10000)
wantRo := random.Intn(10000)
err := db.SetBusyTimeout(wantRw, wantRo)
if err != nil {
t.Fatalf("failed to set busy_timeout: %s", err.Error())
}
gotRw, gotRo, err := db.BusyTimeout()
if err != nil {
t.Fatalf("failed to get busy_timeout: %s", err.Error())
}
if gotRw != wantRw || gotRo != wantRo {
t.Fatalf("want busy_timeout rw=%d, ro=%d, got rw=%d, ro=%d", wantRw, wantRo, gotRw, gotRo)
}
}
func testCompileOptions(t *testing.T, db *DB) {
_, err := db.CompileOptions()
if err != nil {
t.Fatalf("failed to retrieve compilation options: %s", err.Error())
}
}
func testSetSynchronousMode(t *testing.T, db *DB) {
modes := map[string]int{
"OFF": 0,
"NORMAL": 1,
"FULL": 2,
"EXTRA": 3,
}
for m, i := range modes {
if db.SetSynchronousMode(m) != nil {
t.Fatalf("failed to set synchronous mode to %s", m)
}
mm, err := db.GetSynchronousMode()
if err != nil {
t.Fatalf("failed to get synchronous mode: %s", err.Error())
}
if mm != i {
t.Fatalf("synchonous mode not set to %s", m)
}
}
}
func testTableNotExist(t *testing.T, db *DB) {
q, err := db.QueryStringStmt("SELECT * FROM foo")
if err != nil {
t.Fatalf("failed to query empty table: %s", err.Error())
}
if exp, got := `[{"error":"no such table: foo"}]`, asJSON(q); exp != got {
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
}
}
func testTableCreation(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)
}
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 testTableCreationFTS(t *testing.T, db *DB) {
r, err := db.ExecuteStringStmt("CREATE VIRTUAL TABLE foo3 USING fts3(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("CREATE VIRTUAL TABLE foo4 USING fts4(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("CREATE VIRTUAL TABLE email USING fts5(sender, title, body)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
// Creating an FTS5 table actually affects rows, so just make sure it's not an error.
if strings.Contains(asJSON(r), "error") {
t.Fatalf("unexpected error for query, expected %s, got %s", `[{}]`, asJSON(r))
}
}
func testSQLiteMasterTable(t *testing.T, db *DB) {
_, 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 sqlite_master")
if err != nil {
t.Fatalf("failed to query master table: %s", err.Error())
}
if exp, got := `[{"columns":["type","name","tbl_name","rootpage","sql"],"types":["text","text","text","int","text"],"values":[["table","foo","foo",2,"CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"]]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
}
}
func testSQLiteTimeTypes(t *testing.T, db *DB) {
_, err := db.ExecuteStringStmt("CREATE TABLE foo(d DATE, ts TIMESTAMP, dt DATETIME)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
_, err = db.ExecuteStringStmt(`INSERT INTO foo(d, ts, dt) VALUES("2004-02-14", "2004-02-14 07:15:00", "2021-10-22 19:39:32.016087")`)
if err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
r, err := db.QueryStringStmt("SELECT d, ts, dt FROM foo")
if err != nil {
t.Fatalf("failed to query master table: %s", err.Error())
}
if exp, got := `[{"columns":["d","ts","dt"],"types":["date","timestamp","datetime"],"values":[["2004-02-14T00:00:00Z","2004-02-14T07:15:00Z","2021-10-22T19:39:32.016087Z"]]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
}
}
func testSQLiteRandomBlob(t *testing.T, db *DB) {
_, err := db.ExecuteStringStmt("CREATE TABLE large_data (id INTEGER PRIMARY KEY, large_text TEXT)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
_, err = db.ExecuteStringStmt(`
WITH RECURSIVE generate_large_data(id, large_text) AS (
SELECT 1, randomblob(1)
UNION ALL
SELECT id + 1, randomblob(1)
FROM generate_large_data
WHERE id < 2
)
INSERT INTO large_data(id, large_text)
SELECT id, large_text FROM generate_large_data
`)
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
r, err := db.QueryStringStmt("SELECT * FROM large_data LIMIT 1")
if err != nil {
t.Fatalf("failed to query master table: %s", err.Error())
}
if !strings.Contains(asJSON(r), "large_text") {
// Just check that it doesn't panic.
t.Fatalf("unexpected results for query, expected large_text, got %s", asJSON(r))
}
}
func testNotNULLField(t *testing.T, db *DB) {
_, 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())
}
7 months ago
r, err := db.QueryStringStmt(`PRAGMA table_info("foo")`)
if err != nil {
7 months ago
t.Fatalf("failed to get PRAGMA table_info: %s", err.Error())
}
if exp, got := `[{"columns":["cid","name","type","notnull","dflt_value","pk"],"types":["integer","text","text","integer","",""],"values":[[0,"id","INTEGER",1,null,1],[1,"name","TEXT",0,null,0]]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
}
}
func testEmptyStatements(t *testing.T, db *DB) {
_, err := db.ExecuteStringStmt("")
if err != nil {
t.Fatalf("failed to execute empty statement: %s", err.Error())
}
_, err = db.ExecuteStringStmt(";")
if err != nil {
t.Fatalf("failed to execute empty statement with semicolon: %s", err.Error())
}
}
func testSimpleStatementsNumeric(t *testing.T, db *DB) {
_, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT, age NUMERIC)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
_, err = db.ExecuteStringStmt(`INSERT INTO foo(name, age) VALUES("fiona", 20)`)
if err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
r, err := db.QueryStringStmt(`SELECT * FROM foo`)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := `[{"columns":["id","name","age"],"types":["integer","text","numeric"],"values":[[1,"fiona",20]]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
func testSimpleStatementsCollate(t *testing.T, db *DB) {
_, err := db.ExecuteStringStmt("CREATE TABLE foo(x INTEGER PRIMARY KEY, a, b COLLATE BINARY, c COLLATE RTRIM, d COLLATE NOCASE)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
req := &command.Request{
Transaction: true,
Statements: []*command.Statement{
{
Sql: `INSERT INTO foo VALUES(1,'abc','abc', 'abc ','abc')`,
},
{
Sql: `INSERT INTO foo VALUES(2,'abc','abc', 'abc', 'ABC')`,
},
{
Sql: `INSERT INTO foo VALUES(3,'abc','abc', 'abc ', 'Abc')`,
},
{
Sql: `INSERT INTO foo VALUES(4,'abc','abc ','ABC', 'abc')`,
},
},
}
_, err = db.Execute(req, false)
if err != nil {
t.Fatalf("failed to insert records: %s", err.Error())
}
tests := []struct {
query string
exp string
}{
{
query: `SELECT x FROM foo WHERE a = b ORDER BY x`,
exp: `[{"columns":["x"],"types":["integer"],"values":[[1],[2],[3]]}]`,
},
{
query: `SELECT x FROM foo WHERE a = b COLLATE RTRIM ORDER BY x`,
exp: `[{"columns":["x"],"types":["integer"],"values":[[1],[2],[3],[4]]}]`,
},
{
query: `SELECT count(*) FROM foo GROUP BY d ORDER BY 1`,
exp: `[{"columns":["count(*)"],"types":["integer"],"values":[[4]]}]`,
},
{
query: `SELECT count(*) FROM foo GROUP BY (d || '') ORDER BY 1`,
exp: `[{"columns":["count(*)"],"types":["integer"],"values":[[1],[1],[2]]}]`,
},
{
query: `SELECT x FROM foo ORDER BY c COLLATE NOCASE, x`,
exp: `[{"columns":["x"],"types":["integer"],"values":[[2],[4],[3],[1]]}]`,
},
}
for _, tt := range tests {
r, err := db.QueryStringStmt(tt.query)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := tt.exp, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
}
func testSimpleSingleStatements(t *testing.T, db *DB) {
_, 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())
}
_, err = db.ExecuteStringStmt(`INSERT INTO foo(name) VALUES("fiona")`)
if err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
_, err = db.ExecuteStringStmt(`INSERT INTO foo(name) VALUES("aoife")`)
if err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
r, 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,"aoife"]]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
r, err = db.QueryStringStmt(`SELECT * FROM foo WHERE name="aoife"`)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[2,"aoife"]]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
r, err = db.QueryStringStmt(`SELECT * FROM foo WHERE name="dana"`)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := `[{"columns":["id","name"],"types":["integer","text"]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
r, err = db.QueryStringStmt(`SELECT * FROM foo ORDER BY name`)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[2,"aoife"],[1,"fiona"]]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
r, err = db.QueryStringStmt(`SELECT *,name FROM foo`)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := `[{"columns":["id","name","name"],"types":["integer","text","text"],"values":[[1,"fiona","fiona"],[2,"aoife","aoife"]]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
// testSimpleExpressionStatements tests that types are set for expressions.
func testSimpleExpressionStatements(t *testing.T, db *DB) {
_, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT, age INTEGER, height REAL)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
_, err = db.ExecuteStringStmt(`INSERT INTO foo(id, name, age, height) VALUES(1, "fiona", 20, 6.7)`)
if err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
_, err = db.ExecuteStringStmt(`INSERT INTO foo(id, name, age, height) VALUES(2, "aoife", 40, 10.4)`)
if err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
tests := []struct {
query string
exp string
}{
{
query: `SELECT sum(name) FROM foo`,
exp: `[{"columns":["sum(name)"],"types":["real"],"values":[[0]]}]`,
},
{
query: `SELECT sum(age) FROM foo`,
exp: `[{"columns":["sum(age)"],"types":["integer"],"values":[[60]]}]`,
},
{
query: `SELECT sum(height) FROM foo`,
exp: `[{"columns":["sum(height)"],"types":["real"],"values":[[17.1]]}]`,
},
{
query: `SELECT count(*) FROM foo`,
exp: `[{"columns":["count(*)"],"types":["integer"],"values":[[2]]}]`,
},
{
query: `SELECT avg(height) FROM foo`,
exp: `[{"columns":["avg(height)"],"types":["real"],"values":[[8.55]]}]`,
},
{
query: `SELECT avg(height),count(*),sum(age) FROM foo`,
exp: `[{"columns":["avg(height)","count(*)","sum(age)"],"types":["real","integer","integer"],"values":[[8.55,2,60]]}]`,
},
}
for _, tt := range tests {
r, err := db.QueryStringStmt(tt.query)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := tt.exp, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
}
func testSimpleSingleJSONStatements(t *testing.T, db *DB) {
_, err := db.ExecuteStringStmt("CREATE TABLE foo (c0 VARCHAR(36), c1 JSON, c2 NCHAR, c3 NVARCHAR, c4 CLOB)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
_, err = db.ExecuteStringStmt(`INSERT INTO foo(c0, c1, c2, c3, c4) VALUES("fiona", '{"mittens": "foobar"}', "bob", "dana", "declan")`)
if err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
r, err := db.QueryStringStmt("SELECT * FROM foo")
if err != nil {
t.Fatalf("failed to query: %s", err.Error())
}
if exp, got := `[{"columns":["c0","c1","c2","c3","c4"],"types":["varchar(36)","json","nchar","nvarchar","clob"],"values":[["fiona","{\"mittens\": \"foobar\"}","bob","dana","declan"]]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
}
}
8 months ago
func testSimpleSingleJSONBStatements(t *testing.T, db *DB) {
_, err := db.ExecuteStringStmt("CREATE TABLE foo (tag JSONB)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
_, err = db.ExecuteStringStmt(`INSERT INTO foo(tag) VALUES('{"mittens": "qux"}')`)
if err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
r, err := db.QueryStringStmt(`SELECT tag->>'mittens' FROM foo WHERE tag->>'mittens' = 'qux'`)
if err != nil {
t.Fatalf("failed to query: %s", err.Error())
}
if exp, got := `[{"columns":["tag->>'mittens'"],"types":["text"],"values":[["qux"]]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
}
}
func testSimpleJoinStatements(t *testing.T, db *DB) {
_, err := db.ExecuteStringStmt("CREATE TABLE names (id INTEGER NOT NULL PRIMARY KEY, name TEXT, ssn TEXT)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
req := &command.Request{
Statements: []*command.Statement{
{
Sql: `INSERT INTO "names" VALUES(1,'bob','123-45-678')`,
},
{
Sql: `INSERT INTO "names" VALUES(2,'tom','111-22-333')`,
},
{
Sql: `INSERT INTO "names" VALUES(3,'matt','222-22-333')`,
},
},
}
_, err = db.Execute(req, false)
if err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
_, err = db.ExecuteStringStmt("CREATE TABLE staff (id INTEGER NOT NULL PRIMARY KEY, employer TEXT, ssn TEXT)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
_, err = db.ExecuteStringStmt(`INSERT INTO "staff" VALUES(1,'acme','222-22-333')`)
if err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
r, err := db.QueryStringStmt(`SELECT names.id,name,names.ssn,employer FROM names INNER JOIN staff ON staff.ssn = names.ssn`)
if err != nil {
t.Fatalf("failed to query table using JOIN: %s", err.Error())
}
if exp, got := `[{"columns":["id","name","ssn","employer"],"types":["integer","text","text","text"],"values":[[3,"matt","222-22-333","acme"]]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
func testSimpleSingleConcatStatements(t *testing.T, db *DB) {
_, 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())
}
_, err = db.ExecuteStringStmt(`INSERT INTO foo(name) VALUES("fiona")`)
if err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
r, err := db.QueryStringStmt(`SELECT id || "_bar", name FROM foo`)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := `[{"columns":["id || \"_bar\"","name"],"types":["text","text"],"values":[["1_bar","fiona"]]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
func testSimpleMultiStatements(t *testing.T, db *DB) {
_, 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{
Statements: []*command.Statement{
{
Sql: `INSERT INTO foo(name) VALUES("fiona")`,
},
{
Sql: `INSERT INTO foo(name) VALUES("dana")`,
},
},
}
re, err := db.Execute(req, false)
if err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
if exp, got := `[{"last_insert_id":1,"rows_affected":1},{"last_insert_id":2,"rows_affected":1}]`, asJSON(re); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
req = &command.Request{
Statements: []*command.Statement{
{
Sql: `SELECT * FROM foo`,
},
{
Sql: `SELECT * FROM foo`,
},
},
}
ro, err := db.Query(req, false)
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"],[2,"dana"]]},{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"],[2,"dana"]]}]`, asJSON(ro); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
func testSimpleSingleMultiLineStatements(t *testing.T, db *DB) {
req := &command.Request{
Statements: []*command.Statement{
{
Sql: `
CREATE TABLE foo (
id INTEGER NOT NULL PRIMARY KEY,
name TEXT
)`,
},
},
}
_, err := db.Execute(req, false)
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
req = &command.Request{
Statements: []*command.Statement{
{
Sql: `INSERT INTO foo(name) VALUES("fiona")`,
},
{
Sql: `INSERT INTO foo(name) VALUES("dana")`,
},
},
}
re, err := db.Execute(req, false)
if err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
if exp, got := `[{"last_insert_id":1,"rows_affected":1},{"last_insert_id":2,"rows_affected":1}]`, asJSON(re); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
func testSimpleFailingStatements_Execute(t *testing.T, db *DB) {
r, err := db.ExecuteStringStmt(`INSERT INTO foo(name) VALUES("fiona")`)
if err != nil {
t.Fatalf("error executing insertion into non-existent table: %s", err.Error())
}
if exp, got := `[{"error":"no such table: foo"}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
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)
}
r, err = db.ExecuteStringStmt(`CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`)
if err != nil {
t.Fatalf("failed to attempt creation of duplicate table: %s", err.Error())
}
if exp, got := `[{"error":"table foo already exists"}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
r, err = db.ExecuteStringStmt(`INSERT INTO foo(id, name) VALUES(11, "fiona")`)
if err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
if exp, got := `[{"last_insert_id":11,"rows_affected":1}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
r, err = db.ExecuteStringStmt(`INSERT INTO foo(id, name) VALUES(11, "fiona")`)
if err != nil {
t.Fatalf("failed to attempt duplicate record insertion: %s", err.Error())
}
if exp, got := `[{"error":"UNIQUE constraint failed: foo.id"}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
r, err = db.ExecuteStringStmt(`utter nonsense`)
if err != nil {
if exp, got := `[{"error":"near \"utter\": syntax error"}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
}
func testSimpleFailingStatements_Query(t *testing.T, db *DB) {
ro, err := db.QueryStringStmt(`SELECT * FROM bar`)
if err != nil {
t.Fatalf("failed to attempt query of non-existent table: %s", err.Error())
}
if exp, got := `[{"error":"no such table: bar"}]`, asJSON(ro); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
ro, err = db.QueryStringStmt(`SELECTxx * FROM foo`)
if err != nil {
t.Fatalf("failed to attempt nonsense query: %s", err.Error())
}
if exp, got := `[{"error":"near \"SELECTxx\": syntax error"}]`, asJSON(ro); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
r, err := db.QueryStringStmt(`utter nonsense`)
if err != nil {
if exp, got := `[{"error":"near \"utter\": syntax error"}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
}
func testSimplePragmaTableInfo(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\nexp: %s\ngot: %s", exp, got)
}
res, err := db.QueryStringStmt(`PRAGMA table_info("foo")`)
if err != nil {
t.Fatalf("failed to query a common table expression: %s", err.Error())
}
if exp, got := `[{"columns":["cid","name","type","notnull","dflt_value","pk"],"types":["integer","text","text","integer","",""],"values":[[0,"id","INTEGER",1,null,1],[1,"name","TEXT",0,null,0]]}]`, asJSON(res); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
func testWriteOnQueryDatabaseShouldFail(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\nexp: %s\ngot: %s", exp, got)
}
r, err = db.ExecuteStringStmt(`INSERT INTO foo(id, name) VALUES(1, "fiona")`)
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\nexp: %s\ngot: %s", exp, got)
}
ro, err := db.QueryStringStmt(`INSERT INTO foo(id, name) VALUES(2, "fiona")`)
if err != nil {
t.Fatalf("error attempting read-only write test: %s", err)
}
if exp, got := `[{"error":"attempt to change database via query operation"}]`, asJSON(ro); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
ro, err = db.QueryStringStmt(`SELECT COUNT(*) FROM foo`)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := `[{"columns":["COUNT(*)"],"types":["integer"],"values":[[1]]}]`, asJSON(ro); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
func testSimpleParameterizedStatements(t *testing.T, db *DB) {
_, 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{
Statements: []*command.Statement{
{
Sql: "INSERT INTO foo(name) VALUES(?)",
Parameters: []*command.Parameter{
{
Value: &command.Parameter_S{
S: "fiona",
},
},
},
},
},
}
_, err = db.Execute(req, false)
if err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
req.Statements[0].Parameters[0] = &command.Parameter{
Value: &command.Parameter_S{
S: "aoife",
},
}
_, err = db.Execute(req, false)
if err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
r, 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,"aoife"]]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
req.Statements[0].Sql = "SELECT * FROM foo WHERE name=?"
req.Statements[0].Parameters[0] = &command.Parameter{
Value: &command.Parameter_S{
S: "aoife",
},
}
r, err = db.Query(req, false)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[2,"aoife"]]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
req.Statements[0].Parameters[0] = &command.Parameter{
Value: &command.Parameter_S{
S: "fiona",
},
}
r, err = db.Query(req, false)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
req = &command.Request{
Statements: []*command.Statement{
{
Sql: "SELECT * FROM foo WHERE NAME=?",
Parameters: []*command.Parameter{
{
Value: &command.Parameter_S{
S: "fiona",
},
},
},
},
{
Sql: "SELECT * FROM foo WHERE NAME=?",
Parameters: []*command.Parameter{
{
Value: &command.Parameter_S{
S: "aoife",
},
},
},
},
},
}
r, err = db.Query(req, false)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]},{"columns":["id","name"],"types":["integer","text"],"values":[[2,"aoife"]]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
func testSimpleTwoParameterizedStatements(t *testing.T, db *DB) {
_, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, first TEXT, last TEXT)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
req := &command.Request{
Statements: []*command.Statement{
{
Sql: "INSERT INTO foo(first, last) VALUES(?, ?)",
Parameters: []*command.Parameter{
{
Value: &command.Parameter_S{
S: "bob",
},
},
{
Value: &command.Parameter_S{
S: "bobbers",
},
},
},
},
},
}
_, err = db.Execute(req, false)
if err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
r, err := db.QueryStringStmt(`SELECT * FROM foo`)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := `[{"columns":["id","first","last"],"types":["integer","text","text"],"values":[[1,"bob","bobbers"]]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
func testSimpleNilParameterizedStatements(t *testing.T, db *DB) {
_, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, first TEXT, last TEXT)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
req := &command.Request{
Statements: []*command.Statement{
{
Sql: "INSERT INTO foo(first, last) VALUES(?, ?)",
Parameters: []*command.Parameter{
{
Value: &command.Parameter_S{
S: "bob",
},
},
{
Value: nil,
},
},
},
},
}
_, err = db.Execute(req, false)
if err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
r, err := db.QueryStringStmt(`SELECT * FROM foo`)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := `[{"columns":["id","first","last"],"types":["integer","text","text"],"values":[[1,"bob",null]]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
func testSimpleNamedParameterizedStatements(t *testing.T, db *DB) {
_, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, first TEXT, last TEXT)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
if _, err = db.ExecuteStringStmt(`INSERT INTO foo(first, last) VALUES("albert", "einstein")`); err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
if _, err = db.ExecuteStringStmt(`INSERT INTO foo(first, last) VALUES("isaac", "newton")`); err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
req := &command.Request{
Statements: []*command.Statement{
{
Sql: "INSERT INTO foo(first, last) VALUES(:first, :last)",
Parameters: []*command.Parameter{
{
Value: &command.Parameter_S{
S: "isaac",
},
Name: "first",
},
{
Value: &command.Parameter_S{
S: "asimov",
},
Name: "last",
},
},
},
},
}
_, err = db.Execute(req, false)
if err != nil {
t.Fatalf("failed to insert parameterized statement: %s", err.Error())
}
rows, err := db.QueryStringStmt(`SELECT * FROM foo`)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := `[{"columns":["id","first","last"],"types":["integer","text","text"],"values":[[1,"albert","einstein"],[2,"isaac","newton"],[3,"isaac","asimov"]]}]`, asJSON(rows); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
req = &command.Request{
Statements: []*command.Statement{
{
Sql: "SELECT * FROM foo WHERE first=:first AND last=:last",
Parameters: []*command.Parameter{
{
Value: &command.Parameter_S{
S: "newton",
},
Name: "last",
},
{
Value: &command.Parameter_S{
S: "isaac",
},
Name: "first",
},
},
},
},
}
rn, err := db.Query(req, false)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := `[{"columns":["id","first","last"],"types":["integer","text","text"],"values":[[2,"isaac","newton"]]}]`, asJSON(rn); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
req = &command.Request{
Statements: []*command.Statement{
{
Sql: "SELECT * FROM foo WHERE first=:first AND last=:last",
Parameters: []*command.Parameter{
{
Value: &command.Parameter_S{
S: "clarke",
},
Name: "last",
},
{
Value: &command.Parameter_S{
S: "isaac",
},
Name: "first",
},
},
},
},
}
rn, err = db.Query(req, false)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := `[{"columns":["id","first","last"],"types":["integer","text","text"]}]`, asJSON(rn); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
func testSimpleRequest(t *testing.T, db *DB) {
_, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, first TEXT, last TEXT)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
// create table-driven tests
tests := []struct {
name string
stmts []string
exp string
}{
{
name: "insert",
stmts: []string{
`INSERT INTO foo(first, last) VALUES("albert", "einstein")`,
`INSERT INTO foo(first, last) VALUES("isaac", "newton")`,
},
exp: `[{"last_insert_id":1,"rows_affected":1},{"last_insert_id":2,"rows_affected":1}]`,
},
{
name: "select",
stmts: []string{
`SELECT * FROM foo`,
},
exp: `[{"columns":["id","first","last"],"types":["integer","text","text"],"values":[[1,"albert","einstein"],[2,"isaac","newton"]]}]`,
},
{
name: "update",
stmts: []string{
`UPDATE foo SET first="isaac", last="asimov" WHERE id=2`,
},
exp: `[{"last_insert_id":2,"rows_affected":1}]`,
},
{
name: "insert and select",
stmts: []string{
`INSERT INTO foo(first, last) VALUES("richard", "feynman")`,
`SELECT COUNT(*) FROM foo`,
`SELECT last FROM foo WHERE first="richard"`,
},
exp: `[{"last_insert_id":3,"rows_affected":1},{"columns":["COUNT(*)"],"types":["integer"],"values":[[3]]},{"columns":["last"],"types":["text"],"values":[["feynman"]]}]`,
},
{
name: "insert and select non-existent table",
stmts: []string{
`INSERT INTO foo(first, last) VALUES("paul", "dirac")`,
`SELECT COUNT(*) FROM foo`,
`SELECT * FROM bar`,
},
exp: `[{"last_insert_id":4,"rows_affected":1},{"columns":["COUNT(*)"],"types":["integer"],"values":[[4]]},{"error":"no such table: bar"}]`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r, err := db.RequestStringStmts(tt.stmts)
if err != nil {
t.Fatalf("failed to request empty statements: %s", err.Error())
}
if exp, got := tt.exp, asJSON(r); exp != got {
t.Fatalf(`Test "%s" failed, unexpected results for request exp: %s got: %s`, tt.name, exp, got)
}
})
}
}
// Test_SimpleRequestTx tests that a transaction is rolled back when an error occurs, and that
// subsequent statements after the failed statement are not processed. This also checks that
// the code which checks if the statement is a query or not works when holding a transaction.
func testSimpleRequestTx(t *testing.T, db *DB) {
mustExecute(db, `CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`)
mustExecute(db, `INSERT INTO foo(id, name) VALUES(1, "fiona")`)
request := &command.Request{
Statements: []*command.Statement{
{
Sql: `INSERT INTO foo(id, name) VALUES(2, "declan")`,
},
{
Sql: `INSERT INTO foo(id, name) VALUES(1, "fiona")`,
},
{
Sql: `INSERT INTO foo(id, name) VALUES(3, "dana")`,
},
},
Transaction: true,
}
r, err := db.Request(request, false)
if err != nil {
t.Fatalf("failed to make request: %s", err.Error())
}
if exp, got := `[{"last_insert_id":2,"rows_affected":1},{"error":"UNIQUE constraint failed: foo.id"}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for request\nexp: %s\ngot: %s", exp, got)
}
}
func testCommonTableExpressions(t *testing.T, db *DB) {
_, err := db.ExecuteStringStmt("CREATE TABLE test(x foo)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
_, err = db.ExecuteStringStmt(`INSERT INTO test VALUES(1)`)
if err != nil {
t.Fatalf("failed to insert record: %s", err.Error())
}
r, err := db.QueryStringStmt(`WITH bar AS (SELECT * FROM test) SELECT * FROM test WHERE x = 1`)
if err != nil {
t.Fatalf("failed to query a common table expression: %s", err.Error())
}
if exp, got := `[{"columns":["x"],"types":["foo"],"values":[[1]]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
r, err = db.QueryStringStmt(`WITH bar AS (SELECT * FROM test) SELECT * FROM test WHERE x = 2`)
if err != nil {
t.Fatalf("failed to query a common table expression: %s", err.Error())
}
if exp, got := `[{"columns":["x"],"types":["foo"]}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
func testUniqueConstraints(t *testing.T, db *DB) {
_, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT, CONSTRAINT name_unique UNIQUE (name))")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
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 INSERT\nexp: %s\ngot: %s", exp, got)
}
// UNIQUE constraint should fire.
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 := `[{"error":"UNIQUE constraint failed: foo.name"}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for INSERT\nexp: %s\ngot: %s", exp, got)
}
}
func testPartialFail(t *testing.T, db *DB) {
_, 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{
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"},{"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"],[4,"fiona"]]}]`, asJSON(ro); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
func testSerialize(t *testing.T, db *DB) {
_, 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())
}
dstDB, err := os.CreateTemp("", "rqlite-bak-")
if err != nil {
t.Fatalf("failed to create temp file: %s", err.Error())
}
dstDB.Close()
defer os.Remove(dstDB.Name())
// Get the bytes, and write to a temp file.
b, err := db.Serialize()
if err != nil {
t.Fatalf("failed to serialize database: %s", err.Error())
}
if !IsDELETEModeEnabled(b) {
t.Fatalf("expected DELETE mode to be enabled")
}
err = os.WriteFile(dstDB.Name(), b, 0644)
if err != nil {
t.Fatalf("failed to write serialized database to file: %s", err.Error())
}
newDB, err := Open(dstDB.Name(), false, false)
if err != nil {
t.Fatalf("failed to open on-disk serialized database: %s", err.Error())
}
defer newDB.Close()
defer os.Remove(dstDB.Name())
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)
}
}
func testDump(t *testing.T, db *DB) {
expRows := `[{"columns":["COUNT(*)"],"types":["integer"],"values":[[347]]}]`
_, err := db.ExecuteStringStmt(chinook.DB)
if err != nil {
t.Fatalf("failed to load chinook dump: %s", err.Error())
}
r, err := db.QueryStringStmt(`SELECT COUNT(*) FROM Album`)
if err != nil {
t.Fatalf("failed to count rows: %s", err.Error())
}
if exp, got := expRows, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
var b strings.Builder
if err := db.Dump(&b); err != nil {
t.Fatalf("failed to dump database: %s", err.Error())
}
newDB, newDBPath := mustCreateOnDiskDatabase()
10 months ago
defer newDB.Close()
defer os.Remove(newDBPath)
if _, err := newDB.ExecuteStringStmt(b.String()); err != nil {
t.Fatalf("failed to load dumped database into new database: %s", err.Error())
}
r, err = newDB.QueryStringStmt(`SELECT COUNT(*) FROM Album`)
if err != nil {
t.Fatalf("failed to count rows in new database: %s", err.Error())
}
if exp, got := expRows, asJSON(r); exp != got {
t.Fatalf("unexpected results for query of new database\nexp: %s\ngot: %s", exp, got)
}
}
func testSize(t *testing.T, db *DB) {
if _, err := db.Size(); err != nil {
t.Fatalf("failed to read database size: %s", err)
}
}
func testDBFileSize(t *testing.T, db *DB) {
if _, err := db.FileSize(); err != nil {
t.Fatalf("failed to read database file size: %s", err)
}
}
func testDBWALSize(t *testing.T, db *DB) {
if _, err := db.WALSize(); err != nil {
t.Fatalf("failed to read database WAL file size: %s", err)
}
}
func testStmtReadOnly(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\nexp: %s\ngot: %s", exp, got)
}
tests := []struct {
name string
sql string
ro bool
err error
}{
{
name: "CREATE TABLE statement",
sql: "CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)",
err: errors.New(`table foo already exists`),
},
{
name: "CREATE TABLE statement",
sql: "CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT)",
ro: false,
},
{
name: "SELECT statement",
sql: "SELECT * FROM foo",
ro: true,
},
{
name: "SELECT statement",
sql: "SELECT * FROM non_existent_table",
err: errors.New(`no such table: non_existent_table`),
},
{
name: "INSERT statement",
sql: "INSERT INTO foo VALUES (1, 'test')",
ro: false,
},
{
name: "UPDATE statement",
sql: "UPDATE foo SET name='test' WHERE id=1",
ro: false,
},
{
name: "DELETE statement",
sql: "DELETE FROM foo WHERE id=1",
ro: false,
},
{
name: "SELECT statement with positional parameters",
sql: "SELECT * FROM foo WHERE id = ?",
ro: true,
},
{
name: "SELECT statement with named parameters",
sql: "SELECT * FROM foo WHERE id = @id AND name = @name",
ro: true,
},
{
name: "INSERT statement with positional parameters",
sql: "INSERT INTO foo VALUES (?, ?)",
ro: false,
},
{
name: "INSERT statement with named parameters",
sql: "INSERT INTO foo VALUES (@id, @name)",
ro: false,
},
{
name: "WITH clause, read-only",
sql: "WITH bar AS (SELECT * FROM foo WHERE id = ?) SELECT * FROM bar",
ro: true,
},
{
name: "WITH clause, not read-only",
sql: "WITH bar AS (SELECT * FROM foo WHERE id = ?) DELETE FROM foo WHERE id IN (SELECT id FROM bar)",
ro: false,
},
{
name: "Invalid statement",
sql: "INVALID SQL STATEMENT",
err: errors.New(`near "INVALID": syntax error`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
readOnly, err := db.StmtReadOnly(tt.sql)
// Check if error is as expected
if err != nil && tt.err == nil {
t.Fatalf("unexpected error: got %v", err)
} else if err == nil && tt.err != nil {
t.Fatalf("expected error: got nil")
} else if err != nil && tt.err != nil && err.Error() != tt.err.Error() {
t.Fatalf("unexpected error: expected %v, got %v", tt.err, err)
}
// Check if result is as expected
if readOnly != tt.ro {
t.Fatalf("unexpected readOnly: expected %v, got %v", tt.ro, readOnly)
}
})
}
}
func testJSON1(t *testing.T, db *DB) {
_, err := db.ExecuteStringStmt("CREATE TABLE customer(name,phone)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
_, err = db.ExecuteStringStmt(`INSERT INTO customer (name, phone) VALUES("fiona", json('{"mobile":"789111", "home":"123456"}'))`)
if err != nil {
t.Fatalf("failed to insert JSON record: %s", err.Error())
}
q, err := db.QueryStringStmt("SELECT customer.phone FROM customer WHERE customer.name=='fiona'")
if err != nil {
t.Fatalf("failed to perform simple SELECT: %s", err.Error())
}
if exp, got := `[{"columns":["phone"],"types":["text"],"values":[["{\"mobile\":\"789111\",\"home\":\"123456\"}"]]}]`, asJSON(q); exp != got {
t.Fatalf("unexpected results for simple query, expected %s, got %s", exp, got)
}
q, err = db.QueryStringStmt("SELECT json_extract(customer.phone, '$.mobile') FROM customer")
if err != nil {
t.Fatalf("failed to perform simple SELECT: %s", err.Error())
}
if exp, got := `[{"columns":["json_extract(customer.phone, '$.mobile')"],"types":["text"],"values":[["789111"]]}]`, asJSON(q); exp != got {
t.Fatalf("unexpected results for JSON query, expected %s, got %s", exp, got)
}
q, err = db.QueryStringStmt("SELECT customer.phone ->> '$.mobile' FROM customer")
if err != nil {
t.Fatalf("failed to perform simple SELECT: %s", err.Error())
}
if exp, got := `[{"columns":["customer.phone ->> '$.mobile'"],"types":["text"],"values":[["789111"]]}]`, asJSON(q); exp != got {
t.Fatalf("unexpected results for JSON query, expected %s, got %s", exp, got)
}
}
func testDBSTAT_table(t *testing.T, db *DB) {
_, 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())
}
q, err := db.QueryStringStmt("SELECT * FROM dbstat")
if err != nil {
t.Fatalf("failed to query empty table: %s", err.Error())
}
if exp, got := `["name","path","pageno","pagetype","ncell","payload","unused","mx_payload","pgoffset","pgsize"]`, asJSON(q[0].Columns); exp != got {
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
}
}
func testCopy(t *testing.T, db *DB) {
_, 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())
}
dstFile := mustTempFile()
defer os.Remove(dstFile)
dstDB, err := Open(dstFile, false, false)
if err != nil {
t.Fatalf("failed to open destination database: %s", err)
}
defer dstDB.Close()
err = db.Copy(dstDB)
if err != nil {
t.Fatalf("failed to copy database: %s", err.Error())
}
if !IsDELETEModeEnabledSQLiteFile(dstFile) {
t.Fatalf("Destination file not marked in DELETE mode")
}
ro, err := dstDB.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 testBackup(t *testing.T, db *DB) {
for _, vacuum := range []bool{false, true} {
_, 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())
}
_, err = db.ExecuteStringStmt("CREATE TABLE baz (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())
}
dstDB := mustTempFile()
defer os.Remove(dstDB)
err = db.Backup(dstDB, vacuum)
if err != nil {
t.Fatalf("failed to backup database: %s", err.Error())
}
if !IsDELETEModeEnabledSQLiteFile(dstDB) {
t.Fatalf("Backup file not marked in DELETE mode")
}
newDB, err := Open(dstDB, false, false)
if err != nil {
t.Fatalf("failed to open backup database: %s", err.Error())
}
defer newDB.Close()
defer os.Remove(dstDB)
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)
}
ro, err = newDB.QueryStringStmt(`SELECT name FROM sqlite_master`)
if err != nil {
t.Fatalf("failed to query table: %s", err.Error())
}
if exp, got := `[{"columns":["name"],"types":["text"],"values":[["foo"],["baz"]]}]`, asJSON(ro); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
}
func Test_DatabaseCommonOperations(t *testing.T) {
testCases := []struct {
name string
testFunc func(*testing.T, *DB)
}{
{"BusyTimeout", testBusyTimeout},
{"SetSynchronousMode", testSetSynchronousMode},
{"CompileOptions", testCompileOptions},
{"TableNotExist", testTableNotExist},
{"TableCreation", testTableCreation},
{"TableCreationFTS", testTableCreationFTS},
{"SQLiteMasterTable", testSQLiteMasterTable},
{"SQLiteTimeTypes", testSQLiteTimeTypes},
{"NotNULLField", testNotNULLField},
{"RandomBlob", testSQLiteRandomBlob},
{"EmptyStatements", testEmptyStatements},
{"SimpleSingleStatements", testSimpleSingleStatements},
{"SimpleStatementsNumeric", testSimpleStatementsNumeric},
{"SimpleStatementsCollate", testSimpleStatementsCollate},
{"SimpleExpressionStatements", testSimpleExpressionStatements},
{"SimpleSingleJSONStatements", testSimpleSingleJSONStatements},
8 months ago
{"SimpleSingleJSONBStatements", testSimpleSingleJSONBStatements},
{"SimpleJoinStatements", testSimpleJoinStatements},
{"SimpleSingleConcatStatements", testSimpleSingleConcatStatements},
{"SimpleMultiStatements", testSimpleMultiStatements},
{"SimpleSingleMultiLineStatements", testSimpleSingleMultiLineStatements},
{"SimpleFailingStatements_Execute", testSimpleFailingStatements_Execute},
{"SimpleFailingStatements_Query", testSimpleFailingStatements_Query},
{"SimplePragmaTableInfo", testSimplePragmaTableInfo},
{"WriteOnQueryDatabaseShouldFail", testWriteOnQueryDatabaseShouldFail},
{"SimpleParameterizedStatements", testSimpleParameterizedStatements},
{"SimpleTwoParameterizedStatements", testSimpleTwoParameterizedStatements},
{"SimpleNilParameterizedStatements", testSimpleNilParameterizedStatements},
{"SimpleNamedParameterizedStatements", testSimpleNamedParameterizedStatements},
{"SimpleRequest", testSimpleRequest},
{"SimpleRequestTx", testSimpleRequestTx},
{"CommonTableExpressions", testCommonTableExpressions},
{"UniqueConstraints", testUniqueConstraints},
{"PartialFail", testPartialFail},
{"Serialize", testSerialize},
{"Dump", testDump},
{"Size", testSize},
{"DBFileSize", testDBFileSize},
{"DBWALSize", testDBWALSize},
{"StmtReadOnly", testStmtReadOnly},
{"JSON1", testJSON1},
{"DBSTAT_table", testDBSTAT_table},
{"Copy", testCopy},
{"Backup", testBackup},
}
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)
})
}
}