mirror of
https://github.com/chaisql/chai.git
synced 2025-10-05 07:36:56 +08:00

All new error handling code now rely on internal/errors package which provides a compilation time toggle that enables to capture stacktraces for easier debugging while developing. It also comes with a new testutil/assert package which replaces the require package when it comes to checking or comparing errors and printing the stack traces if needed. Finally, the test target of the Makefile uses the debug build tag by default. A testnodebug target is also provided for convenience and to make sure no tests are broken due to not having used the internal/errors or testutil/assert package. See #431 for more details
214 lines
6.1 KiB
Go
214 lines
6.1 KiB
Go
package statement_test
|
|
|
|
import (
|
|
"bytes"
|
|
"database/sql"
|
|
"testing"
|
|
|
|
"github.com/genjidb/genji"
|
|
"github.com/genjidb/genji/internal/testutil"
|
|
"github.com/genjidb/genji/internal/testutil/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestInsertStmt(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
query string
|
|
fails bool
|
|
expected string
|
|
params []interface{}
|
|
}{
|
|
{"Values / Positional Params", "INSERT INTO test (a, b, c) VALUES (?, 'e', ?)", false, `[{"pk()":1,"a":"d","b":"e","c":"f"}]`, []interface{}{"d", "f"}},
|
|
{"Values / Named Params", "INSERT INTO test (a, b, c) VALUES ($d, 'e', $f)", false, `[{"pk()":1,"a":"d","b":"e","c":"f"}]`, []interface{}{sql.Named("f", "f"), sql.Named("d", "d")}},
|
|
{"Values / Invalid params", "INSERT INTO test (a, b, c) VALUES ('d', ?)", true, "", []interface{}{'e'}},
|
|
{"Documents / Named Params", "INSERT INTO test VALUES {a: $a, b: 2.3, c: $c}", false, `[{"pk()":1,"a":1,"b":2.3,"c":true}]`, []interface{}{sql.Named("c", true), sql.Named("a", 1)}},
|
|
{"Documents / List ", "INSERT INTO test VALUES {a: [1, 2, 3]}", false, `[{"pk()":1,"a":[1,2,3]}]`, nil},
|
|
{"Select / same table", "INSERT INTO test SELECT * FROM test", true, ``, nil},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
testFn := func(withIndexes bool) func(t *testing.T) {
|
|
return func(t *testing.T) {
|
|
db, err := genji.Open(":memory:")
|
|
assert.NoError(t, err)
|
|
defer db.Close()
|
|
|
|
err = db.Exec("CREATE TABLE test")
|
|
assert.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);
|
|
`)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
err = db.Exec(test.query, test.params...)
|
|
if test.fails {
|
|
assert.Error(t, err)
|
|
return
|
|
}
|
|
assert.NoError(t, err)
|
|
|
|
st, err := db.Query("SELECT pk(), * FROM test")
|
|
assert.NoError(t, err)
|
|
defer st.Close()
|
|
|
|
var buf bytes.Buffer
|
|
err = testutil.IteratorToJSONArray(&buf, st)
|
|
assert.NoError(t, err)
|
|
require.JSONEq(t, test.expected, buf.String())
|
|
}
|
|
}
|
|
|
|
t.Run("No Index/"+test.name, testFn(false))
|
|
t.Run("With Index/"+test.name, testFn(true))
|
|
}
|
|
|
|
t.Run("with struct param", func(t *testing.T) {
|
|
db, err := genji.Open(":memory:")
|
|
assert.NoError(t, err)
|
|
defer db.Close()
|
|
|
|
err = db.Exec("CREATE TABLE test")
|
|
assert.NoError(t, err)
|
|
|
|
type foo struct {
|
|
A string
|
|
B string `genji:"b-b"`
|
|
}
|
|
|
|
err = db.Exec("INSERT INTO test VALUES ?", &foo{A: "a", B: "b"})
|
|
assert.NoError(t, err)
|
|
res, err := db.Query("SELECT * FROM test")
|
|
defer res.Close()
|
|
|
|
assert.NoError(t, err)
|
|
var buf bytes.Buffer
|
|
err = testutil.IteratorToJSONArray(&buf, res)
|
|
assert.NoError(t, err)
|
|
require.JSONEq(t, `[{"a": "a", "b-b": "b"}]`, buf.String())
|
|
})
|
|
|
|
t.Run("with RETURNING", func(t *testing.T) {
|
|
db, err := genji.Open(":memory:")
|
|
assert.NoError(t, err)
|
|
defer db.Close()
|
|
|
|
err = db.Exec(`CREATE TABLE test`)
|
|
assert.NoError(t, err)
|
|
|
|
d, err := db.QueryDocument(`insert into test (a) VALUES (1) RETURNING *, pk(), a AS A`)
|
|
assert.NoError(t, err)
|
|
testutil.RequireDocJSONEq(t, d, `{"a": 1, "pk()": 1, "A": 1}`)
|
|
})
|
|
|
|
t.Run("ensure rollback", func(t *testing.T) {
|
|
db, err := genji.Open(":memory:")
|
|
assert.NoError(t, err)
|
|
defer db.Close()
|
|
|
|
err = db.Exec(`CREATE TABLE test(a int unique)`)
|
|
assert.NoError(t, err)
|
|
|
|
err = db.Exec(`insert into test (a) VALUES (1), (1)`)
|
|
assert.Error(t, err)
|
|
|
|
res, err := db.Query("SELECT * FROM test")
|
|
assert.NoError(t, err)
|
|
defer res.Close()
|
|
|
|
testutil.RequireStreamEq(t, ``, res)
|
|
})
|
|
|
|
t.Run("with NEXT VALUE FOR", func(t *testing.T) {
|
|
db, err := genji.Open(":memory:")
|
|
assert.NoError(t, err)
|
|
defer db.Close()
|
|
|
|
err = db.Exec(`CREATE SEQUENCE seq; CREATE TABLE test(a int, b int default NEXT VALUE FOR seq)`)
|
|
assert.NoError(t, err)
|
|
|
|
err = db.Exec(`insert into test (a) VALUES (1), (2), (3)`)
|
|
assert.NoError(t, err)
|
|
|
|
res, err := db.Query("SELECT * FROM test")
|
|
assert.NoError(t, err)
|
|
defer res.Close()
|
|
|
|
var b bytes.Buffer
|
|
testutil.IteratorToJSONArray(&b, res)
|
|
require.JSONEq(t, `
|
|
[{"a": 1, "b": 1},
|
|
{"a": 2, "b": 2},
|
|
{"a": 3, "b": 3}]
|
|
|
|
`, b.String())
|
|
})
|
|
|
|
// t.Run("without RETURNING", func(t *testing.T) {
|
|
// db, err := genji.Open(":memory:")
|
|
// assert.NoError(t, err)
|
|
// defer db.Close()
|
|
|
|
// err = db.Exec(`CREATE TABLE test`)
|
|
// assert.NoError(t, err)
|
|
|
|
// _, err = db.QueryDocument(`insert into test (a) VALUES (1)`)
|
|
// require.Equal(t, errs.ErrDocumentNotFound, err)
|
|
// })
|
|
}
|
|
|
|
func TestInsertSelect(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
query string
|
|
fails bool
|
|
expected string
|
|
params []interface{}
|
|
}{
|
|
{"Same table", `INSERT INTO foo SELECT * FROM foo`, true, ``, nil},
|
|
{"No fields / No projection", `INSERT INTO foo SELECT * FROM bar`, false, `[{"pk()":1, "a":1, "b":10}]`, nil},
|
|
{"No fields / Projection", `INSERT INTO foo SELECT a FROM bar`, false, `[{"pk()":1, "a":1}]`, nil},
|
|
{"With fields / No Projection", `INSERT INTO foo (a, b) SELECT * FROM bar`, false, `[{"pk()":1, "a":1, "b":10}]`, nil},
|
|
{"With fields / Projection", `INSERT INTO foo (c, d) SELECT a, b FROM bar`, false, `[{"pk()":1, "c":1, "d":10}]`, nil},
|
|
{"Too many fields / No Projection", `INSERT INTO foo (c) SELECT * FROM bar`, true, ``, nil},
|
|
{"Too many fields / Projection", `INSERT INTO foo (c, d) SELECT a, b, c FROM bar`, true, ``, nil},
|
|
{"Too few fields / No Projection", `INSERT INTO foo (c, d, e) SELECT * FROM bar`, true, ``, nil},
|
|
{"Too few fields / Projection", `INSERT INTO foo (c, d) SELECT a FROM bar`, true, ``, nil},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
db, err := genji.Open(":memory:")
|
|
assert.NoError(t, err)
|
|
defer db.Close()
|
|
|
|
err = db.Exec(`
|
|
CREATE TABLE foo;
|
|
CREATE TABLE bar;
|
|
INSERT INTO bar (a, b) VALUES (1, 10)
|
|
`)
|
|
assert.NoError(t, err)
|
|
|
|
err = db.Exec(test.query, test.params...)
|
|
if test.fails {
|
|
assert.Error(t, err)
|
|
return
|
|
}
|
|
assert.NoError(t, err)
|
|
|
|
st, err := db.Query("SELECT pk(), * FROM foo")
|
|
assert.NoError(t, err)
|
|
defer st.Close()
|
|
|
|
var buf bytes.Buffer
|
|
err = testutil.IteratorToJSONArray(&buf, st)
|
|
assert.NoError(t, err)
|
|
require.JSONEq(t, test.expected, buf.String())
|
|
})
|
|
}
|
|
}
|