diff --git a/.github/workflows/long_fuzz_tests_btree.yml b/.github/workflows/long_fuzz_tests_btree.yml index e27d8eb8a..69732c6cf 100644 --- a/.github/workflows/long_fuzz_tests_btree.yml +++ b/.github/workflows/long_fuzz_tests_btree.yml @@ -28,6 +28,10 @@ jobs: run: cargo test -- --ignored fuzz_long env: RUST_BACKTRACE: 1 + - name: Run ignored long tests with index + run: cargo test --features index_experimental -- --ignored fuzz_long + env: + RUST_BACKTRACE: 1 simple-stress-test: runs-on: blacksmith-4vcpu-ubuntu-2404 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d38006d4c..3bacece0a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -41,6 +41,11 @@ jobs: RUST_LOG: ${{ runner.debug && 'limbo_core::storage=trace' || '' }} run: cargo test --verbose timeout-minutes: 20 + - name: Tests with indexes + env: + RUST_LOG: ${{ runner.debug && 'limbo_core::storage=trace' || '' }} + run: cargo test --verbose --features index_experimental + timeout-minutes: 20 clippy: @@ -93,7 +98,10 @@ jobs: - name: Test run: make test timeout-minutes: 20 - + - uses: "./.github/shared/install_sqlite" + - name: Test with index enabled + run: SQLITE_EXEC="scripts/limbo-sqlite3-index-experimental" make test + timeout-minutes: 20 test-sqlite: runs-on: blacksmith-4vcpu-ubuntu-2404 steps: diff --git a/Makefile b/Makefile index f13c23c4d..5bb117f92 100644 --- a/Makefile +++ b/Makefile @@ -36,6 +36,7 @@ check-wasm-target: limbo: cargo build + cargo build --features index_experimental --bin limbo_index_experimental .PHONY: limbo limbo-c: @@ -93,19 +94,35 @@ test-memory: limbo uv-sync .PHONY: test-memory test-write: limbo uv-sync - SQLITE_EXEC=$(SQLITE_EXEC) uv run --project limbo_test test-write + @if [ "$(SQLITE_EXEC)" != "scripts/limbo-sqlite3" ]; then \ + SQLITE_EXEC=$(SQLITE_EXEC) uv run --project limbo_test test-write; \ + else \ + echo "Skipping test-write: SQLITE_EXEC does not have indexes scripts/limbo-sqlite3"; \ + fi .PHONY: test-write test-update: limbo uv-sync - SQLITE_EXEC=$(SQLITE_EXEC) uv run --project limbo_test test-update + @if [ "$(SQLITE_EXEC)" != "scripts/limbo-sqlite3" ]; then \ + SQLITE_EXEC=$(SQLITE_EXEC) uv run --project limbo_test test-update; \ + else \ + echo "Skipping test-update: SQLITE_EXEC does not have indexes scripts/limbo-sqlite3"; \ + fi .PHONY: test-update test-collate: limbo uv-sync - SQLITE_EXEC=$(SQLITE_EXEC) uv run --project limbo_test test-collate + @if [ "$(SQLITE_EXEC)" != "scripts/limbo-sqlite3" ]; then \ + SQLITE_EXEC=$(SQLITE_EXEC) uv run --project limbo_test test-collate; \ + else \ + echo "Skipping test-collate: SQLITE_EXEC does not have indexes scripts/limbo-sqlite3"; \ + fi .PHONY: test-collate test-constraint: limbo uv-sync - SQLITE_EXEC=$(SQLITE_EXEC) uv run --project limbo_test test-constraint + @if [ "$(SQLITE_EXEC)" != "scripts/limbo-sqlite3" ]; then \ + SQLITE_EXEC=$(SQLITE_EXEC) uv run --project limbo_test test-constraint; \ + else \ + echo "Skipping test-constraint: SQLITE_EXEC does not have indexes scripts/limbo-sqlite3"; \ + fi .PHONY: test-constraint bench-vfs: uv-sync diff --git a/bindings/java/src/test/java/tech/turso/IntegrationTest.java b/bindings/java/src/test/java/tech/turso/IntegrationTest.java index 7d40deb1a..26394c258 100644 --- a/bindings/java/src/test/java/tech/turso/IntegrationTest.java +++ b/bindings/java/src/test/java/tech/turso/IntegrationTest.java @@ -22,7 +22,7 @@ public class IntegrationTest { @Test void create_table_multi_inserts_select() throws Exception { Statement stmt = createDefaultStatement(); - stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);"); + stmt.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT);"); stmt.execute("INSERT INTO users VALUES (1, 'seonwoo');"); stmt.execute("INSERT INTO users VALUES (2, 'seonwoo');"); stmt.execute("INSERT INTO users VALUES (3, 'seonwoo');"); diff --git a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4ConnectionTest.java b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4ConnectionTest.java index 19f91d787..516c49613 100644 --- a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4ConnectionTest.java +++ b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4ConnectionTest.java @@ -63,7 +63,7 @@ class JDBC4ConnectionTest { @Test void prepare_simple_create_table() throws Exception { - connection.prepare("CREATE TABLE users (id INT PRIMARY KEY, username TEXT)"); + connection.prepare("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT)"); } @Test diff --git a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4ResultSetTest.java b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4ResultSetTest.java index d41447694..6e4a37694 100644 --- a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4ResultSetTest.java +++ b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4ResultSetTest.java @@ -38,7 +38,7 @@ class JDBC4ResultSetTest { @Test void invoking_next_before_the_last_row_should_return_true() throws Exception { - stmt.executeUpdate("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);"); + stmt.executeUpdate("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT);"); stmt.executeUpdate("INSERT INTO users VALUES (1, 'sinwoo');"); stmt.executeUpdate("INSERT INTO users VALUES (2, 'seonwoo');"); @@ -51,7 +51,7 @@ class JDBC4ResultSetTest { @Test void invoking_next_after_the_last_row_should_return_false() throws Exception { - stmt.executeUpdate("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);"); + stmt.executeUpdate("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT);"); stmt.executeUpdate("INSERT INTO users VALUES (1, 'sinwoo');"); stmt.executeUpdate("INSERT INTO users VALUES (2, 'seonwoo');"); diff --git a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java index 1f75725bc..35da96832 100644 --- a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java +++ b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java @@ -32,25 +32,25 @@ class JDBC4StatementTest { @Test void execute_ddl_should_return_false() throws Exception { - assertFalse(stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);")); + assertFalse(stmt.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT);")); } @Test void execute_insert_should_return_false() throws Exception { - stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);"); + stmt.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT);"); assertFalse(stmt.execute("INSERT INTO users VALUES (1, 'limbo');")); } @Test void execute_update_should_return_false() throws Exception { - stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);"); + stmt.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT);"); stmt.execute("INSERT INTO users VALUES (1, 'limbo');"); assertFalse(stmt.execute("UPDATE users SET username = 'seonwoo' WHERE id = 1;")); } @Test void execute_select_should_return_true() throws Exception { - stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);"); + stmt.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT);"); stmt.execute("INSERT INTO users VALUES (1, 'limbo');"); assertTrue(stmt.execute("SELECT * FROM users;")); } diff --git a/bindings/python/tests/test_database.py b/bindings/python/tests/test_database.py index ec565a898..9ef047e19 100644 --- a/bindings/python/tests/test_database.py +++ b/bindings/python/tests/test_database.py @@ -79,7 +79,7 @@ def test_fetchall_select_user_ids(provider): def test_in_memory_fetchone_select_all_users(provider): conn = connect(provider, ":memory:") cursor = conn.cursor() - cursor.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT)") + cursor.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT)") cursor.execute("INSERT INTO users VALUES (1, 'alice')") cursor.execute("SELECT * FROM users") diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 9f317b639..7497c95f1 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -17,6 +17,11 @@ dist = true name = "limbo" path = "main.rs" +[[bin]] +name = "limbo_index_experimental" +path = "main.rs" +required-features = ["index_experimental"] + [dependencies] anyhow.workspace = true @@ -51,6 +56,7 @@ toml_edit = {version = "0.22.24", features = ["serde"]} [features] default = ["io_uring"] io_uring = ["limbo_core/io_uring"] +index_experimental = ["limbo_core/index_experimental"] [build-dependencies] syntect = "5.2.0" diff --git a/core/Cargo.toml b/core/Cargo.toml index 9673497a7..ceb855561 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -15,6 +15,7 @@ path = "lib.rs" [features] default = ["fs", "uuid", "time", "json", "static"] +index_experimental = [] fs = ["limbo_ext/vfs"] json = [] uuid = ["limbo_uuid/static"] diff --git a/core/schema.rs b/core/schema.rs index 3535c9237..c24fe5ef9 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -19,20 +19,33 @@ const SCHEMA_TABLE_NAME_ALT: &str = "sqlite_master"; pub struct Schema { pub tables: HashMap>, - // table_name to list of indexes for the table + /// table_name to list of indexes for the table pub indexes: HashMap>>, + /// Used for index_experimental feature flag to track whether a table has an index. + /// This is necessary because we won't populate indexes so that we don't use them but + /// we still need to know if a table has an index to disallow any write operation that requires + /// indexes. + #[cfg(not(feature = "index_experimental"))] + pub has_indexes: std::collections::HashSet, } impl Schema { pub fn new() -> Self { let mut tables: HashMap> = HashMap::new(); + #[cfg(not(feature = "index_experimental"))] + let has_indexes = std::collections::HashSet::new(); let indexes: HashMap>> = HashMap::new(); #[allow(clippy::arc_with_non_send_sync)] tables.insert( SCHEMA_TABLE_NAME.to_string(), Arc::new(Table::BTree(sqlite_schema_table().into())), ); - Self { tables, indexes } + Self { + tables, + indexes, + #[cfg(not(feature = "index_experimental"))] + has_indexes, + } } pub fn is_unique_idx_name(&self, name: &str) -> bool { @@ -76,6 +89,7 @@ impl Schema { } } + #[cfg(feature = "index_experimental")] pub fn add_index(&mut self, index: Arc) { let table_name = normalize_ident(&index.table_name); self.indexes @@ -111,6 +125,16 @@ impl Schema { .expect("Must have the index") .retain_mut(|other_idx| other_idx.name != idx.name); } + + #[cfg(not(feature = "index_experimental"))] + pub fn table_has_indexes(&self, table_name: &str) -> bool { + self.has_indexes.contains(table_name) + } + + #[cfg(not(feature = "index_experimental"))] + pub fn table_set_has_index(&mut self, table_name: &str) { + self.has_indexes.insert(table_name.to_string()); + } } #[derive(Clone, Debug)] diff --git a/core/storage/btree.rs b/core/storage/btree.rs index 688e6f63c..8721b767d 100644 --- a/core/storage/btree.rs +++ b/core/storage/btree.rs @@ -7097,6 +7097,7 @@ mod tests { } } + #[cfg(feature = "index_experimental")] fn btree_index_insert_fuzz_run(attempts: usize, inserts: usize) { let (mut rng, seed) = if std::env::var("SEED").is_ok() { let seed = std::env::var("SEED").unwrap(); @@ -7282,6 +7283,7 @@ mod tests { } #[test] + #[cfg(feature = "index_experimental")] pub fn btree_index_insert_fuzz_run_equal_size() { btree_index_insert_fuzz_run(2, 1024); } @@ -7317,6 +7319,7 @@ mod tests { #[test] #[ignore] + #[cfg(feature = "index_experimental")] pub fn fuzz_long_btree_index_insert_fuzz_run_equal_size() { btree_index_insert_fuzz_run(2, 10_000); } diff --git a/core/translate/alter.rs b/core/translate/alter.rs index 39d158127..c25621bfa 100644 --- a/core/translate/alter.rs +++ b/core/translate/alter.rs @@ -24,6 +24,16 @@ pub fn translate_alter_table( ) -> Result { let (table_name, alter_table) = alter; let ast::Name(table_name) = table_name.name; + #[cfg(not(feature = "index_experimental"))] + { + if schema.table_has_indexes(&table_name) && cfg!(not(feature = "index_experimental")) { + // Let's disable altering a table with indices altogether instead of checking column by + // column to be extra safe. + crate::bail_parse_error!( + "Alter table disabled for table with indexes without index_experimental feature flag" + ); + } + } let Some(original_btree) = schema .get_table(&table_name) diff --git a/core/translate/compound_select.rs b/core/translate/compound_select.rs index 40051485d..56389d39f 100644 --- a/core/translate/compound_select.rs +++ b/core/translate/compound_select.rs @@ -154,8 +154,12 @@ fn emit_compound_select( (cursor_id, index.clone()) } _ => { - new_dedupe_index = true; - create_union_dedupe_index(program, &right_most) + if cfg!(not(feature = "index_experimental")) { + crate::bail_parse_error!("UNION not supported without indexes"); + } else { + new_dedupe_index = true; + create_union_dedupe_index(program, &right_most) + } } }; plan.query_destination = QueryDestination::EphemeralIndex { diff --git a/core/translate/delete.rs b/core/translate/delete.rs index 1ddf25ece..484b46527 100644 --- a/core/translate/delete.rs +++ b/core/translate/delete.rs @@ -18,6 +18,16 @@ pub fn translate_delete( syms: &SymbolTable, mut program: ProgramBuilder, ) -> Result { + #[cfg(not(feature = "index_experimental"))] + { + if schema.table_has_indexes(&tbl_name.name.to_string()) { + // Let's disable altering a table with indices altogether instead of checking column by + // column to be extra safe. + crate::bail_parse_error!( + "DELETE into table disabled for table with indexes and without index_experimental feature flag" + ); + } + } let mut delete_plan = prepare_delete_plan( schema, tbl_name, diff --git a/core/translate/index.rs b/core/translate/index.rs index a645582f2..55d8f4a18 100644 --- a/core/translate/index.rs +++ b/core/translate/index.rs @@ -23,6 +23,9 @@ pub fn translate_create_index( schema: &Schema, mut program: ProgramBuilder, ) -> crate::Result { + if cfg!(not(feature = "index_experimental")) { + crate::bail_parse_error!("CREATE INDEX enabled only with index_experimental feature"); + } let idx_name = normalize_ident(idx_name); let tbl_name = normalize_ident(tbl_name); let opts = crate::vdbe::builder::ProgramBuilderOpts { @@ -296,6 +299,9 @@ pub fn translate_drop_index( schema: &Schema, mut program: ProgramBuilder, ) -> crate::Result { + if cfg!(not(feature = "index_experimental")) { + crate::bail_parse_error!("DROP INDEX enabled only with index_experimental feature"); + } let idx_name = normalize_ident(idx_name); let opts = crate::vdbe::builder::ProgramBuilderOpts { query_mode: mode, diff --git a/core/translate/insert.rs b/core/translate/insert.rs index 342ef6b50..7855d14b1 100644 --- a/core/translate/insert.rs +++ b/core/translate/insert.rs @@ -58,6 +58,16 @@ pub fn translate_insert( crate::bail_parse_error!("ON CONFLICT clause is not supported"); } + #[cfg(not(feature = "index_experimental"))] + { + if schema.table_has_indexes(&tbl_name.name.to_string()) { + // Let's disable altering a table with indices altogether instead of checking column by + // column to be extra safe. + crate::bail_parse_error!( + "INSERT table disabled for table with indexes and without index_experimental feature flag" + ); + } + } let table_name = &tbl_name.name; let table = match schema.get_table(table_name.0.as_str()) { Some(table) => table, diff --git a/core/translate/optimizer/mod.rs b/core/translate/optimizer/mod.rs index 160ba4361..999230d2f 100644 --- a/core/translate/optimizer/mod.rs +++ b/core/translate/optimizer/mod.rs @@ -241,15 +241,19 @@ fn optimize_table_access( let table_idx = join_order_member.original_idx; let access_method = &access_methods_arena.borrow()[best_access_methods[i]]; if access_method.is_scan() { - let is_leftmost_table = i == 0; - let uses_index = access_method.index.is_some(); - let source_table_is_from_clause_subquery = matches!( - &joined_tables[table_idx].table, - Table::FromClauseSubquery(_) - ); + #[cfg(feature = "index_experimental")] + let try_to_build_ephemeral_index = { + let is_leftmost_table = i == 0; + let uses_index = access_method.index.is_some(); + let source_table_is_from_clause_subquery = matches!( + &joined_tables[table_idx].table, + Table::FromClauseSubquery(_) + ); + !is_leftmost_table && !uses_index && !source_table_is_from_clause_subquery + }; + #[cfg(not(feature = "index_experimental"))] + let try_to_build_ephemeral_index = false; - let try_to_build_ephemeral_index = - !is_leftmost_table && !uses_index && !source_table_is_from_clause_subquery; if !try_to_build_ephemeral_index { joined_tables[table_idx].op = Operation::Scan { iter_dir: access_method.iter_dir, diff --git a/core/translate/planner.rs b/core/translate/planner.rs index aa21e8a34..77af53126 100644 --- a/core/translate/planner.rs +++ b/core/translate/planner.rs @@ -50,6 +50,14 @@ pub fn resolve_aggregates(top_level_expr: &Expr, aggs: &mut Vec) -> R { Ok(Func::Agg(f)) => { let distinctness = Distinctness::from_ast(distinctness.as_ref()); + #[cfg(not(feature = "index_experimental"))] + { + if distinctness.is_distinct() { + crate::bail_parse_error!( + "SELECT with DISTINCT is not allowed without indexes enabled" + ); + } + } let num_args = args.as_ref().map_or(0, |args| args.len()); if distinctness.is_distinct() && num_args != 1 { crate::bail_parse_error!( diff --git a/core/translate/schema.rs b/core/translate/schema.rs index 7b0bfe907..4770ed976 100644 --- a/core/translate/schema.rs +++ b/core/translate/schema.rs @@ -93,6 +93,9 @@ pub fn translate_create_table( let index_regs = check_automatic_pk_index_required(&body, &mut program, &tbl_name.name.0)?; if let Some(index_regs) = index_regs.as_ref() { + if cfg!(not(feature = "index_experimental")) { + bail_parse_error!("Constraints UNIQUE and PRIMARY KEY (unless INTEGER PRIMARY KEY) on table are not supported without indexes"); + } for index_reg in index_regs.clone() { program.emit_insn(Insn::CreateBtree { db: 0, @@ -610,6 +613,14 @@ pub fn translate_drop_table( schema: &Schema, mut program: ProgramBuilder, ) -> Result { + #[cfg(not(feature = "index_experimental"))] + { + if schema.table_has_indexes(&tbl_name.name.to_string()) { + bail_parse_error!( + "DROP Table with indexes on the table enabled only with index_experimental feature" + ); + } + } let opts = ProgramBuilderOpts { query_mode, num_cursors: 3, diff --git a/core/translate/select.rs b/core/translate/select.rs index 85469be64..bda9dabe2 100644 --- a/core/translate/select.rs +++ b/core/translate/select.rs @@ -202,6 +202,14 @@ fn prepare_one_select_plan<'a>( distinctness, .. } = *select_inner; + #[cfg(not(feature = "index_experimental"))] + { + if distinctness.is_some() { + crate::bail_parse_error!( + "SELECT with DISTINCT is not allowed without indexes enabled" + ); + } + } let col_count = columns.len(); if col_count == 0 { crate::bail_parse_error!("SELECT without columns is not allowed"); @@ -336,6 +344,15 @@ fn prepare_one_select_plan<'a>( 0 }; let distinctness = Distinctness::from_ast(distinctness.as_ref()); + + #[cfg(not(feature = "index_experimental"))] + { + if distinctness.is_distinct() { + crate::bail_parse_error!( + "SELECT with DISTINCT is not allowed without indexes enabled" + ); + } + } if distinctness.is_distinct() && args_count != 1 { crate::bail_parse_error!("DISTINCT aggregate functions must have exactly one argument"); } diff --git a/core/translate/update.rs b/core/translate/update.rs index d4af63b0e..89f0de71c 100644 --- a/core/translate/update.rs +++ b/core/translate/update.rs @@ -101,6 +101,16 @@ pub fn prepare_update_plan( bail_parse_error!("ON CONFLICT clause is not supported"); } let table_name = &body.tbl_name.name; + #[cfg(not(feature = "index_experimental"))] + { + if schema.table_has_indexes(&table_name.to_string()) { + // Let's disable altering a table with indices altogether instead of checking column by + // column to be extra safe. + bail_parse_error!( + "UPDATE table disabled for table with indexes and without index_experimental feature flag" + ); + } + } let table = match schema.get_table(table_name.0.as_str()) { Some(table) => table, None => bail_parse_error!("Parse error: no such table: {}", table_name), diff --git a/core/util.rs b/core/util.rs index 33af5dbc6..b1489333e 100644 --- a/core/util.rs +++ b/core/util.rs @@ -136,24 +136,37 @@ pub fn parse_schema_rows( StepResult::Busy => break, } } - for UnparsedFromSqlIndex { - table_name, - root_page, - sql, - } in from_sql_indexes - { - let table = schema.get_btree_table(&table_name).unwrap(); - let index = schema::Index::from_sql(&sql, root_page as usize, table.as_ref())?; - schema.add_index(Arc::new(index)); - } - for (table_name, indices) in automatic_indices { - let table = schema.get_btree_table(&table_name).unwrap(); - let ret_index = - schema::Index::automatic_from_primary_key_and_unique(table.as_ref(), indices)?; - for index in ret_index { + for unparsed_sql_from_index in from_sql_indexes { + #[cfg(not(feature = "index_experimental"))] + schema.table_set_has_index(&unparsed_sql_from_index.table_name); + #[cfg(feature = "index_experimental")] + { + let table = schema + .get_btree_table(&unparsed_sql_from_index.table_name) + .unwrap(); + let index = schema::Index::from_sql( + &unparsed_sql_from_index.sql, + unparsed_sql_from_index.root_page as usize, + table.as_ref(), + )?; schema.add_index(Arc::new(index)); } } + for automatic_index in automatic_indices { + #[cfg(not(feature = "index_experimental"))] + schema.table_set_has_index(&automatic_index.0); + #[cfg(feature = "index_experimental")] + { + let table = schema.get_btree_table(&automatic_index.0).unwrap(); + let ret_index = schema::Index::automatic_from_primary_key_and_unique( + table.as_ref(), + automatic_index.1, + )?; + for index in ret_index { + schema.add_index(Arc::new(index)); + } + } + } } Ok(()) } diff --git a/scripts/limbo-sqlite3-index-experimental b/scripts/limbo-sqlite3-index-experimental new file mode 100755 index 000000000..743a07e24 --- /dev/null +++ b/scripts/limbo-sqlite3-index-experimental @@ -0,0 +1,8 @@ +#!/bin/bash + +# if RUST_LOG is non-empty, enable tracing output +if [ -n "$RUST_LOG" ]; then + target/debug/limbo_index_experimental -m list -t testing/test.log "$@" +else + target/debug/limbo_index_experimental -m list "$@" +fi diff --git a/testing/agg-functions.test b/testing/agg-functions.test index c9169906b..50c3f44e2 100755 --- a/testing/agg-functions.test +++ b/testing/agg-functions.test @@ -128,6 +128,8 @@ do_execsql_test select-agg-json-array-object { SELECT json_group_array(json_object('name', name)) FROM products; } {[{"name":"hat"},{"name":"cap"},{"name":"shirt"},{"name":"sweater"},{"name":"sweatshirt"},{"name":"shorts"},{"name":"jeans"},{"name":"sneakers"},{"name":"boots"},{"name":"coat"},{"name":"accessories"}]} -do_execsql_test select-distinct-agg-functions { - SELECT sum(distinct age), count(distinct age), avg(distinct age) FROM users; -} {5050|100|50.5} \ No newline at end of file +if {[info exists ::env(SQLITE_EXEC)] && ($::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental" || $::env(SQLITE_EXEC) eq "sqlite3")} { + do_execsql_test select-distinct-agg-functions { + SELECT sum(distinct age), count(distinct age), avg(distinct age) FROM users; + } {5050|100|50.5} +} diff --git a/testing/cli_tests/test_limbo_cli.py b/testing/cli_tests/test_limbo_cli.py index c44c5322a..2e0f21ad3 100755 --- a/testing/cli_tests/test_limbo_cli.py +++ b/testing/cli_tests/test_limbo_cli.py @@ -107,7 +107,9 @@ class TestLimboShell: flags="", ): if exec_name is None: - exec_name = "./scripts/limbo-sqlite3" + exec_name = os.environ.get('SQLITE_EXEC') + if exec_name is None: + exec_name = "./scripts/limbo-sqlite3" if flags == "": flags = "-q" self.config = ShellConfig(exe_name=exec_name, flags=flags) diff --git a/testing/create_table.test b/testing/create_table.test index 858914f38..f5c4edca9 100755 --- a/testing/create_table.test +++ b/testing/create_table.test @@ -3,14 +3,16 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl -do_execsql_test_in_memory_any_error create_table_one_unique_set { - CREATE TABLE t4(a, unique(b)); +if {[info exists ::env(SQLITE_EXEC)] && ($::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental" || $::env(SQLITE_EXEC) eq "sqlite3")} { + do_execsql_test_in_memory_any_error create_table_one_unique_set { + CREATE TABLE t4(a, unique(b)); + } + + do_execsql_test_on_specific_db {:memory:} create_table_same_uniques_and_primary_keys { + CREATE TABLE t2(a,b, unique(a,b), primary key(a,b)); + } {} + + do_execsql_test_on_specific_db {:memory:} create_table_unique_contained_in_primary_keys { + CREATE TABLE t4(a,b, primary key(a,b), unique(a)); + } {} } - -do_execsql_test_on_specific_db {:memory:} create_table_same_uniques_and_primary_keys { - CREATE TABLE t2(a,b, unique(a,b), primary key(a,b)); -} {} - -do_execsql_test_on_specific_db {:memory:} create_table_unique_contained_in_primary_keys { - CREATE TABLE t4(a,b, primary key(a,b), unique(a)); -} {} diff --git a/testing/delete.test b/testing/delete.test index ec5b2f4db..83e523295 100644 --- a/testing/delete.test +++ b/testing/delete.test @@ -52,11 +52,13 @@ do_execsql_test_on_specific_db {:memory:} delete-reuse-1 { } {1 2 3} # Test delete works when there are indexes -do_execsql_test_on_specific_db {:memory:} delete-all-with-indexes-1 { - CREATE TABLE t(a PRIMARY KEY); - CREATE INDEX tasc ON t(a); - CREATE INDEX tdesc ON t(a DESC); - INSERT INTO t VALUES (randomblob(1000)); - DELETE FROM t; - SELECT * FROM t; -} {} +if {[info exists ::env(SQLITE_EXEC)] && $::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental"} { + do_execsql_test_on_specific_db {:memory:} delete-all-with-indexes-1 { + CREATE TABLE t(a PRIMARY KEY); + CREATE INDEX tasc ON t(a); + CREATE INDEX tdesc ON t(a DESC); + INSERT INTO t VALUES (randomblob(1000)); + DELETE FROM t; + SELECT * FROM t; + } {} +} diff --git a/testing/drop_index.test b/testing/drop_index.test index 63c457bec..6c37fb43e 100755 --- a/testing/drop_index.test +++ b/testing/drop_index.test @@ -3,44 +3,46 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl -# Basic DROP INDEX functionality -do_execsql_test_on_specific_db {:memory:} drop-index-basic-1 { - CREATE TABLE t1(x INTEGER PRIMARY KEY); - CREATE INDEX t_idx on t1 (x); - INSERT INTO t1 VALUES (1); - INSERT INTO t1 VALUES (2); - DROP INDEX t_idx; - SELECT count(*) FROM sqlite_schema WHERE type='index' AND name='t_idx'; -} {0} +if {[info exists ::env(SQLITE_EXEC)] && ($::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental" || $::env(SQLITE_EXEC) eq "sqlite3")} { + # Basic DROP INDEX functionality + do_execsql_test_on_specific_db {:memory:} drop-index-basic-1 { + CREATE TABLE t1(x INTEGER PRIMARY KEY); + CREATE INDEX t_idx on t1 (x); + INSERT INTO t1 VALUES (1); + INSERT INTO t1 VALUES (2); + DROP INDEX t_idx; + SELECT count(*) FROM sqlite_schema WHERE type='index' AND name='t_idx'; + } {0} -# Test DROP INDEX IF EXISTS on existing index -do_execsql_test_on_specific_db {:memory:} drop-index-if-exists-1 { - CREATE TABLE t2(x INTEGER PRIMARY KEY); - CREATE INDEX t_idx2 on t2 (x); - DROP INDEX IF EXISTS t_idx2; - SELECT count(*) FROM sqlite_schema WHERE type='index' AND name='t_idx2'; -} {0} + # Test DROP INDEX IF EXISTS on existing index + do_execsql_test_on_specific_db {:memory:} drop-index-if-exists-1 { + CREATE TABLE t2(x INTEGER PRIMARY KEY); + CREATE INDEX t_idx2 on t2 (x); + DROP INDEX IF EXISTS t_idx2; + SELECT count(*) FROM sqlite_schema WHERE type='index' AND name='t_idx2'; + } {0} -# Test DROP INDEX IF EXISTS on non-existent index -do_execsql_test_on_specific_db {:memory:} drop-index-if-exists-2 { - DROP TABLE IF EXISTS nonexistent_index; - SELECT 'success'; -} {success} + # Test DROP INDEX IF EXISTS on non-existent index + do_execsql_test_on_specific_db {:memory:} drop-index-if-exists-2 { + DROP TABLE IF EXISTS nonexistent_index; + SELECT 'success'; + } {success} -# Test dropping non-existant index produces an error -do_execsql_test_error_content drop-index-no-index { - DROP INDEX t_idx; -} {"No such index: t_idx"} + # Test dropping non-existant index produces an error + do_execsql_test_error_content drop-index-no-index { + DROP INDEX t_idx; + } {"No such index: t_idx"} -# Test dropping index after multiple inserts and deletes -do_execsql_test_on_specific_db {:memory:} drop-index-after-ops-1 { - CREATE TABLE t6(x INTEGER PRIMARY KEY); - CREATE INDEX t_idx6 on t6 (x); - INSERT INTO t6 VALUES (1); - INSERT INTO t6 VALUES (2); - DELETE FROM t6 WHERE x = 1; - INSERT INTO t6 VALUES (3); - DROP INDEX t_idx6; - SELECT count(*) FROM sqlite_schema WHERE type='index' AND name='t_idx6'; -} {0} + # Test dropping index after multiple inserts and deletes + do_execsql_test_on_specific_db {:memory:} drop-index-after-ops-1 { + CREATE TABLE t6(x INTEGER PRIMARY KEY); + CREATE INDEX t_idx6 on t6 (x); + INSERT INTO t6 VALUES (1); + INSERT INTO t6 VALUES (2); + DELETE FROM t6 WHERE x = 1; + INSERT INTO t6 VALUES (3); + DROP INDEX t_idx6; + SELECT count(*) FROM sqlite_schema WHERE type='index' AND name='t_idx6'; + } {0} +} diff --git a/testing/drop_table.test b/testing/drop_table.test index e1c48ec0c..157cff24d 100755 --- a/testing/drop_table.test +++ b/testing/drop_table.test @@ -25,24 +25,26 @@ do_execsql_test_on_specific_db {:memory:} drop-table-if-exists-2 { SELECT 'success'; } {success} -# Test dropping table with index -do_execsql_test_on_specific_db {:memory:} drop-table-with-index-1 { - CREATE TABLE t3(x INTEGER PRIMARY KEY, y TEXT); - CREATE INDEX idx_t3_y ON t3(y); - INSERT INTO t3 VALUES(1, 'one'); - DROP TABLE t3; - SELECT count(*) FROM sqlite_schema WHERE tbl_name='t3'; -} {0} +if {[info exists ::env(SQLITE_EXEC)] && ($::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental" || $::env(SQLITE_EXEC) eq "sqlite3")} { + # Test dropping table with index + do_execsql_test_on_specific_db {:memory:} drop-table-with-index-1 { + CREATE TABLE t3(x INTEGER PRIMARY KEY, y TEXT); + CREATE INDEX idx_t3_y ON t3(y); + INSERT INTO t3 VALUES(1, 'one'); + DROP TABLE t3; + SELECT count(*) FROM sqlite_schema WHERE tbl_name='t3'; + } {0} + # Test dropping table cleans up related schema entries + do_execsql_test_on_specific_db {:memory:} drop-table-schema-cleanup-1 { + CREATE TABLE t4(x INTEGER PRIMARY KEY, y TEXT); + CREATE INDEX idx1_t4 ON t4(x); + CREATE INDEX idx2_t4 ON t4(y); + INSERT INTO t4 VALUES(1, 'one'); + DROP TABLE t4; + SELECT count(*) FROM sqlite_schema WHERE tbl_name='t4'; + } {0} +} -# Test dropping table cleans up related schema entries -do_execsql_test_on_specific_db {:memory:} drop-table-schema-cleanup-1 { - CREATE TABLE t4(x INTEGER PRIMARY KEY, y TEXT); - CREATE INDEX idx1_t4 ON t4(x); - CREATE INDEX idx2_t4 ON t4(y); - INSERT INTO t4 VALUES(1, 'one'); - DROP TABLE t4; - SELECT count(*) FROM sqlite_schema WHERE tbl_name='t4'; -} {0} # Test dropping table after multiple inserts and deletes do_execsql_test_on_specific_db {:memory:} drop-table-after-ops-1 { diff --git a/testing/groupby.test b/testing/groupby.test index 0ed4c9078..5d093068f 100644 --- a/testing/groupby.test +++ b/testing/groupby.test @@ -199,14 +199,16 @@ do_execsql_test group_by_no_sorting_required { 2|113 3|97} -do_execsql_test distinct_agg_functions { - select first_name, sum(distinct age), count(distinct age), avg(distinct age) - from users - group by 1 - limit 3; -} {Aaron|1769|33|53.6060606060606 -Abigail|833|15|55.5333333333333 -Adam|1517|30|50.5666666666667} +if {[info exists ::env(SQLITE_EXEC)] && ($::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental" || $::env(SQLITE_EXEC) eq "sqlite3")} { + do_execsql_test distinct_agg_functions { + select first_name, sum(distinct age), count(distinct age), avg(distinct age) + from users + group by 1 + limit 3; + } {Aaron|1769|33|53.6060606060606 + Abigail|833|15|55.5333333333333 + Adam|1517|30|50.5666666666667} +} do_execsql_test_on_specific_db {:memory:} having_or { CREATE TABLE users (first_name TEXT, age INTEGER); diff --git a/testing/insert.test b/testing/insert.test index c814017c9..709ea655b 100755 --- a/testing/insert.test +++ b/testing/insert.test @@ -181,21 +181,23 @@ do_execsql_test_on_specific_db {:memory:} multi-rows { } {1|1 2|1} -do_execsql_test_on_specific_db {:memory:} unique_insert_no_pkey { - CREATE TABLE t2 (x INTEGER, y INTEGER UNIQUE); - INSERT INTO t2 (y) VALUES (1); - INSERT INTO t2 (y) VALUES (6); - SELECT * FROM t2; -} {|1 -|6} +if {[info exists ::env(SQLITE_EXEC)] && ($::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental" || $::env(SQLITE_EXEC) eq "sqlite3")} { + do_execsql_test_on_specific_db {:memory:} unique_insert_no_pkey { + CREATE TABLE t2 (x INTEGER, y INTEGER UNIQUE); + INSERT INTO t2 (y) VALUES (1); + INSERT INTO t2 (y) VALUES (6); + SELECT * FROM t2; + } {|1 + |6} -do_execsql_test_on_specific_db {:memory:} unique_insert_with_pkey { - CREATE TABLE t2 (x INTEGER PRIMARY KEY, y INTEGER UNIQUE); - INSERT INTO t2 (y) VALUES (1); - INSERT INTO t2 (y) VALUES (6); - SELECT * FROM t2; -} {1|1 -2|6} + do_execsql_test_on_specific_db {:memory:} unique_insert_with_pkey { + CREATE TABLE t2 (x INTEGER PRIMARY KEY, y INTEGER UNIQUE); + INSERT INTO t2 (y) VALUES (1); + INSERT INTO t2 (y) VALUES (6); + SELECT * FROM t2; + } {1|1 + 2|6} +} do_execsql_test_on_specific_db {:memory:} not_null_insert { CREATE TABLE t2 (y INTEGER NOT NULL); @@ -324,15 +326,17 @@ do_execsql_test_on_specific_db {:memory:} insert_from_select_same_table_2 { 5|2|200 6|3|300} -do_execsql_test_on_specific_db {:memory:} insert_from_select_union { - CREATE TABLE t(a, b); - CREATE TABLE t2(b, c); +if {[info exists ::env(SQLITE_EXEC)] && ($::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental" || $::env(SQLITE_EXEC) eq "sqlite3")} { + do_execsql_test_on_specific_db {:memory:} insert_from_select_union { + CREATE TABLE t(a, b); + CREATE TABLE t2(b, c); - INSERT INTO t2 VALUES (1, 100), (2, 200); - INSERT INTO t SELECT * FROM t UNION SELECT * FROM t2; - SELECT * FROM t; -} {1|100 -2|200} + INSERT INTO t2 VALUES (1, 100), (2, 200); + INSERT INTO t SELECT * FROM t UNION SELECT * FROM t2; + SELECT * FROM t; + } {1|100 + 2|200} +} do_execsql_test_on_specific_db {:memory:} negative-primary-integer-key { CREATE TABLE t(a INTEGER PRIMARY KEY); diff --git a/testing/join.test b/testing/join.test index 1f5eb0f1f..8a82a4f5c 100755 --- a/testing/join.test +++ b/testing/join.test @@ -228,11 +228,21 @@ do_execsql_test left-join-constant-condition-true-inner-join-constant-condition- select u.first_name, p.name, u2.first_name from users u left join products as p on 1 join users u2 on 0 limit 5; } {} -do_execsql_test join-utilizing-both-seekrowid-and-secondary-index { - select u.first_name, p.name from users u join products p on u.id = p.id and u.age > 70; -} {Matthew|boots -Nicholas|shorts -Jamie|hat} +if {[info exists ::env(SQLITE_EXEC)] && ($::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental" || $::env(SQLITE_EXEC) eq "sqlite3")} { + do_execsql_test join-utilizing-both-seekrowid-and-secondary-index { + select u.first_name, p.name from users u join products p on u.id = p.id and u.age > 70; + } {Matthew|boots + Nicholas|shorts + Jamie|hat} +} else { + # without index experimental the order is different since we don't use indexes + do_execsql_test join-utilizing-both-seekrowid-and-secondary-index { + select u.first_name, p.name from users u join products p on u.id = p.id and u.age > 70; + } {Jamie|hat + Nicholas|shorts + Matthew|boots} + +} # important difference between regular SELECT * join and a SELECT * USING join is that the join keys are deduplicated # from the result in the USING case. @@ -282,4 +292,4 @@ do_execsql_test left-join-backwards-iteration { where users.id < 13 order by users.id desc limit 3; } {12|Alan| 11|Travis|accessories -10|Daniel|coat} \ No newline at end of file +10|Daniel|coat} diff --git a/testing/orderby.test b/testing/orderby.test index d930bb388..555fccf6d 100755 --- a/testing/orderby.test +++ b/testing/orderby.test @@ -142,11 +142,13 @@ do_execsql_test case-insensitive-alias { select u.first_name as fF, count(1) > 0 as cC from users u where fF = 'Jamie' group by fF order by cC; } {Jamie|1} -do_execsql_test age_idx_order_desc { - select first_name from users order by age desc limit 3; -} {Robert -Sydney -Matthew} +if {[info exists ::env(SQLITE_EXEC)] && ($::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental" || $::env(SQLITE_EXEC) eq "sqlite3")} { + do_execsql_test age_idx_order_desc { + select first_name from users order by age desc limit 3; + } {Robert + Sydney + Matthew} +} do_execsql_test rowid_or_integer_pk_desc { select first_name from users order by id desc limit 3; @@ -163,19 +165,21 @@ do_execsql_test orderby_desc_verify_rows { select count(1) from (select * from users order by age desc) } {10000} -do_execsql_test orderby_desc_with_offset { - select first_name, age from users order by age desc limit 3 offset 666; -} {Francis|94 -Matthew|94 -Theresa|94} +if {[info exists ::env(SQLITE_EXEC)] && ($::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental" || $::env(SQLITE_EXEC) eq "sqlite3")} { + do_execsql_test orderby_desc_with_offset { + select first_name, age from users order by age desc limit 3 offset 666; + } {Francis|94 + Matthew|94 + Theresa|94} -do_execsql_test orderby_desc_with_filter { - select first_name, age from users where age <= 50 order by age desc limit 5; -} {Gerald|50 -Nicole|50 -Tammy|50 -Marissa|50 -Daniel|50} + do_execsql_test orderby_desc_with_filter { + select first_name, age from users where age <= 50 order by age desc limit 5; + } {Gerald|50 + Nicole|50 + Tammy|50 + Marissa|50 + Daniel|50} +} do_execsql_test orderby_asc_with_filter_range { select first_name, age from users where age <= 50 and age >= 49 order by age asc limit 5; @@ -210,4 +214,4 @@ do_execsql_test orderby_desc_regression_verify_order { select id from users where id < 100 order by id desc limit 3; } {99 98 -97} \ No newline at end of file +97} diff --git a/testing/select.test b/testing/select.test index 3162e0904..636058e23 100755 --- a/testing/select.test +++ b/testing/select.test @@ -285,76 +285,79 @@ do_execsql_test_on_specific_db {:memory:} select-union-all-with-filters { 6 10} -do_execsql_test_on_specific_db {:memory:} select-union-1 { - CREATE TABLE t(x TEXT, y TEXT); - CREATE TABLE u(x TEXT, y TEXT); - INSERT INTO t VALUES('x','x'),('y','y'); - INSERT INTO u VALUES('x','x'),('y','y'); +if {[info exists ::env(SQLITE_EXEC)] && ($::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental" || $::env(SQLITE_EXEC) eq "sqlite3")} { + do_execsql_test_on_specific_db {:memory:} select-union-1 { + CREATE TABLE t(x TEXT, y TEXT); + CREATE TABLE u(x TEXT, y TEXT); + INSERT INTO t VALUES('x','x'),('y','y'); + INSERT INTO u VALUES('x','x'),('y','y'); - select * from t UNION select * from u; -} {x|x -y|y} + select * from t UNION select * from u; + } {x|x + y|y} -do_execsql_test_on_specific_db {:memory:} select-union-all-union { - CREATE TABLE t(x TEXT, y TEXT); - CREATE TABLE u(x TEXT, y TEXT); - CREATE TABLE v(x TEXT, y TEXT); - INSERT INTO t VALUES('x','x'),('y','y'); - INSERT INTO u VALUES('x','x'),('y','y'); - INSERT INTO v VALUES('x','x'),('y','y'); + do_execsql_test_on_specific_db {:memory:} select-union-all-union { + CREATE TABLE t(x TEXT, y TEXT); + CREATE TABLE u(x TEXT, y TEXT); + CREATE TABLE v(x TEXT, y TEXT); + INSERT INTO t VALUES('x','x'),('y','y'); + INSERT INTO u VALUES('x','x'),('y','y'); + INSERT INTO v VALUES('x','x'),('y','y'); - select * from t UNION select * from u UNION ALL select * from v; -} {x|x -y|y -x|x -y|y} + select * from t UNION select * from u UNION ALL select * from v; + } {x|x + y|y + x|x + y|y} -do_execsql_test_on_specific_db {:memory:} select-union-all-union-2 { - CREATE TABLE t(x TEXT, y TEXT); - CREATE TABLE u(x TEXT, y TEXT); - CREATE TABLE v(x TEXT, y TEXT); - INSERT INTO t VALUES('x','x'),('y','y'); - INSERT INTO u VALUES('x','x'),('y','y'); - INSERT INTO v VALUES('x','x'),('y','y'); + do_execsql_test_on_specific_db {:memory:} select-union-all-union-2 { + CREATE TABLE t(x TEXT, y TEXT); + CREATE TABLE u(x TEXT, y TEXT); + CREATE TABLE v(x TEXT, y TEXT); + INSERT INTO t VALUES('x','x'),('y','y'); + INSERT INTO u VALUES('x','x'),('y','y'); + INSERT INTO v VALUES('x','x'),('y','y'); - select * from t UNION ALL select * from u UNION select * from v; -} {x|x -y|y} + select * from t UNION ALL select * from u UNION select * from v; + } {x|x + y|y} -do_execsql_test_on_specific_db {:memory:} select-union-3 { - CREATE TABLE t(x TEXT, y TEXT); - CREATE TABLE u(x TEXT, y TEXT); - CREATE TABLE v(x TEXT, y TEXT); - INSERT INTO t VALUES('x','x'),('y','y'); - INSERT INTO u VALUES('x','x'),('y','y'); - INSERT INTO v VALUES('x','x'),('y','y'); + do_execsql_test_on_specific_db {:memory:} select-union-3 { + CREATE TABLE t(x TEXT, y TEXT); + CREATE TABLE u(x TEXT, y TEXT); + CREATE TABLE v(x TEXT, y TEXT); + INSERT INTO t VALUES('x','x'),('y','y'); + INSERT INTO u VALUES('x','x'),('y','y'); + INSERT INTO v VALUES('x','x'),('y','y'); - select * from t UNION select * from u UNION select * from v; -} {x|x -y|y} + select * from t UNION select * from u UNION select * from v; + } {x|x + y|y} -do_execsql_test_on_specific_db {:memory:} select-union-4 { - CREATE TABLE t(x TEXT, y TEXT); - CREATE TABLE u(x TEXT, y TEXT); - CREATE TABLE v(x TEXT, y TEXT); - INSERT INTO t VALUES('x','x'),('y','y'); - INSERT INTO u VALUES('x','x'),('y','y'); - INSERT INTO v VALUES('x','x'),('y','y'); + do_execsql_test_on_specific_db {:memory:} select-union-4 { + CREATE TABLE t(x TEXT, y TEXT); + CREATE TABLE u(x TEXT, y TEXT); + CREATE TABLE v(x TEXT, y TEXT); + INSERT INTO t VALUES('x','x'),('y','y'); + INSERT INTO u VALUES('x','x'),('y','y'); + INSERT INTO v VALUES('x','x'),('y','y'); - select * from t UNION select * from u UNION select * from v UNION select * from t; -} {x|x -y|y} + select * from t UNION select * from u UNION select * from v UNION select * from t; + } {x|x + y|y} -do_execsql_test_on_specific_db {:memory:} select-union-all-union-3 { - CREATE TABLE t(x TEXT, y TEXT); - CREATE TABLE u(x TEXT, y TEXT); - CREATE TABLE v(x TEXT, y TEXT); - INSERT INTO t VALUES('x','x'),('y','y'); - INSERT INTO u VALUES('x','x'),('y','y'); - INSERT INTO v VALUES('x','x'),('y','y'); + do_execsql_test_on_specific_db {:memory:} select-union-all-union-3 { + CREATE TABLE t(x TEXT, y TEXT); + CREATE TABLE u(x TEXT, y TEXT); + CREATE TABLE v(x TEXT, y TEXT); + INSERT INTO t VALUES('x','x'),('y','y'); + INSERT INTO u VALUES('x','x'),('y','y'); + INSERT INTO v VALUES('x','x'),('y','y'); + + select * from t UNION select * from u UNION select * from v UNION ALL select * from t; + } {x|x + y|y + x|x + y|y} +} - select * from t UNION select * from u UNION select * from v UNION ALL select * from t; -} {x|x -y|y -x|x -y|y} diff --git a/testing/subquery.test b/testing/subquery.test index 98ecec001..1cec4c8ec 100644 --- a/testing/subquery.test +++ b/testing/subquery.test @@ -412,19 +412,21 @@ do_execsql_test subquery-ignore-unused-cte { select * from sub; } {Jamie} -# Test verifying that select distinct works (distinct ages are 1-100) -do_execsql_test subquery-count-distinct-age { - select count(1) from (select distinct age from users); -} {100} +if {[info exists ::env(SQLITE_EXEC)] && ($::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental" || $::env(SQLITE_EXEC) eq "sqlite3")} { + # Test verifying that select distinct works (distinct ages are 1-100) + do_execsql_test subquery-count-distinct-age { + select count(1) from (select distinct age from users); + } {100} -# Test verifying that select distinct works for multiple columns, and across joins -do_execsql_test subquery-count-distinct { - select count(1) from ( - select distinct first_name, name - from users u join products p - where u.id < 100 - ); -} {902} + # Test verifying that select distinct works for multiple columns, and across joins + do_execsql_test subquery-count-distinct { + select count(1) from ( + select distinct first_name, name + from users u join products p + where u.id < 100 + ); + } {902} +} do_execsql_test subquery-count-all { select count(1) from ( diff --git a/testing/update.test b/testing/update.test index 7c793932d..a9b9e8193 100755 --- a/testing/update.test +++ b/testing/update.test @@ -190,20 +190,22 @@ do_execsql_test_on_specific_db {:memory:} update_cache_full_regression_test_#162 SELECT count(*) FROM t; } {1} -do_execsql_test_on_specific_db {:memory:} update_index_regression_test { - CREATE TABLE t(x, y); - CREATE INDEX tx ON t (x); - CREATE UNIQUE INDEX tyu ON t (y); - INSERT INTO t VALUES (1, 1); - SELECT x FROM t; -- uses tx index - SELECT y FROM t; -- uses ty index - UPDATE t SET x=2, y=2; - SELECT x FROM t; -- uses tx index - SELECT y FROM t; -- uses ty index -} {1 -1 -2 -2} +if {[info exists ::env(SQLITE_EXEC)] && ($::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental" || $::env(SQLITE_EXEC) eq "sqlite3")} { + do_execsql_test_on_specific_db {:memory:} update_index_regression_test { + CREATE TABLE t(x, y); + CREATE INDEX tx ON t (x); + CREATE UNIQUE INDEX tyu ON t (y); + INSERT INTO t VALUES (1, 1); + SELECT x FROM t; -- uses tx index + SELECT y FROM t; -- uses ty index + UPDATE t SET x=2, y=2; + SELECT x FROM t; -- uses tx index + SELECT y FROM t; -- uses ty index + } {1 + 1 + 2 + 2} +} do_execsql_test_on_specific_db {:memory:} update_where_or_regression_test { CREATE TABLE t (a INTEGER); diff --git a/testing/where.test b/testing/where.test index 958d7825f..71e54b899 100755 --- a/testing/where.test +++ b/testing/where.test @@ -155,26 +155,49 @@ do_execsql_test where-clause-no-table-constant-condition-false-7 { select 1 where 'hamburger'; } {} -# this test functions as an assertion that the index on users.age is being used, since the results are ordered by age without an order by. -do_execsql_test select-where-and { - select first_name, age from users where first_name = 'Jamie' and age > 80 -} {Jamie|87 -Jamie|88 -Jamie|88 -Jamie|92 -Jamie|94 -Jamie|99 +if {[info exists ::env(SQLITE_EXEC)] && ($::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental" || $::env(SQLITE_EXEC) eq "sqlite3")} { + # this test functions as an assertion that the index on users.age is being used, since the results are ordered by age without an order by. + do_execsql_test select-where-and { + select first_name, age from users where first_name = 'Jamie' and age > 80 + } {Jamie|87 + Jamie|88 + Jamie|88 + Jamie|92 + Jamie|94 + Jamie|99 + } + do_execsql_test select-where-or { + select first_name, age from users where first_name = 'Jamie' and age > 80 + } {Jamie|87 + Jamie|88 + Jamie|88 + Jamie|92 + Jamie|94 + Jamie|99 + } +} else { + # this test functions as an assertion that the index on users.age is being used, since the results are ordered by age without an order by. + do_execsql_test select-where-and { + select first_name, age from users where first_name = 'Jamie' and age > 80 + } {Jamie|94 + Jamie|88 + Jamie|99 + Jamie|92 + Jamie|87 + Jamie|88 + } + do_execsql_test select-where-or { + select first_name, age from users where first_name = 'Jamie' and age > 80 + } {Jamie|94 + Jamie|88 + Jamie|99 + Jamie|92 + Jamie|87 + Jamie|88 + } + } -do_execsql_test select-where-or { - select first_name, age from users where first_name = 'Jamie' and age > 80 -} {Jamie|87 -Jamie|88 -Jamie|88 -Jamie|92 -Jamie|94 -Jamie|99 -} do_execsql_test select-where-and-or { select first_name, age from users where first_name = 'Jamie' or age = 1 and age = 2 @@ -383,9 +406,16 @@ do_execsql_test where-age-index-seek-regression-test-2 { select count(1) from users where age > 0; } {10000} -do_execsql_test where-age-index-seek-regression-test-3 { - select age from users where age > 90 limit 1; -} {91} +if {[info exists ::env(SQLITE_EXEC)] && ($::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental" || $::env(SQLITE_EXEC) eq "sqlite3")} { + do_execsql_test where-age-index-seek-regression-test-3 { + select age from users where age > 90 limit 1; + } {91} +} else { + do_execsql_test where-age-index-seek-regression-test-3 { + select age from users where age > 90 limit 1; + } {94} + +} do_execsql_test where-simple-between { SELECT * FROM products WHERE price BETWEEN 70 AND 100; diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 8e103cb56..75215fd2a 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -14,6 +14,9 @@ path = "lib.rs" name = "integration_tests" path = "integration/mod.rs" +[features] +index_experimental = ["limbo_core/index_experimental"] + [dependencies] anyhow.workspace = true env_logger = "0.10.1" diff --git a/tests/integration/common.rs b/tests/integration/common.rs index 8d0fc7830..4814c5777 100644 --- a/tests/integration/common.rs +++ b/tests/integration/common.rs @@ -283,6 +283,7 @@ mod tests { } #[test] + #[cfg(feature = "index_experimental")] fn test_unique_index_ordering() -> anyhow::Result<()> { let db = TempDatabase::new_empty(); let conn = db.connect_limbo(); @@ -323,6 +324,7 @@ mod tests { } #[test] + #[cfg(feature = "index_experimental")] fn test_large_unique_blobs() -> anyhow::Result<()> { let path = TempDir::new().unwrap().keep().join("temp_read_only"); let db = TempDatabase::new_with_existent(&path); diff --git a/tests/integration/fuzz/mod.rs b/tests/integration/fuzz/mod.rs index 55b8c3a25..863f7fdfa 100644 --- a/tests/integration/fuzz/mod.rs +++ b/tests/integration/fuzz/mod.rs @@ -165,6 +165,7 @@ mod tests { } #[test] + #[cfg(feature = "index_experimental")] pub fn index_scan_fuzz() { let db = TempDatabase::new_with_rusqlite("CREATE TABLE t(x PRIMARY KEY)"); let sqlite_conn = rusqlite::Connection::open(db.path.clone()).unwrap(); @@ -212,6 +213,7 @@ mod tests { } #[test] + #[cfg(feature = "index_experimental")] /// A test for verifying that index seek+scan works correctly for compound keys /// on indexes with various column orderings. pub fn index_scan_compound_key_fuzz() { @@ -491,6 +493,7 @@ mod tests { } #[test] + #[cfg(feature = "index_experimental")] pub fn compound_select_fuzz() { let _ = env_logger::try_init(); let (mut rng, seed) = rng_from_time(); @@ -1356,6 +1359,7 @@ mod tests { } #[test] + #[cfg(feature = "index_experimental")] pub fn table_logical_expression_fuzz_run() { let _ = env_logger::try_init(); let g = GrammarGenerator::new(); diff --git a/tests/integration/query_processing/test_write_path.rs b/tests/integration/query_processing/test_write_path.rs index dd1c0d7c9..0e0597167 100644 --- a/tests/integration/query_processing/test_write_path.rs +++ b/tests/integration/query_processing/test_write_path.rs @@ -393,6 +393,7 @@ fn test_write_delete_with_index() -> anyhow::Result<()> { } #[test] +#[cfg(feature = "index_experimental")] fn test_update_with_index() -> anyhow::Result<()> { let _ = env_logger::try_init();