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.
347 lines
10 KiB
Go
347 lines
10 KiB
Go
package db
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rqlite/rqlite/command"
|
|
text "github.com/rqlite/rqlite/db/testdata"
|
|
)
|
|
|
|
// 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)
|
|
}
|
|
|
|
// Confirm checkpoint works without error on an in-memory database. It should just be ignored.
|
|
if err := db.Checkpoint(5 * time.Second); err != nil {
|
|
t.Fatalf("failed to checkpoint in-memory database: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
func Test_LoadIntoMemory(t *testing.T) {
|
|
db, path := mustCreateOnDiskDatabase()
|
|
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, 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 := mustCreateOnDiskDatabase()
|
|
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":["integer"],"values":[[5004]]}]`, asJSON(ro); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
}
|
|
|
|
// Test_ParallelOperationsInMemory runs multiple accesses concurrently, ensuring
|
|
// that correct results are returned in every goroutine. It's not 100% that this
|
|
// test would bring out a bug, but it's almost 100%.
|
|
//
|
|
// See https://github.com/mattn/go-sqlite3/issues/959#issuecomment-890283264
|
|
func Test_ParallelOperationsInMemory(t *testing.T) {
|
|
db := mustCreateInMemoryDatabase()
|
|
defer db.Close()
|
|
|
|
if _, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"); err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
|
|
if _, err := db.ExecuteStringStmt("CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"); err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
|
|
if _, err := db.ExecuteStringStmt("CREATE TABLE qux (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"); err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
|
|
// Confirm schema is as expected, when checked from same goroutine.
|
|
if rows, err := db.QueryStringStmt(`SELECT sql FROM sqlite_master`); err != nil {
|
|
t.Fatalf("failed to query for schema after creation: %s", err.Error())
|
|
} else {
|
|
if exp, got := `[{"columns":["sql"],"types":["text"],"values":[["CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE qux (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"]]}]`, asJSON(rows); exp != got {
|
|
t.Fatalf("schema not as expected during after creation, exp %s, got %s", exp, got)
|
|
}
|
|
}
|
|
|
|
var exWg sync.WaitGroup
|
|
exWg.Add(3)
|
|
|
|
foo := make(chan time.Time)
|
|
bar := make(chan time.Time)
|
|
qux := make(chan time.Time)
|
|
done := make(chan bool)
|
|
|
|
ticker := time.NewTicker(1 * time.Millisecond)
|
|
go func() {
|
|
for {
|
|
select {
|
|
case t := <-ticker.C:
|
|
foo <- t
|
|
bar <- t
|
|
qux <- t
|
|
case <-done:
|
|
close(foo)
|
|
close(bar)
|
|
close(qux)
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
defer exWg.Done()
|
|
for range foo {
|
|
if _, err := db.ExecuteStringStmt(`INSERT INTO foo(id, name) VALUES(1, "fiona")`); err != nil {
|
|
t.Logf("failed to insert records into foo: %s", err.Error())
|
|
}
|
|
}
|
|
}()
|
|
go func() {
|
|
defer exWg.Done()
|
|
for range bar {
|
|
if _, err := db.ExecuteStringStmt(`INSERT INTO bar(id, name) VALUES(1, "fiona")`); err != nil {
|
|
t.Logf("failed to insert records into bar: %s", err.Error())
|
|
}
|
|
}
|
|
}()
|
|
go func() {
|
|
defer exWg.Done()
|
|
for range qux {
|
|
if _, err := db.ExecuteStringStmt(`INSERT INTO qux(id, name) VALUES(1, "fiona")`); err != nil {
|
|
t.Logf("failed to insert records into qux: %s", err.Error())
|
|
}
|
|
}
|
|
}()
|
|
|
|
var qWg sync.WaitGroup
|
|
qWg.Add(3)
|
|
for i := 0; i < 3; i++ {
|
|
go func(j int) {
|
|
defer qWg.Done()
|
|
var n int
|
|
for {
|
|
if rows, err := db.QueryStringStmt(`SELECT sql FROM sqlite_master`); err != nil {
|
|
t.Logf("failed to query for schema during goroutine %d execution: %s", j, err.Error())
|
|
} else {
|
|
n++
|
|
if exp, got := `[{"columns":["sql"],"types":["text"],"values":[["CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE qux (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"]]}]`, asJSON(rows); exp != got {
|
|
t.Logf("schema not as expected during goroutine execution, exp %s, got %s, after %d queries", exp, got, n)
|
|
}
|
|
}
|
|
if n == 500000 {
|
|
break
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
qWg.Wait()
|
|
|
|
close(done)
|
|
exWg.Wait()
|
|
}
|
|
|
|
// Test_TableCreationLoadRawInMemory tests for https://sqlite.org/forum/forumpost/d443fb0730
|
|
func Test_TableCreationLoadRawInMemory(t *testing.T) {
|
|
db := mustCreateInMemoryDatabase()
|
|
defer db.Close()
|
|
|
|
_, err := db.ExecuteStringStmt("CREATE TABLE logs (entry TEXT)")
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
|
|
done := make(chan struct{})
|
|
defer close(done)
|
|
|
|
// Insert some records continually, as fast as possible. Do it from a goroutine.
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
default:
|
|
_, err := db.ExecuteStringStmt(`INSERT INTO logs(entry) VALUES("hello")`)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Get the count over and over again.
|
|
for i := 0; i < 5000; i++ {
|
|
rows, err := db.QueryStringStmt(`SELECT COUNT(*) FROM logs`)
|
|
if err != nil {
|
|
t.Fatalf("failed to query for count: %s", err)
|
|
}
|
|
|
|
if rows[0].Error != "" {
|
|
t.Fatalf("rows had error after %d queries: %s", i, rows[0].Error)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|