mirror of
https://github.com/chaisql/chai.git
synced 2025-10-05 15:46:55 +08:00

Previously, expressions and params were evaluated during the planning phase. This change builds the query plan without evaluating params and expressions which are then evaluated only during the execution phase.
312 lines
13 KiB
Go
312 lines
13 KiB
Go
package parser_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/genjidb/genji/document"
|
|
"github.com/genjidb/genji/internal/database"
|
|
"github.com/genjidb/genji/internal/query/statement"
|
|
"github.com/genjidb/genji/internal/sql/parser"
|
|
"github.com/genjidb/genji/internal/testutil"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestParserCreateTable(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
s string
|
|
expected statement.Statement
|
|
errored bool
|
|
}{
|
|
{"Basic", "CREATE TABLE test", &statement.CreateTableStmt{Info: database.TableInfo{TableName: "test"}}, false},
|
|
{"If not exists", "CREATE TABLE IF NOT EXISTS test", &statement.CreateTableStmt{Info: database.TableInfo{TableName: "test"}, IfNotExists: true}, false},
|
|
{"Path only", "CREATE TABLE test(a)", &statement.CreateTableStmt{}, true},
|
|
{"With primary key", "CREATE TABLE test(foo INTEGER PRIMARY KEY)",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "foo")), Type: document.IntegerValue, IsPrimaryKey: true},
|
|
},
|
|
},
|
|
}, false},
|
|
{"With primary key twice", "CREATE TABLE test(foo PRIMARY KEY PRIMARY KEY)",
|
|
&statement.CreateTableStmt{}, true},
|
|
{"With type", "CREATE TABLE test(foo INTEGER)",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "foo")), Type: document.IntegerValue},
|
|
},
|
|
},
|
|
}, false},
|
|
{"With not null", "CREATE TABLE test(foo NOT NULL)",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "foo")), IsNotNull: true},
|
|
},
|
|
},
|
|
}, false},
|
|
{"With default", "CREATE TABLE test(foo DEFAULT \"10\")",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "foo")), DefaultValue: document.NewTextValue("10")},
|
|
},
|
|
},
|
|
}, false},
|
|
{"With unique", "CREATE TABLE test(foo UNIQUE)",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "foo")), IsUnique: true},
|
|
},
|
|
},
|
|
}, false},
|
|
{"With default twice", "CREATE TABLE test(foo DEFAULT 10 DEFAULT 10)",
|
|
&statement.CreateTableStmt{}, true},
|
|
{"With not null twice", "CREATE TABLE test(foo NOT NULL NOT NULL)",
|
|
&statement.CreateTableStmt{}, true},
|
|
{"With unique twice", "CREATE TABLE test(foo UNIQUE UNIQUE)",
|
|
&statement.CreateTableStmt{}, true},
|
|
{"With type and not null", "CREATE TABLE test(foo INTEGER NOT NULL)",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "foo")), Type: document.IntegerValue, IsNotNull: true},
|
|
},
|
|
},
|
|
}, false},
|
|
{"With not null and primary key", "CREATE TABLE test(foo INTEGER NOT NULL PRIMARY KEY)",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "foo")), Type: document.IntegerValue, IsPrimaryKey: true, IsNotNull: true},
|
|
},
|
|
},
|
|
}, false},
|
|
{"With primary key and not null", "CREATE TABLE test(foo INTEGER PRIMARY KEY NOT NULL)",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "foo")), Type: document.IntegerValue, IsPrimaryKey: true, IsNotNull: true},
|
|
},
|
|
},
|
|
}, false},
|
|
{"With multiple constraints", "CREATE TABLE test(foo INTEGER PRIMARY KEY, bar INTEGER NOT NULL, baz[4][1].bat TEXT)",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "foo")), Type: document.IntegerValue, IsPrimaryKey: true},
|
|
{Path: document.Path(testutil.ParsePath(t, "bar")), Type: document.IntegerValue, IsNotNull: true},
|
|
{Path: document.Path(testutil.ParsePath(t, "baz[4][1].bat")), Type: document.TextValue},
|
|
},
|
|
},
|
|
}, false},
|
|
{"With table constraints / PK on defined field", "CREATE TABLE test(foo INTEGER, bar NOT NULL, PRIMARY KEY (foo))",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "foo")), Type: document.IntegerValue, IsPrimaryKey: true},
|
|
{Path: document.Path(testutil.ParsePath(t, "bar")), IsNotNull: true},
|
|
},
|
|
},
|
|
}, false},
|
|
{"With table constraints / PK on undefined field", "CREATE TABLE test(foo INTEGER, PRIMARY KEY (bar))",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "foo")), Type: document.IntegerValue},
|
|
{Path: document.Path(testutil.ParsePath(t, "bar")), IsPrimaryKey: true},
|
|
},
|
|
},
|
|
}, false},
|
|
{"With table constraints / field constraint after table constraint", "CREATE TABLE test(PRIMARY KEY (bar), foo INTEGER)", nil, true},
|
|
{"With table constraints / duplicate pk", "CREATE TABLE test(foo INTEGER PRIMARY KEY, PRIMARY KEY (bar))", nil, true},
|
|
{"With table constraints / duplicate pk on same path", "CREATE TABLE test(foo INTEGER PRIMARY KEY, PRIMARY KEY (foo))", nil, true},
|
|
{"With table constraints / UNIQUE on defined field", "CREATE TABLE test(foo INTEGER, bar NOT NULL, UNIQUE (foo))",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "foo")), Type: document.IntegerValue, IsUnique: true},
|
|
{Path: document.Path(testutil.ParsePath(t, "bar")), IsNotNull: true},
|
|
},
|
|
},
|
|
}, false},
|
|
{"With table constraints / UNIQUE on undefined field", "CREATE TABLE test(foo INTEGER, UNIQUE (bar))",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "foo")), Type: document.IntegerValue},
|
|
{Path: document.Path(testutil.ParsePath(t, "bar")), IsUnique: true},
|
|
},
|
|
},
|
|
}, false},
|
|
{"With table constraints / UNIQUE twice", "CREATE TABLE test(foo INTEGER UNIQUE, UNIQUE (foo))",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "foo")), Type: document.IntegerValue, IsUnique: true},
|
|
},
|
|
},
|
|
}, false},
|
|
{"With table constraints / duplicate pk on same path", "CREATE TABLE test(foo INTEGER PRIMARY KEY, PRIMARY KEY (foo))", nil, true},
|
|
{"With multiple primary keys", "CREATE TABLE test(foo PRIMARY KEY, bar PRIMARY KEY)",
|
|
&statement.CreateTableStmt{}, true},
|
|
{"With all supported fixed size data types",
|
|
"CREATE TABLE test(d double, b bool)",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "d")), Type: document.DoubleValue},
|
|
{Path: document.Path(testutil.ParsePath(t, "b")), Type: document.BoolValue},
|
|
},
|
|
},
|
|
}, false},
|
|
{"With all supported variable size data types",
|
|
"CREATE TABLE test(i integer, b blob, byt bytes, t text, a array, d document)",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "i")), Type: document.IntegerValue},
|
|
{Path: document.Path(testutil.ParsePath(t, "b")), Type: document.BlobValue},
|
|
{Path: document.Path(testutil.ParsePath(t, "byt")), Type: document.BlobValue},
|
|
{Path: document.Path(testutil.ParsePath(t, "t")), Type: document.TextValue},
|
|
{Path: document.Path(testutil.ParsePath(t, "a")), Type: document.ArrayValue},
|
|
{Path: document.Path(testutil.ParsePath(t, "d")), Type: document.DocumentValue},
|
|
},
|
|
},
|
|
}, false},
|
|
{"With integer aliases types",
|
|
"CREATE TABLE test(i int, ii int2, ei int8, m mediumint, s smallint, b bigint, t tinyint)",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "i")), Type: document.IntegerValue},
|
|
{Path: document.Path(testutil.ParsePath(t, "ii")), Type: document.IntegerValue},
|
|
{Path: document.Path(testutil.ParsePath(t, "ei")), Type: document.IntegerValue},
|
|
{Path: document.Path(testutil.ParsePath(t, "m")), Type: document.IntegerValue},
|
|
{Path: document.Path(testutil.ParsePath(t, "s")), Type: document.IntegerValue},
|
|
{Path: document.Path(testutil.ParsePath(t, "b")), Type: document.IntegerValue},
|
|
{Path: document.Path(testutil.ParsePath(t, "t")), Type: document.IntegerValue},
|
|
},
|
|
},
|
|
}, false},
|
|
{"With double aliases types",
|
|
"CREATE TABLE test(dp DOUBLE PRECISION, r real, d double)",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "dp")), Type: document.DoubleValue},
|
|
{Path: document.Path(testutil.ParsePath(t, "r")), Type: document.DoubleValue},
|
|
{Path: document.Path(testutil.ParsePath(t, "d")), Type: document.DoubleValue},
|
|
},
|
|
},
|
|
}, false},
|
|
{"With text aliases types",
|
|
"CREATE TABLE test(v VARCHAR(255), c CHARACTER(64), t TEXT)",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "v")), Type: document.TextValue},
|
|
{Path: document.Path(testutil.ParsePath(t, "c")), Type: document.TextValue},
|
|
{Path: document.Path(testutil.ParsePath(t, "t")), Type: document.TextValue},
|
|
},
|
|
},
|
|
}, false},
|
|
{"With errored text aliases types",
|
|
"CREATE TABLE test(v VARCHAR(1 IN [1, 2, 3] AND foo > 4) )",
|
|
&statement.CreateTableStmt{
|
|
Info: database.TableInfo{
|
|
TableName: "test",
|
|
FieldConstraints: []*database.FieldConstraint{
|
|
{Path: document.Path(testutil.ParsePath(t, "v")), Type: document.TextValue},
|
|
},
|
|
},
|
|
}, true},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
q, err := parser.ParseQuery(test.s)
|
|
if test.errored {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
require.Len(t, q.Statements, 1)
|
|
require.EqualValues(t, test.expected, q.Statements[0])
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParserCreateIndex(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
s string
|
|
expected statement.Statement
|
|
errored bool
|
|
}{
|
|
{"Basic", "CREATE INDEX idx ON test (foo)", &statement.CreateIndexStmt{
|
|
Info: database.IndexInfo{
|
|
IndexName: "idx", TableName: "test", Paths: []document.Path{document.Path(testutil.ParsePath(t, "foo"))},
|
|
}}, false},
|
|
{"If not exists", "CREATE INDEX IF NOT EXISTS idx ON test (foo.bar[1])", &statement.CreateIndexStmt{
|
|
Info: database.IndexInfo{
|
|
IndexName: "idx", TableName: "test", Paths: []document.Path{document.Path(testutil.ParsePath(t, "foo.bar[1]"))},
|
|
}, IfNotExists: true}, false},
|
|
{"Unique", "CREATE UNIQUE INDEX IF NOT EXISTS idx ON test (foo[3].baz)", &statement.CreateIndexStmt{
|
|
Info: database.IndexInfo{
|
|
IndexName: "idx", TableName: "test", Paths: []document.Path{document.Path(testutil.ParsePath(t, "foo[3].baz"))}, Unique: true,
|
|
}, IfNotExists: true}, false},
|
|
{"No name", "CREATE UNIQUE INDEX ON test (foo[3].baz)", &statement.CreateIndexStmt{
|
|
Info: database.IndexInfo{TableName: "test", Paths: []document.Path{document.Path(testutil.ParsePath(t, "foo[3].baz"))}, Unique: true}}, false},
|
|
{"No name with IF NOT EXISTS", "CREATE UNIQUE INDEX IF NOT EXISTS ON test (foo[3].baz)", nil, true},
|
|
{"More than 1 path", "CREATE INDEX idx ON test (foo, bar)",
|
|
&statement.CreateIndexStmt{
|
|
Info: database.IndexInfo{
|
|
IndexName: "idx",
|
|
TableName: "test",
|
|
Paths: []document.Path{
|
|
document.Path(testutil.ParsePath(t, "foo")),
|
|
document.Path(testutil.ParsePath(t, "bar")),
|
|
},
|
|
},
|
|
},
|
|
false},
|
|
{"No fields", "CREATE INDEX idx ON test", nil, true},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
q, err := parser.ParseQuery(test.s)
|
|
if test.errored {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
require.Len(t, q.Statements, 1)
|
|
require.EqualValues(t, test.expected, q.Statements[0])
|
|
})
|
|
}
|
|
}
|