mirror of
https://github.com/chaisql/chai.git
synced 2025-10-04 23:32:52 +08:00
Move tests to query package
This commit is contained in:
389
db_test.go
389
db_test.go
@@ -1,8 +1,6 @@
|
||||
package genji_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
@@ -305,390 +303,3 @@ func ExampleResult_Iterate() {
|
||||
// {10 foo10 100}
|
||||
// 10 foo10 100
|
||||
}
|
||||
|
||||
func TestCreateTable(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
fails bool
|
||||
}{
|
||||
{"Basic", `CREATE TABLE test`, false},
|
||||
{"Exists", "CREATE TABLE test;CREATE TABLE test", true},
|
||||
{"If not exists", "CREATE TABLE IF NOT EXISTS test", false},
|
||||
{"If not exists, twice", "CREATE TABLE IF NOT EXISTS test;CREATE TABLE IF NOT EXISTS test", false},
|
||||
{"With primary key", "CREATE TABLE test(foo STRING PRIMARY KEY)", false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec(test.query)
|
||||
if test.fails {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.ViewTable("test", func(_ *genji.Tx, _ *database.Table) error {
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateIndex(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
fails bool
|
||||
}{
|
||||
{"Basic", "CREATE INDEX idx ON test (foo)", false},
|
||||
{"If not exists", "CREATE INDEX IF NOT EXISTS idx ON test (foo)", false},
|
||||
{"Unique", "CREATE UNIQUE INDEX IF NOT EXISTS idx ON test (foo)", false},
|
||||
{"No fields", "CREATE INDEX idx ON test", true},
|
||||
{"More than 1 field", "CREATE INDEX idx ON test (foo, bar)", true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec("CREATE TABLE test")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Exec(test.query)
|
||||
if test.fails {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDrop(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
fails bool
|
||||
}{
|
||||
{"Drop table", "DROP TABLE test", false},
|
||||
{"Drop table If not exists", "DROP TABLE IF EXISTS test", false},
|
||||
{"Drop index", "DROP INDEX idx", false},
|
||||
{"Drop index if exists", "DROP INDEX IF EXISTS idx", false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec("CREATE TABLE test; CREATE INDEX idx ON test (foo)")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Exec(test.query)
|
||||
if test.fails {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteStmt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
fails bool
|
||||
expected string
|
||||
params []interface{}
|
||||
}{
|
||||
{"No cond", `DELETE FROM test`, false, "", nil},
|
||||
{"With cond", "DELETE FROM test WHERE b = 'bar1'", false, "bar2,foo3,bar3\n", nil},
|
||||
{"Table not found", "DELETE FROM foo WHERE b = 'bar1'", true, "", nil},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec("CREATE TABLE test")
|
||||
require.NoError(t, err)
|
||||
err = db.Exec("INSERT INTO test (a, b, c) VALUES ('foo1', 'bar1', 'baz1')")
|
||||
require.NoError(t, err)
|
||||
err = db.Exec("INSERT INTO test (a, b) VALUES ('foo2', 'bar1')")
|
||||
require.NoError(t, err)
|
||||
err = db.Exec("INSERT INTO test (d, b, e) VALUES ('foo3', 'bar2', 'bar3')")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Exec(test.query, test.params...)
|
||||
if test.fails {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
st, err := db.Query("SELECT * FROM test")
|
||||
require.NoError(t, err)
|
||||
defer st.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = document.IteratorToCSV(&buf, st)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.expected, buf.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertStmt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
fails bool
|
||||
expected string
|
||||
params []interface{}
|
||||
}{
|
||||
{"Values / No columns", `INSERT INTO test VALUES ("a", 'b', 'c')`, true, ``, nil},
|
||||
{"Values / With columns", `INSERT INTO test (a, b, c) VALUES ('a', 'b', 'c')`, false, "1,a,b,c\n", nil},
|
||||
{"Values / Ident", `INSERT INTO test (a) VALUES (a)`, true, ``, nil},
|
||||
{"Values / Ident string", `INSERT INTO test (a) VALUES ("a")`, true, ``, nil},
|
||||
{"Values / With fields ident string", `INSERT INTO test (a, "foo bar") VALUES ('c', 'd')`, false, "1,c,d\n", nil},
|
||||
{"Values / Positional Params", "INSERT INTO test (a, b, c) VALUES (?, 'e', ?)", false, "1,d,e,f\n", []interface{}{"d", "f"}},
|
||||
{"Values / Named Params", "INSERT INTO test (a, b, c) VALUES ($d, 'e', $f)", false, "1,d,e,f\n", []interface{}{sql.Named("f", "f"), sql.Named("d", "d")}},
|
||||
{"Values / Invalid params", "INSERT INTO test (a, b, c) VALUES ('d', ?)", true, "", []interface{}{'e'}},
|
||||
{"Values / List", `INSERT INTO test (a, b, c) VALUES ("a", 'b', (1, 2, 3))`, true, "", nil},
|
||||
{"Records", "INSERT INTO test DOCUMENTS (a: 'a', b: 2.3, c: 1 = 1)", false, "1,a,2.3,true\n", nil},
|
||||
{"Records / Positional Params", "INSERT INTO test DOCUMENTS (a: ?, b: 2.3, c: ?)", false, "1,a,2.3,true\n", []interface{}{"a", true}},
|
||||
{"Records / Named Params", "INSERT INTO test DOCUMENTS (a: $a, b: 2.3, c: $c)", false, "1,1,2.3,true\n", []interface{}{sql.Named("c", true), sql.Named("a", 1)}},
|
||||
{"Records / List ", "INSERT INTO test DOCUMENTS (a: (1, 2, 3))", true, "", nil},
|
||||
{"Records / strings", `INSERT INTO test DOCUMENTS ('a': 'a', b: 2.3)`, true, "", nil},
|
||||
{"Records / ident value", `INSERT INTO test DOCUMENTS ("a": "a")`, true, "", nil},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
testFn := func(withIndexes bool) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec("CREATE TABLE test")
|
||||
require.NoError(t, err)
|
||||
if withIndexes {
|
||||
err = db.Exec(`
|
||||
CREATE INDEX idx_a ON test (a);
|
||||
CREATE INDEX idx_b ON test (b);
|
||||
CREATE INDEX idx_c ON test (c);
|
||||
`)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
err = db.Exec(test.query, test.params...)
|
||||
if test.fails {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
st, err := db.Query("SELECT key(), * FROM test")
|
||||
require.NoError(t, err)
|
||||
defer st.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = document.IteratorToCSV(&buf, st)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.expected, buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("No Index/"+test.name, testFn(false))
|
||||
t.Run("With Index/"+test.name, testFn(true))
|
||||
}
|
||||
|
||||
t.Run("with primary key", func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec("CREATE TABLE test (foo INTEGER PRIMARY KEY)")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Exec(`INSERT INTO test (bar) VALUES (1)`)
|
||||
require.Error(t, err)
|
||||
err = db.Exec(`INSERT INTO test (bar, foo) VALUES (1, 2)`)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Exec(`INSERT INTO test (bar, foo) VALUES (1, 2)`)
|
||||
require.Equal(t, err, database.ErrDuplicateRecord)
|
||||
})
|
||||
|
||||
t.Run("with shadowing", func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec("CREATE TABLE test")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Exec(`INSERT INTO test ("key()", "key") VALUES (1, 2)`)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSelectStmt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
fails bool
|
||||
expected string
|
||||
params []interface{}
|
||||
}{
|
||||
{"No cond", "SELECT * FROM test", false, "foo1,bar1,baz1,1\nfoo2,bar1,1,2\nfoo3,bar2,3\n", nil},
|
||||
{"Multiple wildcards cond", "SELECT *, *, a FROM test", false, "foo1,bar1,baz1,1,foo1,bar1,baz1,1,foo1\nfoo2,bar1,1,2,foo2,bar1,1,2,foo2\nfoo3,bar2,3,foo3,bar2,3\n", nil},
|
||||
{"With fields", "SELECT a, c FROM test", false, "foo1,baz1\nfoo2\n\n", nil},
|
||||
{"With eq cond", "SELECT * FROM test WHERE b = 'bar1'", false, "foo1,bar1,baz1,1\nfoo2,bar1,1,2\n", nil},
|
||||
{"With neq cond", "SELECT * FROM test WHERE a != 'foo1'", false, "foo2,bar1,1,2\nfoo3,bar2,3\n", nil},
|
||||
{"With gt cond", "SELECT * FROM test WHERE b > 'bar1'", false, "", nil},
|
||||
{"With lt cond", "SELECT * FROM test WHERE a < 'zzzzz'", false, "foo1,bar1,baz1,1\nfoo2,bar1,1,2\n", nil},
|
||||
{"With lte cond", "SELECT * FROM test WHERE a <= 'foo3'", false, "foo1,bar1,baz1,1\nfoo2,bar1,1,2\n", nil},
|
||||
{"With field comparison", "SELECT * FROM test WHERE b < a", false, "foo1,bar1,baz1,1\nfoo2,bar1,1,2\n", nil},
|
||||
{"With limit", "SELECT * FROM test WHERE b = 'bar1' LIMIT 1", false, "foo1,bar1,baz1,1\n", nil},
|
||||
{"With offset", "SELECT *, key() FROM test WHERE b = 'bar1' OFFSET 1", false, "foo2,bar1,1,2,2\n", nil},
|
||||
{"With limit then offset", "SELECT * FROM test WHERE b = 'bar1' LIMIT 1 OFFSET 1", false, "foo2,bar1,1,2\n", nil},
|
||||
{"With offset then limit", "SELECT * FROM test WHERE b = 'bar1' OFFSET 1 LIMIT 1", true, "", nil},
|
||||
{"With positional params", "SELECT * FROM test WHERE a = ? OR d = ?", false, "foo1,bar1,baz1,1\nfoo3,bar2,3\n", []interface{}{"foo1", "foo3"}},
|
||||
{"With named params", "SELECT * FROM test WHERE a = $a OR d = $d", false, "foo1,bar1,baz1,1\nfoo3,bar2,3\n", []interface{}{sql.Named("a", "foo1"), sql.Named("d", "foo3")}},
|
||||
{"With key()", "SELECT key(), a FROM test", false, "1,foo1\n2,foo2\n3\n", []interface{}{sql.Named("a", "foo1"), sql.Named("d", "foo3")}},
|
||||
{"With pk in cond, gt", "SELECT * FROM test WHERE k > 0 AND e = 1", false, "foo2,bar1,1,2\n", nil},
|
||||
{"With pk in cond, =", "SELECT * FROM test WHERE k = 2.0 AND e = 1", false, "foo2,bar1,1,2\n", nil},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
testFn := func(withIndexes bool) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec("CREATE TABLE test (k INTEGER PRIMARY KEY)")
|
||||
require.NoError(t, err)
|
||||
if withIndexes {
|
||||
err = db.Exec(`
|
||||
CREATE INDEX idx_a ON test (a);
|
||||
CREATE INDEX idx_b ON test (b);
|
||||
CREATE INDEX idx_c ON test (c);
|
||||
CREATE INDEX idx_d ON test (d);
|
||||
`)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
err = db.Exec("INSERT INTO test (k, a, b, c) VALUES (1, 'foo1', 'bar1', 'baz1')")
|
||||
require.NoError(t, err)
|
||||
err = db.Exec("INSERT INTO test (k, a, b, e) VALUES (2, 'foo2', 'bar1', 1)")
|
||||
require.NoError(t, err)
|
||||
err = db.Exec("INSERT INTO test (k, d, e) VALUES (3, 'foo3', 'bar2')")
|
||||
require.NoError(t, err)
|
||||
|
||||
st, err := db.Query(test.query, test.params...)
|
||||
if test.fails {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer st.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = document.IteratorToCSV(&buf, st)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.expected, buf.String())
|
||||
}
|
||||
}
|
||||
t.Run("No Index/"+test.name, testFn(false))
|
||||
t.Run("With Index/"+test.name, testFn(true))
|
||||
}
|
||||
|
||||
t.Run("with primary key only", func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec("CREATE TABLE test (foo UINT8 PRIMARY KEY)")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Exec(`INSERT INTO test (foo, bar) VALUES (1, 'a')`)
|
||||
err = db.Exec(`INSERT INTO test (foo, bar) VALUES (2, 'b')`)
|
||||
err = db.Exec(`INSERT INTO test (foo, bar) VALUES (3, 'c')`)
|
||||
err = db.Exec(`INSERT INTO test (foo, bar) VALUES (4, 'd')`)
|
||||
require.NoError(t, err)
|
||||
|
||||
st, err := db.Query("SELECT * FROM test WHERE foo < 400 AND foo >= 2")
|
||||
require.NoError(t, err)
|
||||
defer st.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = document.IteratorToCSV(&buf, st)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "b,2\nc,3\nd,4\n", buf.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateStmt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
fails bool
|
||||
expected string
|
||||
params []interface{}
|
||||
}{
|
||||
{"No cond", `UPDATE test SET a = 'boo'`, false, "boo,bar1,baz1\nboo,bar2\nfoo3,bar3\n", nil},
|
||||
{"No cond / with ident string", `UPDATE test SET "a" = 'boo'`, false, "boo,bar1,baz1\nboo,bar2\nfoo3,bar3\n", nil},
|
||||
{"No cond / with multiple idents", `UPDATE test SET a = c`, false, "baz1,bar1,baz1\nNULL,bar2\nfoo3,bar3\n", nil},
|
||||
{"No cond / with string", `UPDATE test SET 'a' = 'boo'`, true, "", nil},
|
||||
{"With cond", "UPDATE test SET a = 1, b = 2 WHERE a = 'foo2'", false, "foo1,bar1,baz1\n1,2\nfoo3,bar3\n", nil},
|
||||
{"Field not found", "UPDATE test SET a = 1, b = 2 WHERE a = f", false, "foo1,bar1,baz1\nfoo2,bar2\nfoo3,bar3\n", nil},
|
||||
{"Positional params", "UPDATE test SET a = ?, b = ? WHERE a = ?", false, "a,b,baz1\nfoo2,bar2\nfoo3,bar3\n", []interface{}{"a", "b", "foo1"}},
|
||||
{"Named params", "UPDATE test SET a = $a, b = $b WHERE a = $c", false, "a,b,baz1\nfoo2,bar2\nfoo3,bar3\n", []interface{}{sql.Named("b", "b"), sql.Named("a", "a"), sql.Named("c", "foo1")}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec("CREATE TABLE test")
|
||||
require.NoError(t, err)
|
||||
err = db.Exec("INSERT INTO test (a, b, c) VALUES ('foo1', 'bar1', 'baz1')")
|
||||
require.NoError(t, err)
|
||||
err = db.Exec("INSERT INTO test (a, b) VALUES ('foo2', 'bar2')")
|
||||
require.NoError(t, err)
|
||||
err = db.Exec("INSERT INTO test (d, e) VALUES ('foo3', 'bar3')")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Exec(test.query, test.params...)
|
||||
if test.fails {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
st, err := db.Query("SELECT * FROM test")
|
||||
require.NoError(t, err)
|
||||
defer st.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = document.IteratorToCSV(&buf, st)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.expected, buf.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
86
parser/expr_test.go
Normal file
86
parser/expr_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/asdine/genji/query"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParserExpr(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
s string
|
||||
expected query.Expr
|
||||
}{
|
||||
{"=", "age = 10", query.Eq(query.FieldSelector("age"), query.Int8Value(10))},
|
||||
{"!=", "age != 10", query.Neq(query.FieldSelector("age"), query.Int8Value(10))},
|
||||
{">", "age > 10", query.Gt(query.FieldSelector("age"), query.Int8Value(10))},
|
||||
{">=", "age >= 10", query.Gte(query.FieldSelector("age"), query.Int8Value(10))},
|
||||
{"<", "age < 10", query.Lt(query.FieldSelector("age"), query.Int8Value(10))},
|
||||
{"<=", "age <= 10", query.Lte(query.FieldSelector("age"), query.Int8Value(10))},
|
||||
{"AND", "age = 10 AND age <= 11",
|
||||
query.And(
|
||||
query.Eq(query.FieldSelector("age"), query.Int8Value(10)),
|
||||
query.Lte(query.FieldSelector("age"), query.Int8Value(11)),
|
||||
)},
|
||||
{"OR", "age = 10 OR age = 11",
|
||||
query.Or(
|
||||
query.Eq(query.FieldSelector("age"), query.Int8Value(10)),
|
||||
query.Eq(query.FieldSelector("age"), query.Int8Value(11)),
|
||||
)},
|
||||
{"AND then OR", "age >= 10 AND age > $age OR age < 10.4",
|
||||
query.Or(
|
||||
query.And(
|
||||
query.Gte(query.FieldSelector("age"), query.Int8Value(10)),
|
||||
query.Gt(query.FieldSelector("age"), query.NamedParam("age")),
|
||||
),
|
||||
query.Lt(query.FieldSelector("age"), query.Float64Value(10.4)),
|
||||
)},
|
||||
{"with NULL", "age > NULL", query.Gt(query.FieldSelector("age"), query.NullValue())},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ex, err := NewParser(strings.NewReader(test.s)).ParseExpr()
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, test.expected, ex)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserParams(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
s string
|
||||
expected query.Expr
|
||||
errored bool
|
||||
}{
|
||||
{"one positional", "age = ?", query.Eq(query.FieldSelector("age"), query.PositionalParam(1)), false},
|
||||
{"multiple positional", "age = ? AND age <= ?",
|
||||
query.And(
|
||||
query.Eq(query.FieldSelector("age"), query.PositionalParam(1)),
|
||||
query.Lte(query.FieldSelector("age"), query.PositionalParam(2)),
|
||||
), false},
|
||||
{"one named", "age = $age", query.Eq(query.FieldSelector("age"), query.NamedParam("age")), false},
|
||||
{"multiple named", "age = $foo OR age = $bar",
|
||||
query.Or(
|
||||
query.Eq(query.FieldSelector("age"), query.NamedParam("foo")),
|
||||
query.Eq(query.FieldSelector("age"), query.NamedParam("bar")),
|
||||
), false},
|
||||
{"mixed", "age >= ? AND age > $foo OR age < ?", nil, true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ex, err := NewParser(strings.NewReader(test.s)).ParseExpr()
|
||||
if test.errored {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, test.expected, ex)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,90 +1,12 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/asdine/genji/query"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParserExpr(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
s string
|
||||
expected query.Expr
|
||||
}{
|
||||
{"=", "age = 10", query.Eq(query.FieldSelector("age"), query.Int8Value(10))},
|
||||
{"!=", "age != 10", query.Neq(query.FieldSelector("age"), query.Int8Value(10))},
|
||||
{">", "age > 10", query.Gt(query.FieldSelector("age"), query.Int8Value(10))},
|
||||
{">=", "age >= 10", query.Gte(query.FieldSelector("age"), query.Int8Value(10))},
|
||||
{"<", "age < 10", query.Lt(query.FieldSelector("age"), query.Int8Value(10))},
|
||||
{"<=", "age <= 10", query.Lte(query.FieldSelector("age"), query.Int8Value(10))},
|
||||
{"AND", "age = 10 AND age <= 11",
|
||||
query.And(
|
||||
query.Eq(query.FieldSelector("age"), query.Int8Value(10)),
|
||||
query.Lte(query.FieldSelector("age"), query.Int8Value(11)),
|
||||
)},
|
||||
{"OR", "age = 10 OR age = 11",
|
||||
query.Or(
|
||||
query.Eq(query.FieldSelector("age"), query.Int8Value(10)),
|
||||
query.Eq(query.FieldSelector("age"), query.Int8Value(11)),
|
||||
)},
|
||||
{"AND then OR", "age >= 10 AND age > $age OR age < 10.4",
|
||||
query.Or(
|
||||
query.And(
|
||||
query.Gte(query.FieldSelector("age"), query.Int8Value(10)),
|
||||
query.Gt(query.FieldSelector("age"), query.NamedParam("age")),
|
||||
),
|
||||
query.Lt(query.FieldSelector("age"), query.Float64Value(10.4)),
|
||||
)},
|
||||
{"with NULL", "age > NULL", query.Gt(query.FieldSelector("age"), query.NullValue())},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ex, err := NewParser(strings.NewReader(test.s)).ParseExpr()
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, test.expected, ex)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserParams(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
s string
|
||||
expected query.Expr
|
||||
errored bool
|
||||
}{
|
||||
{"one positional", "age = ?", query.Eq(query.FieldSelector("age"), query.PositionalParam(1)), false},
|
||||
{"multiple positional", "age = ? AND age <= ?",
|
||||
query.And(
|
||||
query.Eq(query.FieldSelector("age"), query.PositionalParam(1)),
|
||||
query.Lte(query.FieldSelector("age"), query.PositionalParam(2)),
|
||||
), false},
|
||||
{"one named", "age = $age", query.Eq(query.FieldSelector("age"), query.NamedParam("age")), false},
|
||||
{"multiple named", "age = $foo OR age = $bar",
|
||||
query.Or(
|
||||
query.Eq(query.FieldSelector("age"), query.NamedParam("foo")),
|
||||
query.Eq(query.FieldSelector("age"), query.NamedParam("bar")),
|
||||
), false},
|
||||
{"mixed", "age >= ? AND age > $foo OR age < ?", nil, true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ex, err := NewParser(strings.NewReader(test.s)).ParseExpr()
|
||||
if test.errored {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, test.expected, ex)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserMultiStatement(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@@ -5,9 +5,48 @@ import (
|
||||
"errors"
|
||||
|
||||
"github.com/asdine/genji/database"
|
||||
"github.com/asdine/genji/document"
|
||||
"github.com/asdine/genji/index"
|
||||
)
|
||||
|
||||
// CreateTableStmt is a DSL that allows creating a full CREATE TABLE statement.
|
||||
type CreateTableStmt struct {
|
||||
TableName string
|
||||
IfNotExists bool
|
||||
PrimaryKeyName string
|
||||
PrimaryKeyType document.ValueType
|
||||
}
|
||||
|
||||
// IsReadOnly always returns false. It implements the Statement interface.
|
||||
func (stmt CreateTableStmt) IsReadOnly() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Run runs the Create table statement in the given transaction.
|
||||
// It implements the Statement interface.
|
||||
func (stmt CreateTableStmt) Run(tx *database.Transaction, args []driver.NamedValue) (Result, error) {
|
||||
var res Result
|
||||
|
||||
if stmt.TableName == "" {
|
||||
return res, errors.New("missing table name")
|
||||
}
|
||||
|
||||
var cfg *database.TableConfig
|
||||
|
||||
if stmt.PrimaryKeyName != "" {
|
||||
cfg = new(database.TableConfig)
|
||||
cfg.PrimaryKeyName = stmt.PrimaryKeyName
|
||||
cfg.PrimaryKeyType = stmt.PrimaryKeyType
|
||||
}
|
||||
|
||||
err := tx.CreateTable(stmt.TableName, cfg)
|
||||
if stmt.IfNotExists && err == database.ErrTableAlreadyExists {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// CreateIndexStmt is a DSL that allows creating a full CREATE INDEX statement.
|
||||
// It is typically created using the CreateIndex function.
|
||||
type CreateIndexStmt struct {
|
@@ -1,47 +0,0 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
|
||||
"github.com/asdine/genji/database"
|
||||
"github.com/asdine/genji/document"
|
||||
)
|
||||
|
||||
// CreateTableStmt is a DSL that allows creating a full CREATE TABLE statement.
|
||||
type CreateTableStmt struct {
|
||||
TableName string
|
||||
IfNotExists bool
|
||||
PrimaryKeyName string
|
||||
PrimaryKeyType document.ValueType
|
||||
}
|
||||
|
||||
// IsReadOnly always returns false. It implements the Statement interface.
|
||||
func (stmt CreateTableStmt) IsReadOnly() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Run runs the Create table statement in the given transaction.
|
||||
// It implements the Statement interface.
|
||||
func (stmt CreateTableStmt) Run(tx *database.Transaction, args []driver.NamedValue) (Result, error) {
|
||||
var res Result
|
||||
|
||||
if stmt.TableName == "" {
|
||||
return res, errors.New("missing table name")
|
||||
}
|
||||
|
||||
var cfg *database.TableConfig
|
||||
|
||||
if stmt.PrimaryKeyName != "" {
|
||||
cfg = new(database.TableConfig)
|
||||
cfg.PrimaryKeyName = stmt.PrimaryKeyName
|
||||
cfg.PrimaryKeyType = stmt.PrimaryKeyType
|
||||
}
|
||||
|
||||
err := tx.CreateTable(stmt.TableName, cfg)
|
||||
if stmt.IfNotExists && err == database.ErrTableAlreadyExists {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
76
query/create_test.go
Normal file
76
query/create_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/asdine/genji"
|
||||
"github.com/asdine/genji/database"
|
||||
"github.com/asdine/genji/engine/memoryengine"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCreateTable(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
fails bool
|
||||
}{
|
||||
{"Basic", `CREATE TABLE test`, false},
|
||||
{"Exists", "CREATE TABLE test;CREATE TABLE test", true},
|
||||
{"If not exists", "CREATE TABLE IF NOT EXISTS test", false},
|
||||
{"If not exists, twice", "CREATE TABLE IF NOT EXISTS test;CREATE TABLE IF NOT EXISTS test", false},
|
||||
{"With primary key", "CREATE TABLE test(foo STRING PRIMARY KEY)", false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec(test.query)
|
||||
if test.fails {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.ViewTable("test", func(_ *genji.Tx, _ *database.Table) error {
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateIndex(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
fails bool
|
||||
}{
|
||||
{"Basic", "CREATE INDEX idx ON test (foo)", false},
|
||||
{"If not exists", "CREATE INDEX IF NOT EXISTS idx ON test (foo)", false},
|
||||
{"Unique", "CREATE UNIQUE INDEX IF NOT EXISTS idx ON test (foo)", false},
|
||||
{"No fields", "CREATE INDEX idx ON test", true},
|
||||
{"More than 1 field", "CREATE INDEX idx ON test (foo, bar)", true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec("CREATE TABLE test")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Exec(test.query)
|
||||
if test.fails {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
58
query/delete_test.go
Normal file
58
query/delete_test.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/asdine/genji"
|
||||
"github.com/asdine/genji/document"
|
||||
"github.com/asdine/genji/engine/memoryengine"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDeleteStmt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
fails bool
|
||||
expected string
|
||||
params []interface{}
|
||||
}{
|
||||
{"No cond", `DELETE FROM test`, false, "", nil},
|
||||
{"With cond", "DELETE FROM test WHERE b = 'bar1'", false, "bar2,foo3,bar3\n", nil},
|
||||
{"Table not found", "DELETE FROM foo WHERE b = 'bar1'", true, "", nil},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec("CREATE TABLE test")
|
||||
require.NoError(t, err)
|
||||
err = db.Exec("INSERT INTO test (a, b, c) VALUES ('foo1', 'bar1', 'baz1')")
|
||||
require.NoError(t, err)
|
||||
err = db.Exec("INSERT INTO test (a, b) VALUES ('foo2', 'bar1')")
|
||||
require.NoError(t, err)
|
||||
err = db.Exec("INSERT INTO test (d, b, e) VALUES ('foo3', 'bar2', 'bar3')")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Exec(test.query, test.params...)
|
||||
if test.fails {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
st, err := db.Query("SELECT * FROM test")
|
||||
require.NoError(t, err)
|
||||
defer st.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = document.IteratorToCSV(&buf, st)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.expected, buf.String())
|
||||
})
|
||||
}
|
||||
}
|
@@ -7,6 +7,34 @@ import (
|
||||
"github.com/asdine/genji/database"
|
||||
)
|
||||
|
||||
// DropTableStmt is a DSL that allows creating a DROP TABLE query.
|
||||
type DropTableStmt struct {
|
||||
TableName string
|
||||
IfExists bool
|
||||
}
|
||||
|
||||
// IsReadOnly always returns false. It implements the Statement interface.
|
||||
func (stmt DropTableStmt) IsReadOnly() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Run runs the DropTable statement in the given transaction.
|
||||
// It implements the Statement interface.
|
||||
func (stmt DropTableStmt) Run(tx *database.Transaction, args []driver.NamedValue) (Result, error) {
|
||||
var res Result
|
||||
|
||||
if stmt.TableName == "" {
|
||||
return res, errors.New("missing table name")
|
||||
}
|
||||
|
||||
err := tx.DropTable(stmt.TableName)
|
||||
if err == database.ErrTableNotFound && stmt.IfExists {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// DropIndexStmt is a DSL that allows creating a DROP INDEX query.
|
||||
type DropIndexStmt struct {
|
||||
IndexName string
|
@@ -1,36 +0,0 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
|
||||
"github.com/asdine/genji/database"
|
||||
)
|
||||
|
||||
// DropTableStmt is a DSL that allows creating a DROP TABLE query.
|
||||
type DropTableStmt struct {
|
||||
TableName string
|
||||
IfExists bool
|
||||
}
|
||||
|
||||
// IsReadOnly always returns false. It implements the Statement interface.
|
||||
func (stmt DropTableStmt) IsReadOnly() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Run runs the DropTable statement in the given transaction.
|
||||
// It implements the Statement interface.
|
||||
func (stmt DropTableStmt) Run(tx *database.Transaction, args []driver.NamedValue) (Result, error) {
|
||||
var res Result
|
||||
|
||||
if stmt.TableName == "" {
|
||||
return res, errors.New("missing table name")
|
||||
}
|
||||
|
||||
err := tx.DropTable(stmt.TableName)
|
||||
if err == database.ErrTableNotFound && stmt.IfExists {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
40
query/drop_test.go
Normal file
40
query/drop_test.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/asdine/genji"
|
||||
"github.com/asdine/genji/engine/memoryengine"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDrop(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
fails bool
|
||||
}{
|
||||
{"Drop table", "DROP TABLE test", false},
|
||||
{"Drop table If not exists", "DROP TABLE IF EXISTS test", false},
|
||||
{"Drop index", "DROP INDEX idx", false},
|
||||
{"Drop index if exists", "DROP INDEX IF EXISTS idx", false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec("CREATE TABLE test; CREATE INDEX idx ON test (foo)")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Exec(test.query)
|
||||
if test.fails {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
@@ -83,6 +83,17 @@ func (stmt InsertStmt) insertDocuments(t *database.Table, stack EvalStack) (Resu
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
case KVPairs:
|
||||
v, err := tp.Eval(stack)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
d, err = v.Value.Value.DecodeToDocument()
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
default:
|
||||
return res, fmt.Errorf("values must be a list of documents if field list is empty")
|
||||
}
|
||||
|
||||
res.lastInsertKey, err = t.Insert(d)
|
||||
|
107
query/insert_test.go
Normal file
107
query/insert_test.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/asdine/genji"
|
||||
"github.com/asdine/genji/database"
|
||||
"github.com/asdine/genji/document"
|
||||
"github.com/asdine/genji/engine/memoryengine"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestInsertStmt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
fails bool
|
||||
expected string
|
||||
params []interface{}
|
||||
}{
|
||||
{"Values / No columns", `INSERT INTO test VALUES ("a", 'b', 'c')`, true, ``, nil},
|
||||
{"Values / With columns", `INSERT INTO test (a, b, c) VALUES ('a', 'b', 'c')`, false, "1,a,b,c\n", nil},
|
||||
{"Values / Ident", `INSERT INTO test (a) VALUES (a)`, true, ``, nil},
|
||||
{"Values / Ident string", `INSERT INTO test (a) VALUES ("a")`, true, ``, nil},
|
||||
{"Values / With fields ident string", `INSERT INTO test (a, "foo bar") VALUES ('c', 'd')`, false, "1,c,d\n", nil},
|
||||
{"Values / Positional Params", "INSERT INTO test (a, b, c) VALUES (?, 'e', ?)", false, "1,d,e,f\n", []interface{}{"d", "f"}},
|
||||
{"Values / Named Params", "INSERT INTO test (a, b, c) VALUES ($d, 'e', $f)", false, "1,d,e,f\n", []interface{}{sql.Named("f", "f"), sql.Named("d", "d")}},
|
||||
{"Values / Invalid params", "INSERT INTO test (a, b, c) VALUES ('d', ?)", true, "", []interface{}{'e'}},
|
||||
{"Values / List", `INSERT INTO test (a, b, c) VALUES ("a", 'b', (1, 2, 3))`, true, "", nil},
|
||||
{"Documents", "INSERT INTO test VALUES {a: 'a', b: 2.3, c: 1 = 1}", false, "1,a,2.3,true\n", nil},
|
||||
{"Documents / Positional Params", "INSERT INTO test VALUES {a: ?, b: 2.3, c: ?}", false, "1,a,2.3,true\n", []interface{}{"a", true}},
|
||||
{"Documents / Named Params", "INSERT INTO test VALUES {a: $a, b: 2.3, c: $c}", false, "1,1,2.3,true\n", []interface{}{sql.Named("c", true), sql.Named("a", 1)}},
|
||||
{"Documents / List ", "INSERT INTO test VALUES {a: (1, 2, 3)}", true, "", nil},
|
||||
{"Documents / strings", `INSERT INTO test VALUES {'a': 'a', b: 2.3}`, true, "", nil},
|
||||
{"Documents / double quotes", `INSERT INTO test VALUES {"a": "b"}`, false, "1,b\n", nil},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
testFn := func(withIndexes bool) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec("CREATE TABLE test")
|
||||
require.NoError(t, err)
|
||||
if withIndexes {
|
||||
err = db.Exec(`
|
||||
CREATE INDEX idx_a ON test (a);
|
||||
CREATE INDEX idx_b ON test (b);
|
||||
CREATE INDEX idx_c ON test (c);
|
||||
`)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
err = db.Exec(test.query, test.params...)
|
||||
if test.fails {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
st, err := db.Query("SELECT key(), * FROM test")
|
||||
require.NoError(t, err)
|
||||
defer st.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = document.IteratorToCSV(&buf, st)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.expected, buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("No Index/"+test.name, testFn(false))
|
||||
t.Run("With Index/"+test.name, testFn(true))
|
||||
}
|
||||
|
||||
t.Run("with primary key", func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec("CREATE TABLE test (foo INTEGER PRIMARY KEY)")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Exec(`INSERT INTO test (bar) VALUES (1)`)
|
||||
require.Error(t, err)
|
||||
err = db.Exec(`INSERT INTO test (bar, foo) VALUES (1, 2)`)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Exec(`INSERT INTO test (bar, foo) VALUES (1, 2)`)
|
||||
require.Equal(t, err, database.ErrDuplicateRecord)
|
||||
})
|
||||
|
||||
t.Run("with shadowing", func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec("CREATE TABLE test")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Exec(`INSERT INTO test ("key()", "key") VALUES (1, 2)`)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
109
query/select_test.go
Normal file
109
query/select_test.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/asdine/genji"
|
||||
"github.com/asdine/genji/document"
|
||||
"github.com/asdine/genji/engine/memoryengine"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSelectStmt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
fails bool
|
||||
expected string
|
||||
params []interface{}
|
||||
}{
|
||||
{"No cond", "SELECT * FROM test", false, "foo1,bar1,baz1,1\nfoo2,bar1,1,2\nfoo3,bar2,3\n", nil},
|
||||
{"Multiple wildcards cond", "SELECT *, *, a FROM test", false, "foo1,bar1,baz1,1,foo1,bar1,baz1,1,foo1\nfoo2,bar1,1,2,foo2,bar1,1,2,foo2\nfoo3,bar2,3,foo3,bar2,3\n", nil},
|
||||
{"With fields", "SELECT a, c FROM test", false, "foo1,baz1\nfoo2\n\n", nil},
|
||||
{"With eq cond", "SELECT * FROM test WHERE b = 'bar1'", false, "foo1,bar1,baz1,1\nfoo2,bar1,1,2\n", nil},
|
||||
{"With neq cond", "SELECT * FROM test WHERE a != 'foo1'", false, "foo2,bar1,1,2\nfoo3,bar2,3\n", nil},
|
||||
{"With gt cond", "SELECT * FROM test WHERE b > 'bar1'", false, "", nil},
|
||||
{"With lt cond", "SELECT * FROM test WHERE a < 'zzzzz'", false, "foo1,bar1,baz1,1\nfoo2,bar1,1,2\n", nil},
|
||||
{"With lte cond", "SELECT * FROM test WHERE a <= 'foo3'", false, "foo1,bar1,baz1,1\nfoo2,bar1,1,2\n", nil},
|
||||
{"With field comparison", "SELECT * FROM test WHERE b < a", false, "foo1,bar1,baz1,1\nfoo2,bar1,1,2\n", nil},
|
||||
{"With limit", "SELECT * FROM test WHERE b = 'bar1' LIMIT 1", false, "foo1,bar1,baz1,1\n", nil},
|
||||
{"With offset", "SELECT *, key() FROM test WHERE b = 'bar1' OFFSET 1", false, "foo2,bar1,1,2,2\n", nil},
|
||||
{"With limit then offset", "SELECT * FROM test WHERE b = 'bar1' LIMIT 1 OFFSET 1", false, "foo2,bar1,1,2\n", nil},
|
||||
{"With offset then limit", "SELECT * FROM test WHERE b = 'bar1' OFFSET 1 LIMIT 1", true, "", nil},
|
||||
{"With positional params", "SELECT * FROM test WHERE a = ? OR d = ?", false, "foo1,bar1,baz1,1\nfoo3,bar2,3\n", []interface{}{"foo1", "foo3"}},
|
||||
{"With named params", "SELECT * FROM test WHERE a = $a OR d = $d", false, "foo1,bar1,baz1,1\nfoo3,bar2,3\n", []interface{}{sql.Named("a", "foo1"), sql.Named("d", "foo3")}},
|
||||
{"With key()", "SELECT key(), a FROM test", false, "1,foo1\n2,foo2\n3\n", []interface{}{sql.Named("a", "foo1"), sql.Named("d", "foo3")}},
|
||||
{"With pk in cond, gt", "SELECT * FROM test WHERE k > 0 AND e = 1", false, "foo2,bar1,1,2\n", nil},
|
||||
{"With pk in cond, =", "SELECT * FROM test WHERE k = 2.0 AND e = 1", false, "foo2,bar1,1,2\n", nil},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
testFn := func(withIndexes bool) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec("CREATE TABLE test (k INTEGER PRIMARY KEY)")
|
||||
require.NoError(t, err)
|
||||
if withIndexes {
|
||||
err = db.Exec(`
|
||||
CREATE INDEX idx_a ON test (a);
|
||||
CREATE INDEX idx_b ON test (b);
|
||||
CREATE INDEX idx_c ON test (c);
|
||||
CREATE INDEX idx_d ON test (d);
|
||||
`)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
err = db.Exec("INSERT INTO test (k, a, b, c) VALUES (1, 'foo1', 'bar1', 'baz1')")
|
||||
require.NoError(t, err)
|
||||
err = db.Exec("INSERT INTO test (k, a, b, e) VALUES (2, 'foo2', 'bar1', 1)")
|
||||
require.NoError(t, err)
|
||||
err = db.Exec("INSERT INTO test (k, d, e) VALUES (3, 'foo3', 'bar2')")
|
||||
require.NoError(t, err)
|
||||
|
||||
st, err := db.Query(test.query, test.params...)
|
||||
if test.fails {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer st.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = document.IteratorToCSV(&buf, st)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.expected, buf.String())
|
||||
}
|
||||
}
|
||||
t.Run("No Index/"+test.name, testFn(false))
|
||||
t.Run("With Index/"+test.name, testFn(true))
|
||||
}
|
||||
|
||||
t.Run("with primary key only", func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec("CREATE TABLE test (foo UINT8 PRIMARY KEY)")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Exec(`INSERT INTO test (foo, bar) VALUES (1, 'a')`)
|
||||
err = db.Exec(`INSERT INTO test (foo, bar) VALUES (2, 'b')`)
|
||||
err = db.Exec(`INSERT INTO test (foo, bar) VALUES (3, 'c')`)
|
||||
err = db.Exec(`INSERT INTO test (foo, bar) VALUES (4, 'd')`)
|
||||
require.NoError(t, err)
|
||||
|
||||
st, err := db.Query("SELECT * FROM test WHERE foo < 400 AND foo >= 2")
|
||||
require.NoError(t, err)
|
||||
defer st.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = document.IteratorToCSV(&buf, st)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "b,2\nc,3\nd,4\n", buf.String())
|
||||
})
|
||||
}
|
64
query/update_test.go
Normal file
64
query/update_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/asdine/genji"
|
||||
"github.com/asdine/genji/document"
|
||||
"github.com/asdine/genji/engine/memoryengine"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUpdateStmt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
fails bool
|
||||
expected string
|
||||
params []interface{}
|
||||
}{
|
||||
{"No cond", `UPDATE test SET a = 'boo'`, false, "boo,bar1,baz1\nboo,bar2\nfoo3,bar3\n", nil},
|
||||
{"No cond / with ident string", `UPDATE test SET "a" = 'boo'`, false, "boo,bar1,baz1\nboo,bar2\nfoo3,bar3\n", nil},
|
||||
{"No cond / with multiple idents", `UPDATE test SET a = c`, false, "baz1,bar1,baz1\nNULL,bar2\nfoo3,bar3\n", nil},
|
||||
{"No cond / with string", `UPDATE test SET 'a' = 'boo'`, true, "", nil},
|
||||
{"With cond", "UPDATE test SET a = 1, b = 2 WHERE a = 'foo2'", false, "foo1,bar1,baz1\n1,2\nfoo3,bar3\n", nil},
|
||||
{"Field not found", "UPDATE test SET a = 1, b = 2 WHERE a = f", false, "foo1,bar1,baz1\nfoo2,bar2\nfoo3,bar3\n", nil},
|
||||
{"Positional params", "UPDATE test SET a = ?, b = ? WHERE a = ?", false, "a,b,baz1\nfoo2,bar2\nfoo3,bar3\n", []interface{}{"a", "b", "foo1"}},
|
||||
{"Named params", "UPDATE test SET a = $a, b = $b WHERE a = $c", false, "a,b,baz1\nfoo2,bar2\nfoo3,bar3\n", []interface{}{sql.Named("b", "b"), sql.Named("a", "a"), sql.Named("c", "foo1")}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
db, err := genji.New(memoryengine.NewEngine())
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec("CREATE TABLE test")
|
||||
require.NoError(t, err)
|
||||
err = db.Exec("INSERT INTO test (a, b, c) VALUES ('foo1', 'bar1', 'baz1')")
|
||||
require.NoError(t, err)
|
||||
err = db.Exec("INSERT INTO test (a, b) VALUES ('foo2', 'bar2')")
|
||||
require.NoError(t, err)
|
||||
err = db.Exec("INSERT INTO test (d, e) VALUES ('foo3', 'bar3')")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Exec(test.query, test.params...)
|
||||
if test.fails {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
st, err := db.Query("SELECT * FROM test")
|
||||
require.NoError(t, err)
|
||||
defer st.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = document.IteratorToCSV(&buf, st)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.expected, buf.String())
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user