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