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"
text "github.com/rqlite/rqlite/db/testdata"
"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 ( )
defer os . Remove ( path )
_ , err := db . CompileOptions ( )
if err != nil {
t . Fatalf ( "failed to retrieve compilation options: %s" , err . Error ( ) )
}
}
func Test_TableNotExist ( t * testing . T ) {
db , path := mustCreateDatabase ( )
defer db . Close ( )
defer os . Remove ( path )
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 Test_TableCreation ( t * testing . T ) {
db , path := mustCreateDatabase ( )
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 )
}
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_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)"
createTableBar := "CREATE TABLE bar (fooid INTEGER NOT NULL PRIMARY KEY, FOREIGN KEY(fooid) REFERENCES foo(id))"
insertIntoBar := "INSERT INTO bar(fooid) VALUES(1)"
db := mustCreateInMemoryDatabase ( )
defer db . Close ( )
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 := mustCreateInMemoryDatabaseFK ( )
defer dbFK . Close ( )
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_SQLiteMasterTable ( 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 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 Test_SQLiteTimeTypes ( t * testing . T ) {
db , path := mustCreateDatabase ( )
defer db . Close ( )
defer os . Remove ( path )
_ , 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 Test_NotNULLField ( 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 ( ` PRAGMA table_info("foo") ` )
if err != nil {
t . Fatalf ( "failed to get PRAGMA table_info: %s" , err . Error ( ) )
}
if exp , got := ` [ { "columns":["cid","name","type","notnull","dflt_value","pk"],"types":["","","","","",""],"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 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 ( )
defer os . Remove ( path )
_ , 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 Test_SimpleSingleStatements ( 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 ( ) )
}
_ , 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 )
}
}
func Test_SimpleSingleJSONStatements ( t * testing . T ) {
db , path := mustCreateDatabase ( )
defer db . Close ( )
defer os . Remove ( path )
_ , 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 )
}
}
func Test_SimpleJoinStatements ( t * testing . T ) {
db , path := mustCreateDatabase ( )
defer db . Close ( )
defer os . Remove ( path )
_ , 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 Test_SimpleSingleConcatStatements ( 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 ( ) )
}
_ , 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"],"values":[["1_bar","fiona"]]}] ` , asJSON ( r ) ; exp != got {
t . Fatalf ( "unexpected results for query\nexp: %s\ngot: %s" , exp , got )
}
}
func Test_SimpleMultiStatements ( 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 {
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 Test_SimpleSingleMultiLineStatements ( t * testing . T ) {
db , path := mustCreateDatabase ( )
defer db . Close ( )
defer os . Remove ( path )
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 Test_SimpleFailingStatements_Execute ( t * testing . T ) {
db , path := mustCreateDatabase ( )
defer db . Close ( )
defer os . Remove ( path )
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 Test_SimpleFailingStatements_Query ( t * testing . T ) {
db , path := mustCreateDatabase ( )
defer db . Close ( )
defer os . Remove ( path )
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 Test_SimplePragmaTableInfo ( t * testing . T ) {
db , path := mustCreateDatabase ( )
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 )
}
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":["","","","","",""],"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 Test_WriteOnQueryOnDiskDatabaseShouldFail ( t * testing . T ) {
db , path := mustCreateDatabase ( )
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 )
}
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":[""],"values":[[1]]}] ` , asJSON ( ro ) ; exp != got {
t . Fatalf ( "unexpected results for query\nexp: %s\ngot: %s" , exp , got )
}
}
func Test_WriteOnQueryInMemDatabaseShouldFail ( t * testing . T ) {
db := mustCreateInMemoryDatabase ( )
defer db . Close ( )
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":[""],"values":[[1]]}] ` , asJSON ( ro ) ; exp != got {
t . Fatalf ( "unexpected results for query\nexp: %s\ngot: %s" , exp , got )
}
}
func Test_ConcurrentQueriesInMemory ( t * testing . T ) {
db := mustCreateInMemoryDatabase ( )
defer db . Close ( )
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":[""],"values":[[5000]]}] ` , asJSON ( ro ) ; exp != got {
t . Logf ( "unexpected results for query\nexp: %s\ngot: %s" , exp , got )
}
} ( )
}
wg . Wait ( )
}
func Test_SimpleParameterizedStatements ( 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 {
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 Test_SimpleTwoParameterizedStatements ( 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, 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 Test_SimpleNilParameterizedStatements ( 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, 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 Test_SimpleNamedParameterizedStatements ( 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, 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 Test_SimpleRequest ( 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, 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":[""],"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":[""],"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.
func Test_SimpleRequestTx ( t * testing . T ) {
db , path := mustCreateDatabase ( )
defer db . Close ( )
defer os . Remove ( path )
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 Test_CommonTableExpressions ( t * testing . T ) {
db , path := mustCreateDatabase ( )
defer db . Close ( )
defer os . Remove ( path )
_ , 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 Test_UniqueConstraints ( 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, 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 )
}
}
// Test_ConnectionIsolation test that ISOLATION behavior of on-disk databases doesn't
// change unexpectedly.
func Test_ConnectionIsolation ( t * testing . T ) {
db , path := mustCreateDatabase ( )
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 )
}
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 )
}
}
// Test_ConnectionIsolationMemory test that ISOLATION behavior of in-memory databases doesn't
// change unexpectedly.
func Test_ConnectionIsolationMemory ( t * testing . T ) {
db := mustCreateInMemoryDatabase ( )
defer db . Close ( )
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 )
}
_ , err = db . ExecuteStringStmt ( ` INSERT INTO foo(name) VALUES("fiona") ` )
if err != nil {
t . Fatalf ( "error executing insertion into table: %s" , err . Error ( ) )
}
_ , err = db . QueryStringStmt ( "SELECT * FROM foo" )
if err == nil {
t . Fatalf ( "SELECT should have received error" )
}
if exp , got := "database is locked" , err . Error ( ) ; exp != got {
t . Fatalf ( "error incorrect, exp %s, got %s" , exp , got )
}
}
func Test_Size ( t * testing . T ) {
db , path := mustCreateDatabase ( )
defer db . Close ( )
defer os . Remove ( path )
if _ , err := db . Size ( ) ; err != nil {
t . Fatalf ( "failed to read database size: %s" , err )
}
}
func Test_DBFileSize ( t * testing . T ) {
db , path := mustCreateDatabase ( )
defer db . Close ( )
defer os . Remove ( path )
if _ , err := db . FileSize ( ) ; err != nil {
t . Fatalf ( "failed to read database file size: %s" , err )
}
}
func Test_PartialFail ( 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 {
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 Test_SimpleTransaction ( 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") ` ,
} ,
} ,
}
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 := 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(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_Backup ( 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 ( ) )
}
dstDB := mustTempFile ( )
defer os . Remove ( dstDB )
err = db . Backup ( dstDB )
if err != nil {
t . Fatalf ( "failed to backup database: %s" , err . Error ( ) )
}
newDB , err := Open ( dstDB , 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 )
}
}
func Test_Copy ( t * testing . T ) {
srcDB , path := mustCreateDatabase ( )
defer srcDB . Close ( )
defer os . Remove ( path )
_ , err := srcDB . 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 = srcDB . 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 )
if err != nil {
t . Fatalf ( "failed to open destination database: %s" , err )
}
defer dstDB . Close ( )
err = srcDB . Copy ( dstDB )
if err != nil {
t . Fatalf ( "failed to copy database: %s" , err . Error ( ) )
}
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 Test_SerializeOnDisk ( 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 ( ) )
}
dstDB , err := ioutil . TempFile ( "" , "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 ( ) )
}
err = ioutil . 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 )
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 Test_SerializeInMemory ( t * testing . T ) {
db := mustCreateInMemoryDatabase ( )
defer db . Close ( )
_ , 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 := ioutil . TempFile ( "" , "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 ( ) )
}
err = ioutil . 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 )
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 Test_Dump ( t * testing . T ) {
db , path := mustCreateDatabase ( )
defer db . Close ( )
defer os . Remove ( path )
_ , err := db . ExecuteStringStmt ( chinook . DB )
if err != nil {
t . Fatalf ( "failed to load chinook dump: %s" , err . Error ( ) )
}
var b strings . Builder
if err := db . Dump ( & b ) ; err != nil {
t . Fatalf ( "failed to dump database: %s" , err . Error ( ) )
}
if b . String ( ) != chinook . DB {
t . Fatal ( "dumped database does not equal entered database" )
}
}
func Test_DumpMemory ( t * testing . T ) {
db , path := mustCreateDatabase ( )
defer db . Close ( )
defer os . Remove ( path )
inmem , err := LoadIntoMemory ( path , false )
if err != nil {
t . Fatalf ( "failed to create loaded in-memory database: %s" , err . Error ( ) )
}
_ , err = inmem . ExecuteStringStmt ( chinook . DB )
if err != nil {
t . Fatalf ( "failed to load chinook dump: %s" , err . Error ( ) )
}
var b strings . Builder
if err := inmem . Dump ( & b ) ; err != nil {
t . Fatalf ( "failed to dump database: %s" , err . Error ( ) )
}
if b . String ( ) != chinook . DB {
t . Fatal ( "dumped database does not equal entered database" )
}
}
// Test_1GiBInMemory tests that in-memory databases larger than 1GiB,
// but smaller than 2GiB, can be created without issue.
func Test_1GiBInMemory ( t * testing . T ) {
db := mustCreateInMemoryDatabase ( )
defer db . Close ( )
_ , err := db . ExecuteStringStmt ( "CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, txt TEXT)" )
if err != nil {
t . Fatalf ( "failed to create table: %s" , err . Error ( ) )
}
stmt := fmt . Sprintf ( ` INSERT INTO foo(txt) VALUES("%s") ` , text . Lorum )
for i := 0 ; i < 1715017 ; i ++ {
r , err := db . ExecuteStringStmt ( stmt )
if err != nil {
t . Fatalf ( "failed to Execute statement %s" , err . Error ( ) )
}
if len ( r ) != 1 {
t . Fatalf ( "unexpected length for Execute results: %d" , len ( r ) )
}
if r [ 0 ] . GetError ( ) != "" {
t . Fatalf ( "failed to insert record: %s" , r [ 0 ] . GetError ( ) )
}
}
r , err := db . ExecuteStringStmt ( stmt )
if err != nil {
t . Fatalf ( "failed to insert record %s" , err . Error ( ) )
}
if exp , got := ` [ { "last_insert_id":1715018,"rows_affected":1}] ` , asJSON ( r ) ; exp != got {
t . Fatalf ( "got incorrect response, exp: %s, got: %s" , exp , got )
}
sz , err := db . Size ( )
if err != nil {
t . Fatalf ( "failed to get size: %s" , err . Error ( ) )
}
if sz <= 1024 * 1024 * 1024 {
t . Fatalf ( "failed to create a database greater than 1 GiB in size: %d" , sz )
}
}
// 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 ( )
_ , 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 Test_JSON1 ( t * testing . T ) {
db := mustCreateInMemoryDatabase ( )
defer db . Close ( )
_ , 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":[""],"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":[""],"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":[""],"values":[["789111"]]}] ` , asJSON ( q ) ; exp != got {
t . Fatalf ( "unexpected results for JSON query, expected %s, got %s" , exp , got )
}
}
// 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 ( )
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 mustCreateDatabase ( ) ( * DB , string ) {
var err error
f := mustTempFile ( )
db , err := Open ( f , false )
if err != nil {
panic ( "failed to open database" )
}
return db , f
}
func mustCreateInMemoryDatabase ( ) * DB {
db , err := OpenInMemory ( false )
if err != nil {
panic ( "failed to open in-memory database" )
}
return db
}
func mustCreateInMemoryDatabaseFK ( ) * DB {
db , err := OpenInMemory ( true )
if err != nil {
panic ( "failed to open in-memory database with foreign key constraints" )
}
return db
}
// 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 )
}
// 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 := ioutil . TempFile ( "" , "rqlite-db-test" )
if err != nil {
panic ( err . Error ( ) )
}
tmpfile . Close ( )
return tmpfile . Name ( )
}