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.

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)
}
}