From 9ae4563bcdc31b92b2c3d537e2a82ff6982952c9 Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Mon, 16 Jun 2025 13:58:42 +0200 Subject: [PATCH 01/19] `index_experimental` flag to enable index usages Currently indexes are the bulk of the problem with `UPDATE` and `DELETE`, while we work on fixing those it makes sense to disable indexing since they are not stable. We want to try to make everything else stable before we continue with indexing. --- .github/workflows/rust.yml | 5 +++- Makefile | 1 + cli/Cargo.toml | 6 ++++ core/Cargo.toml | 1 + core/schema.rs | 1 + core/translate/optimizer/mod.rs | 20 +++++++------ core/util.rs | 36 +++++++++++++++--------- scripts/limbo-sqlite3-index-experimental | 8 ++++++ testing/join.test | 12 ++++---- testing/orderby.test | 36 ++++++++++++------------ 10 files changed, 79 insertions(+), 47 deletions(-) create mode 100755 scripts/limbo-sqlite3-index-experimental diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d38006d4c..19ea342c4 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -93,7 +93,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..ce9f5df67 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: 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..1458b968e 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -76,6 +76,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 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/util.rs b/core/util.rs index 33af5dbc6..c5163b995 100644 --- a/core/util.rs +++ b/core/util.rs @@ -136,23 +136,31 @@ pub fn parse_schema_rows( StepResult::Busy => break, } } - for UnparsedFromSqlIndex { - table_name, - root_page, - sql, - } in from_sql_indexes + #[cfg(feature = "index_experimental")] { - 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 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)); } + #[cfg(feature = "index_experimental")] + { + 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 { + 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/join.test b/testing/join.test index 1f5eb0f1f..8dc51892d 100755 --- a/testing/join.test +++ b/testing/join.test @@ -228,11 +228,11 @@ 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} +#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} # 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 +282,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..86b7473cd 100755 --- a/testing/orderby.test +++ b/testing/orderby.test @@ -142,11 +142,11 @@ 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} +#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 +163,19 @@ 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} +#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 +210,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} From dde93e8debcfe45f28574d09713d55dbcd4174c3 Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Mon, 16 Jun 2025 14:11:51 +0200 Subject: [PATCH 02/19] disable distinct without index_experimental distinct uses indexes, therefore we need to disable them --- core/translate/planner.rs | 8 ++++++++ core/translate/select.rs | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) 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/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"); } From bcbce15d7bb438c9c7f55a458ee6a6fd62a4a9f3 Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Mon, 16 Jun 2025 16:09:04 +0200 Subject: [PATCH 03/19] disable UNION deduplication --- core/translate/compound_select.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 { From 63b37ea6f21c32d37188e6765d19444e9da73e3b Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Mon, 16 Jun 2025 16:34:15 +0200 Subject: [PATCH 04/19] re-enable tests that work with indexes --- testing/agg-functions.test | 8 +++++--- testing/groupby.test | 18 ++++++++++-------- testing/insert.test | 18 ++++++++++-------- testing/join.test | 20 +++++++++++++++----- testing/orderby.test | 38 +++++++++++++++++++++----------------- 5 files changed, 61 insertions(+), 41 deletions(-) diff --git a/testing/agg-functions.test b/testing/agg-functions.test index c9169906b..0b445d884 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"} { + 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/groupby.test b/testing/groupby.test index 0ed4c9078..69348ad62 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"} { + 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..cc0b8351a 100755 --- a/testing/insert.test +++ b/testing/insert.test @@ -324,15 +324,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"} { + 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 8dc51892d..598087512 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"} { + 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. diff --git a/testing/orderby.test b/testing/orderby.test index 86b7473cd..169e7670c 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"} { + 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"} { + 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; From b5f2f375b8d7fb27dfb9004f8f0a05caabc75ff9 Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Mon, 16 Jun 2025 17:42:08 +0200 Subject: [PATCH 05/19] disable alter, delete, create index, insert and update for indexes --- core/translate/alter.rs | 9 +++++++++ core/translate/delete.rs | 9 +++++++++ core/translate/index.rs | 7 +++++++ core/translate/insert.rs | 10 +++++++++- core/translate/schema.rs | 3 +++ core/translate/update.rs | 8 ++++++++ 6 files changed, 45 insertions(+), 1 deletion(-) diff --git a/core/translate/alter.rs b/core/translate/alter.rs index 39d158127..62649a90e 100644 --- a/core/translate/alter.rs +++ b/core/translate/alter.rs @@ -2,6 +2,7 @@ use fallible_iterator::FallibleIterator as _; use limbo_sqlite3_parser::{ast, lexer::sql::Parser}; use crate::{ + bail_parse_error, function::{AlterTableFunc, Func}, schema::{Column, Schema}, util::normalize_ident, @@ -24,6 +25,14 @@ pub fn translate_alter_table( ) -> Result { let (table_name, alter_table) = alter; let ast::Name(table_name) = table_name.name; + let indexes = schema.get_indices(&table_name); + if !indexes.is_empty() && cfg!(not(feature = "index_experimental")) { + // Let's disable altering a table with indices altogether instead of checking column by + // column to be extra safe. + 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/delete.rs b/core/translate/delete.rs index 1ddf25ece..3fe7af06e 100644 --- a/core/translate/delete.rs +++ b/core/translate/delete.rs @@ -1,3 +1,4 @@ +use crate::bail_parse_error; use crate::schema::Table; use crate::translate::emitter::emit_program; use crate::translate::optimizer::optimize_plan; @@ -18,6 +19,14 @@ pub fn translate_delete( syms: &SymbolTable, mut program: ProgramBuilder, ) -> Result { + let indexes = schema.get_indices(&tbl_name.name.to_string()); + if !indexes.is_empty() && cfg!(not(feature = "index_experimental")) { + // Let's disable altering a table with indices altogether instead of checking column by + // column to be extra safe. + 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..ffb623a8f 100644 --- a/core/translate/index.rs +++ b/core/translate/index.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use crate::bail_parse_error; use crate::vdbe::insn::CmpInsFlags; use crate::{ schema::{BTreeTable, Column, Index, IndexColumn, PseudoTable, Schema}, @@ -23,6 +24,9 @@ pub fn translate_create_index( schema: &Schema, mut program: ProgramBuilder, ) -> crate::Result { + if cfg!(not(feature = "index_experimental")) { + 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 +300,9 @@ pub fn translate_drop_index( schema: &Schema, mut program: ProgramBuilder, ) -> crate::Result { + if cfg!(not(feature = "index_experimental")) { + 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..e9bcbd4d2 100644 --- a/core/translate/insert.rs +++ b/core/translate/insert.rs @@ -10,6 +10,7 @@ use crate::util::normalize_ident; use crate::vdbe::builder::{ProgramBuilderOpts, QueryMode}; use crate::vdbe::insn::{IdxInsertFlags, InsertFlags, RegisterOrLiteral}; use crate::vdbe::BranchOffset; +use crate::{bail_parse_error, Result, SymbolTable, VirtualTable}; use crate::{ schema::{Column, Schema}, vdbe::{ @@ -17,7 +18,6 @@ use crate::{ insn::Insn, }, }; -use crate::{Result, SymbolTable, VirtualTable}; use super::emitter::Resolver; use super::expr::{translate_expr, translate_expr_no_constant_opt, NoConstantOptReason}; @@ -58,6 +58,14 @@ pub fn translate_insert( crate::bail_parse_error!("ON CONFLICT clause is not supported"); } + let indexes = schema.get_indices(&tbl_name.name.to_string()); + if !indexes.is_empty() && cfg!(not(feature = "index_experimental")) { + // Let's disable altering a table with indices altogether instead of checking column by + // column to be extra safe. + 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/schema.rs b/core/translate/schema.rs index 7b0bfe907..e4d5f5948 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, diff --git a/core/translate/update.rs b/core/translate/update.rs index d4af63b0e..7cb94d6f3 100644 --- a/core/translate/update.rs +++ b/core/translate/update.rs @@ -101,6 +101,14 @@ pub fn prepare_update_plan( bail_parse_error!("ON CONFLICT clause is not supported"); } let table_name = &body.tbl_name.name; + let indexes = schema.get_indices(&table_name.to_string()); + if !indexes.is_empty() && cfg!(not(feature = "index_experimental")) { + // Let's disable altering a table with indices altogether instead of checking column by + // column to be extra safe. + bail_parse_error!( + "INSERT 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), From f91d2c5e994f5f475b5b17a4ba6902a50aa193e5 Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Mon, 16 Jun 2025 22:44:10 +0200 Subject: [PATCH 06/19] fix disable in write cases --- core/schema.rs | 26 +++++++++++++++++++--- core/translate/alter.rs | 16 ++++++++------ core/translate/delete.rs | 16 ++++++++------ core/translate/insert.rs | 16 ++++++++------ core/translate/schema.rs | 7 ++++++ core/translate/update.rs | 16 ++++++++------ core/util.rs | 47 ++++++++++++++++++++++------------------ 7 files changed, 92 insertions(+), 52 deletions(-) diff --git a/core/schema.rs b/core/schema.rs index 1458b968e..3d7f3b074 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -9,7 +9,7 @@ use limbo_sqlite3_parser::{ ast::{Cmd, CreateTableBody, QualifiedName, ResultColumn, Stmt}, lexer::sql::Parser, }; -use std::collections::{BTreeSet, HashMap}; +use std::collections::{BTreeSet, HashMap, HashSet}; use std::rc::Rc; use std::sync::Arc; use tracing::trace; @@ -19,20 +19,30 @@ 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. + pub has_indexes: HashSet, } impl Schema { pub fn new() -> Self { let mut tables: HashMap> = HashMap::new(); + let mut has_indexes = 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, + has_indexes, + } } pub fn is_unique_idx_name(&self, name: &str) -> bool { @@ -112,6 +122,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/translate/alter.rs b/core/translate/alter.rs index 62649a90e..4ea08e997 100644 --- a/core/translate/alter.rs +++ b/core/translate/alter.rs @@ -25,13 +25,15 @@ pub fn translate_alter_table( ) -> Result { let (table_name, alter_table) = alter; let ast::Name(table_name) = table_name.name; - let indexes = schema.get_indices(&table_name); - if !indexes.is_empty() && cfg!(not(feature = "index_experimental")) { - // Let's disable altering a table with indices altogether instead of checking column by - // column to be extra safe. - bail_parse_error!( - "Alter table disabled for table with indexes without index_experimental feature flag" - ); + #[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. + bail_parse_error!( + "Alter table disabled for table with indexes without index_experimental feature flag" + ); + } } let Some(original_btree) = schema diff --git a/core/translate/delete.rs b/core/translate/delete.rs index 3fe7af06e..c8357b47c 100644 --- a/core/translate/delete.rs +++ b/core/translate/delete.rs @@ -19,13 +19,15 @@ pub fn translate_delete( syms: &SymbolTable, mut program: ProgramBuilder, ) -> Result { - let indexes = schema.get_indices(&tbl_name.name.to_string()); - if !indexes.is_empty() && cfg!(not(feature = "index_experimental")) { - // Let's disable altering a table with indices altogether instead of checking column by - // column to be extra safe. - bail_parse_error!( - "DELETE into table disabled for table with indexes and without index_experimental feature flag" - ); + #[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. + 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, diff --git a/core/translate/insert.rs b/core/translate/insert.rs index e9bcbd4d2..c81516f46 100644 --- a/core/translate/insert.rs +++ b/core/translate/insert.rs @@ -58,13 +58,15 @@ pub fn translate_insert( crate::bail_parse_error!("ON CONFLICT clause is not supported"); } - let indexes = schema.get_indices(&tbl_name.name.to_string()); - if !indexes.is_empty() && cfg!(not(feature = "index_experimental")) { - // Let's disable altering a table with indices altogether instead of checking column by - // column to be extra safe. - bail_parse_error!( - "INSERT table disabled for table with indexes and without index_experimental feature flag" - ); + #[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. + 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()) { diff --git a/core/translate/schema.rs b/core/translate/schema.rs index e4d5f5948..49f792e47 100644 --- a/core/translate/schema.rs +++ b/core/translate/schema.rs @@ -613,6 +613,13 @@ pub fn translate_drop_table( schema: &Schema, mut program: ProgramBuilder, ) -> Result { + if cfg!(not(feature = "index_experimental")) + && 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/update.rs b/core/translate/update.rs index 7cb94d6f3..89f0de71c 100644 --- a/core/translate/update.rs +++ b/core/translate/update.rs @@ -101,13 +101,15 @@ pub fn prepare_update_plan( bail_parse_error!("ON CONFLICT clause is not supported"); } let table_name = &body.tbl_name.name; - let indexes = schema.get_indices(&table_name.to_string()); - if !indexes.is_empty() && cfg!(not(feature = "index_experimental")) { - // Let's disable altering a table with indices altogether instead of checking column by - // column to be extra safe. - bail_parse_error!( - "INSERT table disabled for table with indexes and without index_experimental feature flag" - ); + #[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, diff --git a/core/util.rs b/core/util.rs index c5163b995..0c52eabaa 100644 --- a/core/util.rs +++ b/core/util.rs @@ -136,29 +136,34 @@ pub fn parse_schema_rows( StepResult::Busy => break, } } - #[cfg(feature = "index_experimental")] - { - 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 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")] { - 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 { - schema.add_index(Arc::new(index)); - } + 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)); } } } From 133827e9f22ae796a33124a1d1cac43a37ba1b24 Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Mon, 16 Jun 2025 22:44:23 +0200 Subject: [PATCH 07/19] fix delete test with primary key --- testing/delete.test | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) 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; + } {} +} From 48ae6766d7a7c0bfd7394c852e162c71c96527c3 Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Mon, 16 Jun 2025 22:54:40 +0200 Subject: [PATCH 08/19] fix comp errors --- core/schema.rs | 2 +- core/translate/index.rs | 5 ++--- core/translate/schema.rs | 11 ++++++----- core/util.rs | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/core/schema.rs b/core/schema.rs index 3d7f3b074..f6ed82230 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -31,7 +31,7 @@ pub struct Schema { impl Schema { pub fn new() -> Self { let mut tables: HashMap> = HashMap::new(); - let mut has_indexes = HashSet::new(); + let has_indexes = HashSet::new(); let indexes: HashMap>> = HashMap::new(); #[allow(clippy::arc_with_non_send_sync)] tables.insert( diff --git a/core/translate/index.rs b/core/translate/index.rs index ffb623a8f..55d8f4a18 100644 --- a/core/translate/index.rs +++ b/core/translate/index.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use crate::bail_parse_error; use crate::vdbe::insn::CmpInsFlags; use crate::{ schema::{BTreeTable, Column, Index, IndexColumn, PseudoTable, Schema}, @@ -25,7 +24,7 @@ pub fn translate_create_index( mut program: ProgramBuilder, ) -> crate::Result { if cfg!(not(feature = "index_experimental")) { - bail_parse_error!("CREATE INDEX enabled only with index_experimental feature"); + 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); @@ -301,7 +300,7 @@ pub fn translate_drop_index( mut program: ProgramBuilder, ) -> crate::Result { if cfg!(not(feature = "index_experimental")) { - bail_parse_error!("DROP INDEX enabled only with index_experimental feature"); + 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 { diff --git a/core/translate/schema.rs b/core/translate/schema.rs index 49f792e47..4770ed976 100644 --- a/core/translate/schema.rs +++ b/core/translate/schema.rs @@ -613,12 +613,13 @@ pub fn translate_drop_table( schema: &Schema, mut program: ProgramBuilder, ) -> Result { - if cfg!(not(feature = "index_experimental")) - && schema.table_has_indexes(&tbl_name.name.to_string()) + #[cfg(not(feature = "index_experimental"))] { - bail_parse_error!( - "DROP Table with indexes on the table enabled only with index_experimental feature" - ); + 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, diff --git a/core/util.rs b/core/util.rs index 0c52eabaa..b1489333e 100644 --- a/core/util.rs +++ b/core/util.rs @@ -152,7 +152,7 @@ pub fn parse_schema_rows( schema.add_index(Arc::new(index)); } } - for automatic_index in &automatic_indices { + for automatic_index in automatic_indices { #[cfg(not(feature = "index_experimental"))] schema.table_set_has_index(&automatic_index.0); #[cfg(feature = "index_experimental")] @@ -160,7 +160,7 @@ pub fn parse_schema_rows( 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, + automatic_index.1, )?; for index in ret_index { schema.add_index(Arc::new(index)); From 4b88d47abae62bc09b39052b739006d380f56141 Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Tue, 17 Jun 2025 10:52:28 +0200 Subject: [PATCH 09/19] disable last unique_insert tcl tests --- testing/insert.test | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/testing/insert.test b/testing/insert.test index cc0b8351a..aa990a74c 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"} { + 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); From 032337745bf290dc38f68bcf138cbb4265ae0158 Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Tue, 17 Jun 2025 12:35:04 +0200 Subject: [PATCH 10/19] disable more tests without index --- testing/create_table.test | 22 ++++--- testing/drop_index.test | 74 +++++++++++----------- testing/drop_table.test | 36 ++++++----- testing/select.test | 127 +++++++++++++++++++------------------- testing/subquery.test | 26 ++++---- testing/update.test | 30 ++++----- testing/where.test | 72 ++++++++++++++------- 7 files changed, 215 insertions(+), 172 deletions(-) diff --git a/testing/create_table.test b/testing/create_table.test index 858914f38..5ba4f3839 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"} { + 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/drop_index.test b/testing/drop_index.test index 63c457bec..63ab7b84d 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"} { + # 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..00da47f1a 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"} { + # 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/select.test b/testing/select.test index 3162e0904..96b977c59 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"} { + 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..ce9d40914 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"} { + # 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..96cf27d0a 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"} { + 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..8331289f6 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"} { + # 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"} { + 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; From 878e39eee0ae21c00ced94b19a7cc6992d09cb4f Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Tue, 17 Jun 2025 12:35:17 +0200 Subject: [PATCH 11/19] skip python write tests without indexes --- Makefile | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index ce9f5df67..5bb117f92 100644 --- a/Makefile +++ b/Makefile @@ -94,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 From e90996783b4708f67d12690352a4ea5d6cc0307c Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Tue, 17 Jun 2025 12:40:21 +0200 Subject: [PATCH 12/19] clippy --- core/schema.rs | 9 ++++++--- core/translate/alter.rs | 3 +-- core/translate/delete.rs | 3 +-- core/translate/insert.rs | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/core/schema.rs b/core/schema.rs index f6ed82230..c24fe5ef9 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -9,7 +9,7 @@ use limbo_sqlite3_parser::{ ast::{Cmd, CreateTableBody, QualifiedName, ResultColumn, Stmt}, lexer::sql::Parser, }; -use std::collections::{BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeSet, HashMap}; use std::rc::Rc; use std::sync::Arc; use tracing::trace; @@ -25,13 +25,15 @@ pub struct Schema { /// 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. - pub has_indexes: HashSet, + #[cfg(not(feature = "index_experimental"))] + pub has_indexes: std::collections::HashSet, } impl Schema { pub fn new() -> Self { let mut tables: HashMap> = HashMap::new(); - let has_indexes = HashSet::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( @@ -41,6 +43,7 @@ impl Schema { Self { tables, indexes, + #[cfg(not(feature = "index_experimental"))] has_indexes, } } diff --git a/core/translate/alter.rs b/core/translate/alter.rs index 4ea08e997..c25621bfa 100644 --- a/core/translate/alter.rs +++ b/core/translate/alter.rs @@ -2,7 +2,6 @@ use fallible_iterator::FallibleIterator as _; use limbo_sqlite3_parser::{ast, lexer::sql::Parser}; use crate::{ - bail_parse_error, function::{AlterTableFunc, Func}, schema::{Column, Schema}, util::normalize_ident, @@ -30,7 +29,7 @@ pub fn translate_alter_table( 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. - bail_parse_error!( + crate::bail_parse_error!( "Alter table disabled for table with indexes without index_experimental feature flag" ); } diff --git a/core/translate/delete.rs b/core/translate/delete.rs index c8357b47c..484b46527 100644 --- a/core/translate/delete.rs +++ b/core/translate/delete.rs @@ -1,4 +1,3 @@ -use crate::bail_parse_error; use crate::schema::Table; use crate::translate::emitter::emit_program; use crate::translate::optimizer::optimize_plan; @@ -24,7 +23,7 @@ pub fn translate_delete( 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. - bail_parse_error!( + crate::bail_parse_error!( "DELETE into table disabled for table with indexes and without index_experimental feature flag" ); } diff --git a/core/translate/insert.rs b/core/translate/insert.rs index c81516f46..7855d14b1 100644 --- a/core/translate/insert.rs +++ b/core/translate/insert.rs @@ -10,7 +10,6 @@ use crate::util::normalize_ident; use crate::vdbe::builder::{ProgramBuilderOpts, QueryMode}; use crate::vdbe::insn::{IdxInsertFlags, InsertFlags, RegisterOrLiteral}; use crate::vdbe::BranchOffset; -use crate::{bail_parse_error, Result, SymbolTable, VirtualTable}; use crate::{ schema::{Column, Schema}, vdbe::{ @@ -18,6 +17,7 @@ use crate::{ insn::Insn, }, }; +use crate::{Result, SymbolTable, VirtualTable}; use super::emitter::Resolver; use super::expr::{translate_expr, translate_expr_no_constant_opt, NoConstantOptReason}; @@ -63,7 +63,7 @@ pub fn translate_insert( 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. - bail_parse_error!( + crate::bail_parse_error!( "INSERT table disabled for table with indexes and without index_experimental feature flag" ); } From 814f68043d0081f7e299cabb91dcc5e5ad0af0f5 Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Tue, 17 Jun 2025 12:51:10 +0200 Subject: [PATCH 13/19] filter out sqlite3 executable too --- testing/agg-functions.test | 2 +- testing/create_table.test | 2 +- testing/drop_index.test | 2 +- testing/drop_table.test | 2 +- testing/groupby.test | 2 +- testing/insert.test | 4 ++-- testing/join.test | 2 +- testing/orderby.test | 4 ++-- testing/select.test | 2 +- testing/subquery.test | 2 +- testing/update.test | 2 +- testing/where.test | 4 ++-- 12 files changed, 15 insertions(+), 15 deletions(-) diff --git a/testing/agg-functions.test b/testing/agg-functions.test index 0b445d884..50c3f44e2 100755 --- a/testing/agg-functions.test +++ b/testing/agg-functions.test @@ -128,7 +128,7 @@ 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"}]} -if {[info exists ::env(SQLITE_EXEC)] && $::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental"} { +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/create_table.test b/testing/create_table.test index 5ba4f3839..f5c4edca9 100755 --- a/testing/create_table.test +++ b/testing/create_table.test @@ -3,7 +3,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl -if {[info exists ::env(SQLITE_EXEC)] && $::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental"} { +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)); } diff --git a/testing/drop_index.test b/testing/drop_index.test index 63ab7b84d..6c37fb43e 100755 --- a/testing/drop_index.test +++ b/testing/drop_index.test @@ -3,7 +3,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl -if {[info exists ::env(SQLITE_EXEC)] && $::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental"} { +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); diff --git a/testing/drop_table.test b/testing/drop_table.test index 00da47f1a..157cff24d 100755 --- a/testing/drop_table.test +++ b/testing/drop_table.test @@ -25,7 +25,7 @@ do_execsql_test_on_specific_db {:memory:} drop-table-if-exists-2 { SELECT 'success'; } {success} -if {[info exists ::env(SQLITE_EXEC)] && $::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental"} { +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); diff --git a/testing/groupby.test b/testing/groupby.test index 69348ad62..5d093068f 100644 --- a/testing/groupby.test +++ b/testing/groupby.test @@ -199,7 +199,7 @@ do_execsql_test group_by_no_sorting_required { 2|113 3|97} -if {[info exists ::env(SQLITE_EXEC)] && $::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental"} { +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 diff --git a/testing/insert.test b/testing/insert.test index aa990a74c..709ea655b 100755 --- a/testing/insert.test +++ b/testing/insert.test @@ -181,7 +181,7 @@ do_execsql_test_on_specific_db {:memory:} multi-rows { } {1|1 2|1} -if {[info exists ::env(SQLITE_EXEC)] && $::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental"} { +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); @@ -326,7 +326,7 @@ do_execsql_test_on_specific_db {:memory:} insert_from_select_same_table_2 { 5|2|200 6|3|300} -if {[info exists ::env(SQLITE_EXEC)] && $::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental"} { +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); diff --git a/testing/join.test b/testing/join.test index 598087512..8a82a4f5c 100755 --- a/testing/join.test +++ b/testing/join.test @@ -228,7 +228,7 @@ 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; } {} -if {[info exists ::env(SQLITE_EXEC)] && $::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental"} { +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 diff --git a/testing/orderby.test b/testing/orderby.test index 169e7670c..555fccf6d 100755 --- a/testing/orderby.test +++ b/testing/orderby.test @@ -142,7 +142,7 @@ 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} -if {[info exists ::env(SQLITE_EXEC)] && $::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental"} { +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 @@ -165,7 +165,7 @@ do_execsql_test orderby_desc_verify_rows { select count(1) from (select * from users order by age desc) } {10000} -if {[info exists ::env(SQLITE_EXEC)] && $::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental"} { +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 diff --git a/testing/select.test b/testing/select.test index 96b977c59..636058e23 100755 --- a/testing/select.test +++ b/testing/select.test @@ -285,7 +285,7 @@ do_execsql_test_on_specific_db {:memory:} select-union-all-with-filters { 6 10} -if {[info exists ::env(SQLITE_EXEC)] && $::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental"} { +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); diff --git a/testing/subquery.test b/testing/subquery.test index ce9d40914..1cec4c8ec 100644 --- a/testing/subquery.test +++ b/testing/subquery.test @@ -412,7 +412,7 @@ do_execsql_test subquery-ignore-unused-cte { select * from sub; } {Jamie} -if {[info exists ::env(SQLITE_EXEC)] && $::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental"} { +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); diff --git a/testing/update.test b/testing/update.test index 96cf27d0a..a9b9e8193 100755 --- a/testing/update.test +++ b/testing/update.test @@ -190,7 +190,7 @@ do_execsql_test_on_specific_db {:memory:} update_cache_full_regression_test_#162 SELECT count(*) FROM t; } {1} -if {[info exists ::env(SQLITE_EXEC)] && $::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental"} { +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); diff --git a/testing/where.test b/testing/where.test index 8331289f6..71e54b899 100755 --- a/testing/where.test +++ b/testing/where.test @@ -155,7 +155,7 @@ do_execsql_test where-clause-no-table-constant-condition-false-7 { select 1 where 'hamburger'; } {} -if {[info exists ::env(SQLITE_EXEC)] && $::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental"} { +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 @@ -406,7 +406,7 @@ do_execsql_test where-age-index-seek-regression-test-2 { select count(1) from users where age > 0; } {10000} -if {[info exists ::env(SQLITE_EXEC)] && $::env(SQLITE_EXEC) eq "scripts/limbo-sqlite3-index-experimental"} { +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} From 4514bd5681f19c464d1e65c213798fa5cf5fb8a3 Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Tue, 17 Jun 2025 13:44:00 +0200 Subject: [PATCH 14/19] use integer primary key on test_in_memory_fetchone_select_all_users --- bindings/python/tests/test_database.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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") From 26eb63b09f3165e2589ed0f123b103ff6a378c75 Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Tue, 17 Jun 2025 15:16:58 +0200 Subject: [PATCH 15/19] use integer pk in java tests --- .../test/java/tech/turso/jdbc4/JDBC4ResultSetTest.java | 4 ++-- .../test/java/tech/turso/jdbc4/JDBC4StatementTest.java | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) 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;")); } From de1f29dadf2663fa12b34f69c347efd74db77720 Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Tue, 17 Jun 2025 15:28:32 +0200 Subject: [PATCH 16/19] core_tester index fuzz tests with flag --- .github/workflows/rust.yml | 5 +++++ tests/Cargo.toml | 3 +++ tests/integration/fuzz/mod.rs | 2 ++ 3 files changed, 10 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 19ea342c4..5dd5745df 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 -p core_tester --features index_experimental -- --ignored index_scan + timeout-minutes: 20 clippy: 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/fuzz/mod.rs b/tests/integration/fuzz/mod.rs index 55b8c3a25..9af0360d8 100644 --- a/tests/integration/fuzz/mod.rs +++ b/tests/integration/fuzz/mod.rs @@ -165,6 +165,7 @@ mod tests { } #[test] + #[ignore = "only used with 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] + #[ignore = "only used with 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() { From b86491c54f88afecfd37eb536a74e2da7040174e Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Tue, 17 Jun 2025 15:32:44 +0200 Subject: [PATCH 17/19] more integer pk in java tests --- bindings/java/src/test/java/tech/turso/IntegrationTest.java | 2 +- .../src/test/java/tech/turso/jdbc4/JDBC4ConnectionTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 From 34592b172c35bd98f63480d850b11b6e2c1aa197 Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Tue, 17 Jun 2025 15:44:28 +0200 Subject: [PATCH 18/19] run index tests with flags instead of ignore --- .github/workflows/long_fuzz_tests_btree.yml | 4 ++++ .github/workflows/rust.yml | 2 +- core/storage/btree.rs | 3 +++ tests/integration/common.rs | 2 ++ tests/integration/fuzz/mod.rs | 6 ++++-- tests/integration/query_processing/test_write_path.rs | 1 + 6 files changed, 15 insertions(+), 3 deletions(-) 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 5dd5745df..3bacece0a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -44,7 +44,7 @@ jobs: - name: Tests with indexes env: RUST_LOG: ${{ runner.debug && 'limbo_core::storage=trace' || '' }} - run: cargo test --verbose -p core_tester --features index_experimental -- --ignored index_scan + run: cargo test --verbose --features index_experimental timeout-minutes: 20 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/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 9af0360d8..863f7fdfa 100644 --- a/tests/integration/fuzz/mod.rs +++ b/tests/integration/fuzz/mod.rs @@ -165,7 +165,7 @@ mod tests { } #[test] - #[ignore = "only used with feature index_experimental"] + #[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(); @@ -213,7 +213,7 @@ mod tests { } #[test] - #[ignore = "only used with feature index_experimental"] + #[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() { @@ -493,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(); @@ -1358,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(); From 871eee109f1dc69d7280406daaece85493c6c4db Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Tue, 17 Jun 2025 21:26:32 +0200 Subject: [PATCH 19/19] test_limbo_cli use SQLITE_EXEC if possible --- testing/cli_tests/test_limbo_cli.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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)