mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-25 12:04:21 +01:00
Merge ' index_experimental flag to enable index usages ' from Pere Diaz Bou
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.
There is a small hack in Tcl tests where we check `if {[info exists
::env(SQLITE_EXEC)] && $::env(SQLITE_EXEC) eq "scripts/limbo-
sqlite3-index-experimental"} {`
to ensure we run those tests only with the script index-experimental.
Closes #1758
This commit is contained in:
4
.github/workflows/long_fuzz_tests_btree.yml
vendored
4
.github/workflows/long_fuzz_tests_btree.yml
vendored
@@ -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
|
||||
|
||||
10
.github/workflows/rust.yml
vendored
10
.github/workflows/rust.yml
vendored
@@ -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:
|
||||
|
||||
25
Makefile
25
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
|
||||
|
||||
@@ -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');");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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');");
|
||||
|
||||
|
||||
@@ -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;"));
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -19,20 +19,33 @@ const SCHEMA_TABLE_NAME_ALT: &str = "sqlite_master";
|
||||
|
||||
pub struct Schema {
|
||||
pub tables: HashMap<String, Arc<Table>>,
|
||||
// table_name to list of indexes for the table
|
||||
/// table_name to list of indexes for the table
|
||||
pub indexes: HashMap<String, Vec<Arc<Index>>>,
|
||||
/// 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<String>,
|
||||
}
|
||||
|
||||
impl Schema {
|
||||
pub fn new() -> Self {
|
||||
let mut tables: HashMap<String, Arc<Table>> = HashMap::new();
|
||||
#[cfg(not(feature = "index_experimental"))]
|
||||
let has_indexes = std::collections::HashSet::new();
|
||||
let indexes: HashMap<String, Vec<Arc<Index>>> = 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<Index>) {
|
||||
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)]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,16 @@ pub fn translate_alter_table(
|
||||
) -> Result<ProgramBuilder> {
|
||||
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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -18,6 +18,16 @@ pub fn translate_delete(
|
||||
syms: &SymbolTable,
|
||||
mut program: ProgramBuilder,
|
||||
) -> Result<ProgramBuilder> {
|
||||
#[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,
|
||||
|
||||
@@ -23,6 +23,9 @@ pub fn translate_create_index(
|
||||
schema: &Schema,
|
||||
mut program: ProgramBuilder,
|
||||
) -> crate::Result<ProgramBuilder> {
|
||||
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<ProgramBuilder> {
|
||||
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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -50,6 +50,14 @@ pub fn resolve_aggregates(top_level_expr: &Expr, aggs: &mut Vec<Aggregate>) -> 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!(
|
||||
|
||||
@@ -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<ProgramBuilder> {
|
||||
#[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,
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
43
core/util.rs
43
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(())
|
||||
}
|
||||
|
||||
8
scripts/limbo-sqlite3-index-experimental
Executable file
8
scripts/limbo-sqlite3-index-experimental
Executable file
@@ -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
|
||||
@@ -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}
|
||||
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}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
} {}
|
||||
|
||||
@@ -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;
|
||||
} {}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}
|
||||
10|Daniel|coat}
|
||||
|
||||
@@ -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}
|
||||
97}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user