diff --git a/db_test.go b/db_test.go index d4cc9de7..e0a8f315 100644 --- a/db_test.go +++ b/db_test.go @@ -86,7 +86,7 @@ func TestOpen(t *testing.T) { err = db.Exec(` CREATE TABLE tableA (a INTEGER UNIQUE NOT NULL, b (c (d DOUBLE PRIMARY KEY))); CREATE TABLE tableB (a TEXT NOT NULL DEFAULT 'hello', PRIMARY KEY (a)); - CREATE TABLE tableC; + CREATE TABLE tableC (a INTEGER, b BOOL); CREATE INDEX tableC_a_b_idx ON tableC(a, b); CREATE SEQUENCE seqD INCREMENT BY 10 CYCLE MINVALUE 100 NO MAXVALUE START 500; @@ -116,7 +116,7 @@ func TestOpen(t *testing.T) { `{"name":"tableA", "sql":"CREATE TABLE tableA (a INTEGER NOT NULL, b (c (d DOUBLE NOT NULL)), CONSTRAINT tableA_a_unique UNIQUE (a), CONSTRAINT tableA_pk PRIMARY KEY (b.c.d))", "namespace":10, "type":"table"}`, `{"name":"tableA_a_idx", "owner":{"table_name":"tableA", "paths":["a"]}, "sql":"CREATE UNIQUE INDEX tableA_a_idx ON tableA (a)", "namespace":11, "type":"index"}`, `{"name":"tableB", "sql":"CREATE TABLE tableB (a TEXT NOT NULL DEFAULT \"hello\", CONSTRAINT tableB_pk PRIMARY KEY (a))", "namespace":12, "type":"table"}`, - `{"name":"tableC", "docid_sequence_name":"tableC_seq", "sql":"CREATE TABLE tableC (...)", "namespace":13, "type":"table"}`, + `{"name":"tableC", "docid_sequence_name":"tableC_seq", "sql":"CREATE TABLE tableC (a INTEGER, b BOOLEAN)", "namespace":13, "type":"table"}`, `{"name":"tableC_a_b_idx", "owner":{"table_name":"tableC"}, "sql":"CREATE INDEX tableC_a_b_idx ON tableC (a, b)", "namespace":14, "type":"index"}`, `{"name":"tableC_seq", "owner":{"table_name":"tableC"}, "sql":"CREATE SEQUENCE tableC_seq CACHE 64", "type":"sequence"}`, } diff --git a/example_test.go b/example_test.go index ad92f63a..b744e5f5 100644 --- a/example_test.go +++ b/example_test.go @@ -27,7 +27,7 @@ func Example() { defer db.Close() // Create a table. Genji tables are schemaless by default, you don't need to specify a schema. - err = db.Exec("CREATE TABLE user") + err = db.Exec("CREATE TABLE user (name text, ...)") if err != nil { panic(err) } diff --git a/internal/database/catalog.go b/internal/database/catalog.go index e3c15b76..8e911069 100644 --- a/internal/database/catalog.go +++ b/internal/database/catalog.go @@ -275,11 +275,19 @@ func (c *Catalog) CreateIndex(tx *Transaction, info *IndexInfo) error { } // check if the associated table exists - _, err = c.GetTableInfo(info.Owner.TableName) + ti, err := c.GetTableInfo(info.Owner.TableName) if err != nil { return err } + // check if the indexed fields exist + for _, p := range info.Paths { + fc := ti.GetFieldConstraintForPath(p) + if fc == nil { + return errors.Errorf("field %q does not exist for table %q", p, ti.TableName) + } + } + info.StoreNamespace, err = c.generateStoreName(tx) if err != nil { return err diff --git a/internal/database/catalog_test.go b/internal/database/catalog_test.go index 688731a2..f318d095 100644 --- a/internal/database/catalog_test.go +++ b/internal/database/catalog_test.go @@ -294,6 +294,9 @@ func TestCatalogCreateIndex(t *testing.T) { updateCatalog(t, db, func(tx *database.Transaction, catalog *database.Catalog) error { return catalog.CreateTable(tx, "test", &database.TableInfo{ + FieldConstraints: database.MustNewFieldConstraints( + &database.FieldConstraint{Field: "a", Type: types.TextValue}, + ), TableConstraints: []*database.TableConstraint{ {Paths: []document.Path{testutil.ParseDocumentPath(t, "a")}, PrimaryKey: true}, }, @@ -321,7 +324,11 @@ func TestCatalogCreateIndex(t *testing.T) { db := testutil.NewTestDB(t) updateCatalog(t, db, func(tx *database.Transaction, catalog *database.Catalog) error { - return catalog.CreateTable(tx, "test", nil) + return catalog.CreateTable(tx, "test", &database.TableInfo{ + FieldConstraints: database.MustNewFieldConstraints( + &database.FieldConstraint{Field: "foo", Type: types.TextValue}, + ), + }) }) updateCatalog(t, db, func(tx *database.Transaction, catalog *database.Catalog) error { @@ -356,25 +363,37 @@ func TestCatalogCreateIndex(t *testing.T) { db := testutil.NewTestDB(t) updateCatalog(t, db, func(tx *database.Transaction, catalog *database.Catalog) error { - return catalog.CreateTable(tx, "test", nil) + return catalog.CreateTable(tx, "test", &database.TableInfo{ + FieldConstraints: database.MustNewFieldConstraints( + &database.FieldConstraint{Field: "foo", Type: types.DocumentValue, AnonymousType: &database.AnonymousType{ + FieldConstraints: database.MustNewFieldConstraints( + &database.FieldConstraint{Field: " bar ", Type: types.DocumentValue, AnonymousType: &database.AnonymousType{ + FieldConstraints: database.MustNewFieldConstraints( + &database.FieldConstraint{Field: "c", Type: types.TextValue}, + ), + }}, + ), + }}, + ), + }) }) updateCatalog(t, db, func(tx *database.Transaction, catalog *database.Catalog) error { err := catalog.CreateIndex(tx, &database.IndexInfo{ - Owner: database.Owner{TableName: "test"}, Paths: []document.Path{testutil.ParseDocumentPath(t, "foo.a[10].` bar `.c")}, + Owner: database.Owner{TableName: "test"}, Paths: []document.Path{testutil.ParseDocumentPath(t, "foo.` bar `.c")}, }) assert.NoError(t, err) - _, err = catalog.GetIndex(tx, "test_foo.a[10]. bar .c_idx") + _, err = catalog.GetIndex(tx, "test_foo. bar .c_idx") assert.NoError(t, err) // create another one err = catalog.CreateIndex(tx, &database.IndexInfo{ - Owner: database.Owner{TableName: "test"}, Paths: []document.Path{testutil.ParseDocumentPath(t, "foo.a[10].` bar `.c")}, + Owner: database.Owner{TableName: "test"}, Paths: []document.Path{testutil.ParseDocumentPath(t, "foo.` bar `.c")}, }) assert.NoError(t, err) - _, err = catalog.GetIndex(tx, "test_foo.a[10]. bar .c_idx1") + _, err = catalog.GetIndex(tx, "test_foo. bar .c_idx1") assert.NoError(t, err) return nil }) @@ -386,7 +405,12 @@ func TestTxDropIndex(t *testing.T) { db := testutil.NewTestDB(t) updateCatalog(t, db, func(tx *database.Transaction, catalog *database.Catalog) error { - err := catalog.CreateTable(tx, "test", nil) + err := catalog.CreateTable(tx, "test", &database.TableInfo{ + FieldConstraints: database.MustNewFieldConstraints( + &database.FieldConstraint{Field: "foo", Type: types.TextValue}, + &database.FieldConstraint{Field: "bar", Type: types.AnyValue}, + ), + }) assert.NoError(t, err) err = catalog.CreateIndex(tx, &database.IndexInfo{ IndexName: "idxFoo", Owner: database.Owner{TableName: "test"}, Paths: []document.Path{testutil.ParseDocumentPath(t, "foo")}, diff --git a/internal/database/info.go b/internal/database/info.go index 553b1121..46bbdc56 100644 --- a/internal/database/info.go +++ b/internal/database/info.go @@ -168,26 +168,7 @@ func (ti *TableInfo) GetPrimaryKey() *PrimaryKey { } func (ti *TableInfo) GetFieldConstraintForPath(p document.Path) *FieldConstraint { - var cur *FieldConstraints = &ti.FieldConstraints - for i := range p { - if cur == nil { - return nil - } - fc, ok := cur.ByField[p[i].FieldName] - if !ok { - return nil - } - if i == len(p)-1 { - return fc - } - - if fc.AnonymousType == nil { - return nil - } - cur = &fc.AnonymousType.FieldConstraints - } - - return nil + return ti.FieldConstraints.GetFieldConstraintForPath(p) } // String returns a SQL representation. diff --git a/internal/planner/optimizer_test.go b/internal/planner/optimizer_test.go index 0e954ff3..27ee589b 100644 --- a/internal/planner/optimizer_test.go +++ b/internal/planner/optimizer_test.go @@ -390,7 +390,7 @@ func TestSelectIndex_Simple(t *testing.T) { defer cleanup() testutil.MustExec(t, db, tx, ` - CREATE TABLE foo (k INT PRIMARY KEY, c INT); + CREATE TABLE foo (k INT PRIMARY KEY, a INT, b INT, c INT, d ANY); CREATE INDEX idx_foo_a ON foo(a); CREATE INDEX idx_foo_b ON foo(b); CREATE UNIQUE INDEX idx_foo_c ON foo(c); @@ -700,7 +700,7 @@ func TestSelectIndex_Composite(t *testing.T) { defer cleanup() testutil.MustExec(t, db, tx, ` - CREATE TABLE foo (k INT PRIMARY KEY, c INT); + CREATE TABLE foo (k INT PRIMARY KEY, a ANY, b ANY, c INT, d ANY, x ANY, y ANY, z ANY); CREATE INDEX idx_foo_a ON foo(a); CREATE INDEX idx_foo_b ON foo(b); CREATE UNIQUE INDEX idx_foo_c ON foo(c); @@ -754,10 +754,10 @@ func TestSelectIndex_Composite(t *testing.T) { testutil.MustExec(t, db, tx, ` CREATE TABLE foo ( k ARRAY PRIMARY KEY, - a ARRAY + a ARRAY, + b ANY ); CREATE INDEX idx_foo_a_b ON foo(a, b); - CREATE INDEX idx_foo_a0 ON foo(a[0]); INSERT INTO foo (k, a, b) VALUES ([1, 1], [1, 1], [1, 1]), ([2, 2], [2, 2], [2, 2]), @@ -848,8 +848,8 @@ func TestOptimize(t *testing.T) { db, tx, cleanup := testutil.NewTestTx(t) defer cleanup() testutil.MustExec(t, db, tx, ` - CREATE TABLE foo; - CREATE TABLE bar; + CREATE TABLE foo(a any, d any); + CREATE TABLE bar(a, d); CREATE INDEX idx_foo_a_d ON foo(a, d); CREATE INDEX idx_bar_a_d ON bar(a, d); `) diff --git a/internal/query/statement/create_test.go b/internal/query/statement/create_test.go index edf032a6..7fbad2c3 100644 --- a/internal/query/statement/create_test.go +++ b/internal/query/statement/create_test.go @@ -33,12 +33,12 @@ func TestCreateIndex(t *testing.T) { {"Basic", "CREATE INDEX idx ON test (foo)", false}, {"If not exists", "CREATE INDEX IF NOT EXISTS idx ON test (foo.bar)", false}, {"Duplicate", "CREATE INDEX idx ON test (foo.bar);CREATE INDEX idx ON test (foo.bar)", true}, - {"Unique", "CREATE UNIQUE INDEX IF NOT EXISTS idx ON test (foo[1])", false}, - {"No name", "CREATE UNIQUE INDEX ON test (foo[1])", false}, - {"No name if not exists", "CREATE UNIQUE INDEX IF NOT EXISTS ON test (foo[1])", true}, + {"Unique", "CREATE UNIQUE INDEX IF NOT EXISTS idx ON test (foo)", false}, + {"No name", "CREATE UNIQUE INDEX ON test (foo)", false}, + {"No name if not exists", "CREATE UNIQUE INDEX IF NOT EXISTS ON test (foo)", true}, {"No fields", "CREATE INDEX idx ON test", true}, - {"Composite (2)", "CREATE INDEX idx ON test (foo, bar)", false}, - {"Composite (4)", "CREATE INDEX idx ON test (foo, bar, baz, baf)", false}, + {"Composite (2)", "CREATE INDEX idx ON test (foo, baz)", false}, + {"Composite (3)", "CREATE INDEX idx ON test (foo, baz, baf)", false}, } for _, test := range tests { @@ -46,7 +46,7 @@ func TestCreateIndex(t *testing.T) { db, tx, cleanup := testutil.NewTestTx(t) defer cleanup() - testutil.MustExec(t, db, tx, "CREATE TABLE test") + testutil.MustExec(t, db, tx, "CREATE TABLE test(foo (bar TEXT), baz any, baf any)") err := testutil.Exec(db, tx, test.query) if test.fails { diff --git a/internal/query/statement/explain_test.go b/internal/query/statement/explain_test.go index 353c97fb..75d1aa2b 100644 --- a/internal/query/statement/explain_test.go +++ b/internal/query/statement/explain_test.go @@ -46,7 +46,7 @@ func TestExplainStmt(t *testing.T) { assert.NoError(t, err) defer db.Close() - err = db.Exec("CREATE TABLE test (k INTEGER PRIMARY KEY)") + err = db.Exec("CREATE TABLE test (k INTEGER PRIMARY KEY, a any, b any, x any, y any)") assert.NoError(t, err) err = db.Exec(` CREATE INDEX idx_a ON test (a); diff --git a/internal/query/statement/insert_test.go b/internal/query/statement/insert_test.go index a461e987..1a77c14f 100644 --- a/internal/query/statement/insert_test.go +++ b/internal/query/statement/insert_test.go @@ -34,7 +34,7 @@ func TestInsertStmt(t *testing.T) { assert.NoError(t, err) defer db.Close() - err = db.Exec("CREATE TABLE test") + err = db.Exec("CREATE TABLE test(a any, b any, c any)") assert.NoError(t, err) if withIndexes { err = db.Exec(` diff --git a/internal/query/statement/reindex_test.go b/internal/query/statement/reindex_test.go index 56c67015..60a22a6a 100644 --- a/internal/query/statement/reindex_test.go +++ b/internal/query/statement/reindex_test.go @@ -29,8 +29,8 @@ func TestReIndex(t *testing.T) { defer cleanup() testutil.MustExec(t, db, tx, ` - CREATE TABLE test1; - CREATE TABLE test2; + CREATE TABLE test1(a any, b any); + CREATE TABLE test2(a any, b any); CREATE INDEX idx_test1_a ON test1(a); CREATE INDEX idx_test1_b ON test1(b); diff --git a/internal/query/statement/select_test.go b/internal/query/statement/select_test.go index d301a193..5de5b964 100644 --- a/internal/query/statement/select_test.go +++ b/internal/query/statement/select_test.go @@ -236,7 +236,7 @@ func TestSelectStmt(t *testing.T) { assert.NoError(t, err) defer db.Close() - err = db.Exec("CREATE TABLE test; CREATE INDEX idx_foo ON test(foo);") + err = db.Exec("CREATE TABLE test(foo any); CREATE INDEX idx_foo ON test(foo);") assert.NoError(t, err) err = db.Exec(`INSERT INTO test (foo) VALUES (1), ('hello'), (2), (true)`) @@ -293,7 +293,7 @@ func TestSelectStmt(t *testing.T) { defer db.Close() err = db.Exec(` - CREATE TABLE test; + CREATE TABLE test(a any); INSERT INTO test (a) VALUES ([1,2,3]), ([4, 5, 6]); `) assert.NoError(t, err) diff --git a/internal/testutil/db.go b/internal/testutil/db.go index fea1a710..61708cb6 100644 --- a/internal/testutil/db.go +++ b/internal/testutil/db.go @@ -138,6 +138,8 @@ func Query(db *database.Database, tx *database.Transaction, q string, params ... } func MustExec(t *testing.T, db *database.Database, tx *database.Transaction, q string, params ...environment.Param) { + t.Helper() + err := Exec(db, tx, q, params...) assert.NoError(t, err) } diff --git a/sqltests/CREATE_INDEX/base.sql b/sqltests/CREATE_INDEX/base.sql index fa2851c1..668bec02 100644 --- a/sqltests/CREATE_INDEX/base.sql +++ b/sqltests/CREATE_INDEX/base.sql @@ -1,5 +1,5 @@ -- setup: -CREATE TABLE test; +CREATE TABLE test (a int); -- test: named index CREATE INDEX test_a_idx ON test(a); diff --git a/sqltests/CREATE_INDEX/undeclared.sql b/sqltests/CREATE_INDEX/undeclared.sql new file mode 100644 index 00000000..9f3469b7 --- /dev/null +++ b/sqltests/CREATE_INDEX/undeclared.sql @@ -0,0 +1,19 @@ +-- test: undeclared field +CREATE TABLE test; +CREATE INDEX test_a_idx ON test(a); +-- error: + +-- test: undeclared field: IF NOT EXISTS +CREATE TABLE test; +CREATE INDEX IF NOT EXISTS test_a_idx ON test(a); +-- error: + +-- test: undeclared field: other fields +CREATE TABLE test(b int); +CREATE INDEX test_a_idx ON test(a); +-- error: + +-- test: undeclared field: variadic +CREATE TABLE test(b int, ...); +CREATE INDEX test_a_idx ON test(a); +-- error: diff --git a/sqltests/SELECT/order_by.sql b/sqltests/SELECT/order_by.sql index d146d42c..c77f3378 100644 --- a/sqltests/SELECT/order_by.sql +++ b/sqltests/SELECT/order_by.sql @@ -1,5 +1,5 @@ -- setup: -CREATE TABLE test; +CREATE TABLE test(a double); INSERT INTO test (a) VALUES (1), (2), (3); -- suite: no index diff --git a/sqltests/SELECT/projection_table.sql b/sqltests/SELECT/projection_table.sql index 787c17e3..82a51b0c 100644 --- a/sqltests/SELECT/projection_table.sql +++ b/sqltests/SELECT/projection_table.sql @@ -1,5 +1,5 @@ -- setup: -CREATE TABLE test; +CREATE TABLE test(a double, ...); INSERT INTO test(a, b, c) VALUES (1, {a: 1}, [true]); -- suite: no index