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.
298 lines
8.7 KiB
Go
298 lines
8.7 KiB
Go
1 year ago
|
package db
|
||
|
|
||
|
import (
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"sync"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/rqlite/rqlite/command"
|
||
|
)
|
||
|
|
||
|
// Test_TableCreationInMemory tests basic operation of an in-memory database,
|
||
|
// ensuring that using different connection objects (as the Execute and Query
|
||
|
// will do) works properly i.e. that the connections object work on the same
|
||
|
// in-memory database.
|
||
|
func Test_TableCreationInMemory(t *testing.T) {
|
||
|
db := mustCreateInMemoryDatabase()
|
||
|
defer db.Close()
|
||
|
|
||
|
if !db.InMemory() {
|
||
|
t.Fatal("in-memory database marked as not in-memory")
|
||
|
}
|
||
|
|
||
|
r, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to create table: %s", err.Error())
|
||
|
}
|
||
|
if exp, got := `[{}]`, asJSON(r); exp != got {
|
||
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
||
|
}
|
||
|
|
||
|
q, err := db.QueryStringStmt("SELECT * FROM foo")
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to query empty table: %s", err.Error())
|
||
|
}
|
||
|
if exp, got := `[{"columns":["id","name"],"types":["integer","text"]}]`, asJSON(q); exp != got {
|
||
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Test_LoadIntoMemory(t *testing.T) {
|
||
|
db, path := mustCreateDatabase()
|
||
|
defer db.Close()
|
||
|
defer os.Remove(path)
|
||
|
|
||
|
_, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to create table: %s", err.Error())
|
||
|
}
|
||
|
|
||
|
r, err := db.QueryStringStmt("SELECT * FROM foo")
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to query empty table: %s", err.Error())
|
||
|
}
|
||
|
if exp, got := `[{"columns":["id","name"],"types":["integer","text"]}]`, asJSON(r); exp != got {
|
||
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
||
|
}
|
||
|
|
||
|
inmem, err := LoadIntoMemory(path, false)
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to create loaded in-memory database: %s", err.Error())
|
||
|
}
|
||
|
|
||
|
// Ensure it has been loaded correctly into the database
|
||
|
r, err = inmem.QueryStringStmt("SELECT * FROM foo")
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to query empty table: %s", err.Error())
|
||
|
}
|
||
|
if exp, got := `[{"columns":["id","name"],"types":["integer","text"]}]`, asJSON(r); exp != got {
|
||
|
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Test_DeserializeIntoMemory(t *testing.T) {
|
||
|
db, path := mustCreateDatabase()
|
||
|
defer db.Close()
|
||
|
defer os.Remove(path)
|
||
|
|
||
|
_, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to create table: %s", err.Error())
|
||
|
}
|
||
|
|
||
|
req := &command.Request{
|
||
|
Transaction: true,
|
||
|
Statements: []*command.Statement{
|
||
|
{
|
||
|
Sql: `INSERT INTO foo(id, name) VALUES(1, "fiona")`,
|
||
|
},
|
||
|
{
|
||
|
Sql: `INSERT INTO foo(id, name) VALUES(2, "fiona")`,
|
||
|
},
|
||
|
{
|
||
|
Sql: `INSERT INTO foo(id, name) VALUES(3, "fiona")`,
|
||
|
},
|
||
|
{
|
||
|
Sql: `INSERT INTO foo(id, name) VALUES(4, "fiona")`,
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
_, err = db.Execute(req, false)
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to insert records: %s", err.Error())
|
||
|
}
|
||
|
|
||
|
// Get byte representation of database on disk which, according to SQLite docs
|
||
|
// is the same as a serialized version.
|
||
|
b, err := ioutil.ReadFile(path)
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to read database on disk: %s", err.Error())
|
||
|
}
|
||
|
|
||
|
newDB, err := DeserializeIntoMemory(b, false)
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to deserialize database: %s", err.Error())
|
||
|
}
|
||
|
defer newDB.Close()
|
||
|
|
||
|
ro, err := newDB.QueryStringStmt(`SELECT * FROM foo`)
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to query table: %s", err.Error())
|
||
|
}
|
||
|
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"],[2,"fiona"],[3,"fiona"],[4,"fiona"]]}]`, asJSON(ro); exp != got {
|
||
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
||
|
}
|
||
|
|
||
|
// Write a lot of records to the new database, to ensure it's fully functional.
|
||
|
req = &command.Request{
|
||
|
Statements: []*command.Statement{
|
||
|
{
|
||
|
Sql: `INSERT INTO foo(name) VALUES("fiona")`,
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
for i := 0; i < 5000; i++ {
|
||
|
_, err = newDB.Execute(req, false)
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to insert records: %s", err.Error())
|
||
|
}
|
||
|
}
|
||
|
ro, err = newDB.QueryStringStmt(`SELECT COUNT(*) FROM foo`)
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to query table: %s", err.Error())
|
||
|
}
|
||
|
if exp, got := `[{"columns":["COUNT(*)"],"types":[""],"values":[[5004]]}]`, asJSON(ro); exp != got {
|
||
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Test_ParallelOperationsInMemory runs multiple accesses concurrently, ensuring
|
||
|
// that correct results are returned in every goroutine. It's not 100% that this
|
||
|
// test would bring out a bug, but it's almost 100%.
|
||
|
//
|
||
|
// See https://github.com/mattn/go-sqlite3/issues/959#issuecomment-890283264
|
||
|
func Test_ParallelOperationsInMemory(t *testing.T) {
|
||
|
db := mustCreateInMemoryDatabase()
|
||
|
defer db.Close()
|
||
|
|
||
|
if _, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"); err != nil {
|
||
|
t.Fatalf("failed to create table: %s", err.Error())
|
||
|
}
|
||
|
|
||
|
if _, err := db.ExecuteStringStmt("CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"); err != nil {
|
||
|
t.Fatalf("failed to create table: %s", err.Error())
|
||
|
}
|
||
|
|
||
|
if _, err := db.ExecuteStringStmt("CREATE TABLE qux (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"); err != nil {
|
||
|
t.Fatalf("failed to create table: %s", err.Error())
|
||
|
}
|
||
|
|
||
|
// Confirm schema is as expected, when checked from same goroutine.
|
||
|
if rows, err := db.QueryStringStmt(`SELECT sql FROM sqlite_master`); err != nil {
|
||
|
t.Fatalf("failed to query for schema after creation: %s", err.Error())
|
||
|
} else {
|
||
|
if exp, got := `[{"columns":["sql"],"types":["text"],"values":[["CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE qux (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"]]}]`, asJSON(rows); exp != got {
|
||
|
t.Fatalf("schema not as expected during after creation, exp %s, got %s", exp, got)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var exWg sync.WaitGroup
|
||
|
exWg.Add(3)
|
||
|
|
||
|
foo := make(chan time.Time)
|
||
|
bar := make(chan time.Time)
|
||
|
qux := make(chan time.Time)
|
||
|
done := make(chan bool)
|
||
|
|
||
|
ticker := time.NewTicker(1 * time.Millisecond)
|
||
|
go func() {
|
||
|
for {
|
||
|
select {
|
||
|
case t := <-ticker.C:
|
||
|
foo <- t
|
||
|
bar <- t
|
||
|
qux <- t
|
||
|
case <-done:
|
||
|
close(foo)
|
||
|
close(bar)
|
||
|
close(qux)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
go func() {
|
||
|
defer exWg.Done()
|
||
|
for range foo {
|
||
|
if _, err := db.ExecuteStringStmt(`INSERT INTO foo(id, name) VALUES(1, "fiona")`); err != nil {
|
||
|
t.Logf("failed to insert records into foo: %s", err.Error())
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
go func() {
|
||
|
defer exWg.Done()
|
||
|
for range bar {
|
||
|
if _, err := db.ExecuteStringStmt(`INSERT INTO bar(id, name) VALUES(1, "fiona")`); err != nil {
|
||
|
t.Logf("failed to insert records into bar: %s", err.Error())
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
go func() {
|
||
|
defer exWg.Done()
|
||
|
for range qux {
|
||
|
if _, err := db.ExecuteStringStmt(`INSERT INTO qux(id, name) VALUES(1, "fiona")`); err != nil {
|
||
|
t.Logf("failed to insert records into qux: %s", err.Error())
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
var qWg sync.WaitGroup
|
||
|
qWg.Add(3)
|
||
|
for i := 0; i < 3; i++ {
|
||
|
go func(j int) {
|
||
|
defer qWg.Done()
|
||
|
var n int
|
||
|
for {
|
||
|
if rows, err := db.QueryStringStmt(`SELECT sql FROM sqlite_master`); err != nil {
|
||
|
t.Logf("failed to query for schema during goroutine %d execution: %s", j, err.Error())
|
||
|
} else {
|
||
|
n++
|
||
|
if exp, got := `[{"columns":["sql"],"types":["text"],"values":[["CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE qux (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"]]}]`, asJSON(rows); exp != got {
|
||
|
t.Logf("schema not as expected during goroutine execution, exp %s, got %s, after %d queries", exp, got, n)
|
||
|
}
|
||
|
}
|
||
|
if n == 500000 {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}(i)
|
||
|
}
|
||
|
qWg.Wait()
|
||
|
|
||
|
close(done)
|
||
|
exWg.Wait()
|
||
|
}
|
||
|
|
||
|
// Test_TableCreationLoadRawInMemory tests for https://sqlite.org/forum/forumpost/d443fb0730
|
||
|
func Test_TableCreationLoadRawInMemory(t *testing.T) {
|
||
|
db := mustCreateInMemoryDatabase()
|
||
|
defer db.Close()
|
||
|
|
||
|
_, err := db.ExecuteStringStmt("CREATE TABLE logs (entry TEXT)")
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to create table: %s", err.Error())
|
||
|
}
|
||
|
|
||
|
done := make(chan struct{})
|
||
|
defer close(done)
|
||
|
|
||
|
// Insert some records continually, as fast as possible. Do it from a goroutine.
|
||
|
go func() {
|
||
|
for {
|
||
|
select {
|
||
|
case <-done:
|
||
|
return
|
||
|
default:
|
||
|
_, err := db.ExecuteStringStmt(`INSERT INTO logs(entry) VALUES("hello")`)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
// Get the count over and over again.
|
||
|
for i := 0; i < 5000; i++ {
|
||
|
rows, err := db.QueryStringStmt(`SELECT COUNT(*) FROM logs`)
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to query for count: %s", err)
|
||
|
}
|
||
|
|
||
|
if rows[0].Error != "" {
|
||
|
t.Fatalf("rows had error after %d queries: %s", i, rows[0].Error)
|
||
|
}
|
||
|
}
|
||
|
}
|