Switch to runtime flag for enabling indexes

Makes it easier to test the feature:

```
$ cargo run --  --experimental-indexes
Limbo v0.0.22
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database
limbo> CREATE TABLE t(x);
limbo> CREATE INDEX t_idx ON t(x);
limbo> DROP INDEX t_idx;
```
This commit is contained in:
Pekka Enberg
2025-06-25 14:11:51 +03:00
parent 344eecb7ac
commit 2fc5c0ce5c
45 changed files with 248 additions and 226 deletions

View File

@@ -29,7 +29,7 @@ jobs:
env: env:
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
- name: Run ignored long tests with index - name: Run ignored long tests with index
run: cargo test --features index_experimental -- --ignored fuzz_long run: cargo test -- --ignored fuzz_long
env: env:
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1

View File

@@ -41,12 +41,6 @@ jobs:
RUST_LOG: ${{ runner.debug && 'limbo_core::storage=trace' || '' }} RUST_LOG: ${{ runner.debug && 'limbo_core::storage=trace' || '' }}
run: cargo test --verbose run: cargo test --verbose
timeout-minutes: 20 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: clippy:
runs-on: blacksmith-4vcpu-ubuntu-2404 runs-on: blacksmith-4vcpu-ubuntu-2404

View File

@@ -24,7 +24,7 @@ pub unsafe extern "C" fn db_open(path: *const c_char) -> *mut c_void {
p if p.contains(":memory:") => Arc::new(limbo_core::MemoryIO::new()), p if p.contains(":memory:") => Arc::new(limbo_core::MemoryIO::new()),
_ => Arc::new(limbo_core::PlatformIO::new().expect("Failed to create IO")), _ => Arc::new(limbo_core::PlatformIO::new().expect("Failed to create IO")),
}; };
let db = Database::open_file(io.clone(), path, false); let db = Database::open_file(io.clone(), path, false, false);
match db { match db {
Ok(db) => { Ok(db) => {
let conn = db.connect().unwrap(); let conn = db.connect().unwrap();

View File

@@ -68,7 +68,7 @@ pub extern "system" fn Java_tech_turso_core_LimboDB_openUtf8<'local>(
} }
}; };
let db = match Database::open_file(io.clone(), &path, false) { let db = match Database::open_file(io.clone(), &path, false, false) {
Ok(db) => db, Ok(db) => db,
Err(e) => { Err(e) => {
set_err_msg_and_throw_exception(&mut env, obj, LIMBO_ETC, e.to_string()); set_err_msg_and_throw_exception(&mut env, obj, LIMBO_ETC, e.to_string());

View File

@@ -71,7 +71,7 @@ impl Database {
let file = io.open_file(&path, flag, false).map_err(into_napi_error)?; let file = io.open_file(&path, flag, false).map_err(into_napi_error)?;
let db_file = Arc::new(DatabaseFile::new(file)); let db_file = Arc::new(DatabaseFile::new(file));
let db = limbo_core::Database::open(io.clone(), &path, db_file, false) let db = limbo_core::Database::open(io.clone(), &path, db_file, false, false)
.map_err(into_napi_error)?; .map_err(into_napi_error)?;
let conn = db.connect().map_err(into_napi_error)?; let conn = db.connect().map_err(into_napi_error)?;

View File

@@ -305,7 +305,7 @@ pub fn connect(path: &str) -> Result<Connection> {
io: Arc<dyn limbo_core::IO>, io: Arc<dyn limbo_core::IO>,
path: &str, path: &str,
) -> std::result::Result<Arc<limbo_core::Database>, PyErr> { ) -> std::result::Result<Arc<limbo_core::Database>, PyErr> {
limbo_core::Database::open_file(io, path, false).map_err(|e| { limbo_core::Database::open_file(io, path, false, false).map_err(|e| {
PyErr::new::<DatabaseError, _>(format!("Failed to open database: {:?}", e)) PyErr::new::<DatabaseError, _>(format!("Failed to open database: {:?}", e))
}) })
} }

View File

@@ -9,9 +9,6 @@ license.workspace = true
repository.workspace = true repository.workspace = true
description = "Limbo Rust API" description = "Limbo Rust API"
[features]
index_experimental = ["limbo_core/index_experimental"]
[dependencies] [dependencies]
limbo_core = { workspace = true, features = ["io_uring"] } limbo_core = { workspace = true, features = ["io_uring"] }
thiserror = "2.0.9" thiserror = "2.0.9"

View File

@@ -83,12 +83,12 @@ impl Builder {
match self.path.as_str() { match self.path.as_str() {
":memory:" => { ":memory:" => {
let io: Arc<dyn limbo_core::IO> = Arc::new(limbo_core::MemoryIO::new()); let io: Arc<dyn limbo_core::IO> = Arc::new(limbo_core::MemoryIO::new());
let db = limbo_core::Database::open_file(io, self.path.as_str(), false)?; let db = limbo_core::Database::open_file(io, self.path.as_str(), false, false)?;
Ok(Database { inner: db }) Ok(Database { inner: db })
} }
path => { path => {
let io: Arc<dyn limbo_core::IO> = Arc::new(limbo_core::PlatformIO::new()?); let io: Arc<dyn limbo_core::IO> = Arc::new(limbo_core::PlatformIO::new()?);
let db = limbo_core::Database::open_file(io, path, false)?; let db = limbo_core::Database::open_file(io, path, false, false)?;
Ok(Database { inner: db }) Ok(Database { inner: db })
} }
} }

View File

@@ -22,7 +22,7 @@ impl Database {
let io: Arc<dyn limbo_core::IO> = Arc::new(PlatformIO { vfs: VFS::new() }); let io: Arc<dyn limbo_core::IO> = Arc::new(PlatformIO { vfs: VFS::new() });
let file = io.open_file(path, OpenFlags::Create, false).unwrap(); let file = io.open_file(path, OpenFlags::Create, false).unwrap();
let db_file = Arc::new(DatabaseFile::new(file)); let db_file = Arc::new(DatabaseFile::new(file));
let db = limbo_core::Database::open(io, path, db_file, false).unwrap(); let db = limbo_core::Database::open(io, path, db_file, false, false).unwrap();
let conn = db.connect().unwrap(); let conn = db.connect().unwrap();
Database { db, conn } Database { db, conn }
} }

View File

@@ -50,7 +50,6 @@ toml_edit = {version = "0.22.24", features = ["serde"]}
[features] [features]
default = ["io_uring"] default = ["io_uring"]
io_uring = ["limbo_core/io_uring"] io_uring = ["limbo_core/io_uring"]
index_experimental = ["limbo_core/index_experimental"]
[build-dependencies] [build-dependencies]
syntect = "5.2.0" syntect = "5.2.0"

View File

@@ -57,6 +57,8 @@ pub struct Opts {
pub vfs: Option<String>, pub vfs: Option<String>,
#[clap(long, help = "Enable experimental MVCC feature")] #[clap(long, help = "Enable experimental MVCC feature")]
pub experimental_mvcc: bool, pub experimental_mvcc: bool,
#[clap(long, help = "Enable experimental indexing feature")]
pub experimental_indexes: bool,
#[clap(short = 't', long, help = "specify output file for log traces")] #[clap(short = 't', long, help = "specify output file for log traces")]
pub tracing_output: Option<String>, pub tracing_output: Option<String>,
} }
@@ -129,7 +131,12 @@ impl Limbo {
}; };
( (
io.clone(), io.clone(),
Database::open_file(io.clone(), &db_file, opts.experimental_mvcc)?, Database::open_file(
io.clone(),
&db_file,
opts.experimental_mvcc,
opts.experimental_indexes,
)?,
) )
}; };
let conn = db.connect()?; let conn = db.connect()?;
@@ -356,7 +363,10 @@ impl Limbo {
_path => get_io(DbLocation::Path, &self.opts.io.to_string())?, _path => get_io(DbLocation::Path, &self.opts.io.to_string())?,
} }
}; };
(io.clone(), Database::open_file(io.clone(), path, false)?) (
io.clone(),
Database::open_file(io.clone(), path, false, false)?,
)
}; };
self.io = io; self.io = io;
self.conn = db.connect()?; self.conn = db.connect()?;

View File

@@ -15,7 +15,6 @@ path = "lib.rs"
[features] [features]
default = ["fs", "uuid", "time", "json", "static"] default = ["fs", "uuid", "time", "json", "static"]
index_experimental = []
fs = ["limbo_ext/vfs"] fs = ["limbo_ext/vfs"]
json = [] json = []
uuid = ["limbo_uuid/static"] uuid = ["limbo_uuid/static"]

View File

@@ -18,7 +18,7 @@ fn bench_prepare_query(criterion: &mut Criterion) {
#[allow(clippy::arc_with_non_send_sync)] #[allow(clippy::arc_with_non_send_sync)]
let io = Arc::new(PlatformIO::new().unwrap()); let io = Arc::new(PlatformIO::new().unwrap());
let db = Database::open_file(io.clone(), "../testing/testing.db", false).unwrap(); let db = Database::open_file(io.clone(), "../testing/testing.db", false, false).unwrap();
let limbo_conn = db.connect().unwrap(); let limbo_conn = db.connect().unwrap();
let queries = [ let queries = [
@@ -65,7 +65,7 @@ fn bench_execute_select_rows(criterion: &mut Criterion) {
#[allow(clippy::arc_with_non_send_sync)] #[allow(clippy::arc_with_non_send_sync)]
let io = Arc::new(PlatformIO::new().unwrap()); let io = Arc::new(PlatformIO::new().unwrap());
let db = Database::open_file(io.clone(), "../testing/testing.db", false).unwrap(); let db = Database::open_file(io.clone(), "../testing/testing.db", false, false).unwrap();
let limbo_conn = db.connect().unwrap(); let limbo_conn = db.connect().unwrap();
let mut group = criterion.benchmark_group("Execute `SELECT * FROM users LIMIT ?`"); let mut group = criterion.benchmark_group("Execute `SELECT * FROM users LIMIT ?`");
@@ -134,7 +134,7 @@ fn bench_execute_select_1(criterion: &mut Criterion) {
#[allow(clippy::arc_with_non_send_sync)] #[allow(clippy::arc_with_non_send_sync)]
let io = Arc::new(PlatformIO::new().unwrap()); let io = Arc::new(PlatformIO::new().unwrap());
let db = Database::open_file(io.clone(), "../testing/testing.db", false).unwrap(); let db = Database::open_file(io.clone(), "../testing/testing.db", false, false).unwrap();
let limbo_conn = db.connect().unwrap(); let limbo_conn = db.connect().unwrap();
let mut group = criterion.benchmark_group("Execute `SELECT 1`"); let mut group = criterion.benchmark_group("Execute `SELECT 1`");
@@ -187,7 +187,7 @@ fn bench_execute_select_count(criterion: &mut Criterion) {
#[allow(clippy::arc_with_non_send_sync)] #[allow(clippy::arc_with_non_send_sync)]
let io = Arc::new(PlatformIO::new().unwrap()); let io = Arc::new(PlatformIO::new().unwrap());
let db = Database::open_file(io.clone(), "../testing/testing.db", false).unwrap(); let db = Database::open_file(io.clone(), "../testing/testing.db", false, false).unwrap();
let limbo_conn = db.connect().unwrap(); let limbo_conn = db.connect().unwrap();
let mut group = criterion.benchmark_group("Execute `SELECT count() FROM users`"); let mut group = criterion.benchmark_group("Execute `SELECT count() FROM users`");

View File

@@ -22,7 +22,7 @@ fn bench(criterion: &mut Criterion) {
#[allow(clippy::arc_with_non_send_sync)] #[allow(clippy::arc_with_non_send_sync)]
let io = Arc::new(PlatformIO::new().unwrap()); let io = Arc::new(PlatformIO::new().unwrap());
let db = Database::open_file(io.clone(), "../testing/testing.db", false).unwrap(); let db = Database::open_file(io.clone(), "../testing/testing.db", false, false).unwrap();
let limbo_conn = db.connect().unwrap(); let limbo_conn = db.connect().unwrap();
// Benchmark JSONB with different payload sizes // Benchmark JSONB with different payload sizes
@@ -491,7 +491,7 @@ fn bench_sequential_jsonb(criterion: &mut Criterion) {
#[allow(clippy::arc_with_non_send_sync)] #[allow(clippy::arc_with_non_send_sync)]
let io = Arc::new(PlatformIO::new().unwrap()); let io = Arc::new(PlatformIO::new().unwrap());
let db = Database::open_file(io.clone(), "../testing/testing.db", false).unwrap(); let db = Database::open_file(io.clone(), "../testing/testing.db", false, false).unwrap();
let limbo_conn = db.connect().unwrap(); let limbo_conn = db.connect().unwrap();
// Select a subset of JSON payloads to use in the sequential test // Select a subset of JSON payloads to use in the sequential test
@@ -648,7 +648,7 @@ fn bench_json_patch(criterion: &mut Criterion) {
#[allow(clippy::arc_with_non_send_sync)] #[allow(clippy::arc_with_non_send_sync)]
let io = Arc::new(PlatformIO::new().unwrap()); let io = Arc::new(PlatformIO::new().unwrap());
let db = Database::open_file(io.clone(), "../testing/testing.db", false).unwrap(); let db = Database::open_file(io.clone(), "../testing/testing.db", false, false).unwrap();
let limbo_conn = db.connect().unwrap(); let limbo_conn = db.connect().unwrap();
let json_patch_cases = [ let json_patch_cases = [

View File

@@ -30,7 +30,7 @@ fn bench_tpc_h_queries(criterion: &mut Criterion) {
#[allow(clippy::arc_with_non_send_sync)] #[allow(clippy::arc_with_non_send_sync)]
let io = Arc::new(PlatformIO::new().unwrap()); let io = Arc::new(PlatformIO::new().unwrap());
let db = Database::open_file(io.clone(), TPC_H_PATH, false).unwrap(); let db = Database::open_file(io.clone(), TPC_H_PATH, false, true).unwrap();
let limbo_conn = db.connect().unwrap(); let limbo_conn = db.connect().unwrap();
let queries = [ let queries = [

View File

@@ -109,7 +109,7 @@ impl Database {
} }
}, },
}; };
let db = Self::open_file(io.clone(), path, false)?; let db = Self::open_file(io.clone(), path, false, false)?;
Ok((io, db)) Ok((io, db))
} }
} }

View File

@@ -114,8 +114,13 @@ unsafe impl Sync for Database {}
impl Database { impl Database {
#[cfg(feature = "fs")] #[cfg(feature = "fs")]
pub fn open_file(io: Arc<dyn IO>, path: &str, enable_mvcc: bool) -> Result<Arc<Database>> { pub fn open_file(
Self::open_file_with_flags(io, path, OpenFlags::default(), enable_mvcc) io: Arc<dyn IO>,
path: &str,
enable_mvcc: bool,
enable_indexes: bool,
) -> Result<Arc<Database>> {
Self::open_file_with_flags(io, path, OpenFlags::default(), enable_mvcc, enable_indexes)
} }
#[cfg(feature = "fs")] #[cfg(feature = "fs")]
@@ -124,10 +129,11 @@ impl Database {
path: &str, path: &str,
flags: OpenFlags, flags: OpenFlags,
enable_mvcc: bool, enable_mvcc: bool,
enable_indexes: bool,
) -> Result<Arc<Database>> { ) -> Result<Arc<Database>> {
let file = io.open_file(path, flags, true)?; let file = io.open_file(path, flags, true)?;
let db_file = Arc::new(DatabaseFile::new(file)); let db_file = Arc::new(DatabaseFile::new(file));
Self::open_with_flags(io, path, db_file, flags, enable_mvcc) Self::open_with_flags(io, path, db_file, flags, enable_mvcc, enable_indexes)
} }
#[allow(clippy::arc_with_non_send_sync)] #[allow(clippy::arc_with_non_send_sync)]
@@ -136,8 +142,16 @@ impl Database {
path: &str, path: &str,
db_file: Arc<dyn DatabaseStorage>, db_file: Arc<dyn DatabaseStorage>,
enable_mvcc: bool, enable_mvcc: bool,
enable_indexes: bool,
) -> Result<Arc<Database>> { ) -> Result<Arc<Database>> {
Self::open_with_flags(io, path, db_file, OpenFlags::default(), enable_mvcc) Self::open_with_flags(
io,
path,
db_file,
OpenFlags::default(),
enable_mvcc,
enable_indexes,
)
} }
#[allow(clippy::arc_with_non_send_sync)] #[allow(clippy::arc_with_non_send_sync)]
@@ -147,6 +161,7 @@ impl Database {
db_file: Arc<dyn DatabaseStorage>, db_file: Arc<dyn DatabaseStorage>,
flags: OpenFlags, flags: OpenFlags,
enable_mvcc: bool, enable_mvcc: bool,
enable_indexes: bool,
) -> Result<Arc<Database>> { ) -> Result<Arc<Database>> {
let wal_path = format!("{}-wal", path); let wal_path = format!("{}-wal", path);
let maybe_shared_wal = WalFileShared::open_shared_if_exists(&io, wal_path.as_str())?; let maybe_shared_wal = WalFileShared::open_shared_if_exists(&io, wal_path.as_str())?;
@@ -167,7 +182,7 @@ impl Database {
let is_empty = db_size == 0 && !wal_has_frames; let is_empty = db_size == 0 && !wal_has_frames;
let shared_page_cache = Arc::new(RwLock::new(DumbLruPageCache::default())); let shared_page_cache = Arc::new(RwLock::new(DumbLruPageCache::default()));
let schema = Arc::new(RwLock::new(Schema::new())); let schema = Arc::new(RwLock::new(Schema::new(enable_indexes)));
let db = Database { let db = Database {
mv_store, mv_store,
path: path.to_string(), path: path.to_string(),
@@ -319,7 +334,7 @@ impl Database {
} }
}, },
}; };
let db = Self::open_file(io.clone(), path, false)?; let db = Self::open_file(io.clone(), path, false, false)?;
Ok((io, db)) Ok((io, db))
} }
} }

View File

@@ -21,18 +21,13 @@ pub struct Schema {
pub tables: HashMap<String, Arc<Table>>, 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>>>, 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>, pub has_indexes: std::collections::HashSet<String>,
pub indexes_enabled: bool,
} }
impl Schema { impl Schema {
pub fn new() -> Self { pub fn new(indexes_enabled: bool) -> Self {
let mut tables: HashMap<String, Arc<Table>> = HashMap::new(); let mut tables: HashMap<String, Arc<Table>> = HashMap::new();
#[cfg(not(feature = "index_experimental"))]
let has_indexes = std::collections::HashSet::new(); let has_indexes = std::collections::HashSet::new();
let indexes: HashMap<String, Vec<Arc<Index>>> = HashMap::new(); let indexes: HashMap<String, Vec<Arc<Index>>> = HashMap::new();
#[allow(clippy::arc_with_non_send_sync)] #[allow(clippy::arc_with_non_send_sync)]
@@ -43,8 +38,8 @@ impl Schema {
Self { Self {
tables, tables,
indexes, indexes,
#[cfg(not(feature = "index_experimental"))]
has_indexes, has_indexes,
indexes_enabled,
} }
} }
@@ -89,7 +84,6 @@ impl Schema {
} }
} }
#[cfg(feature = "index_experimental")]
pub fn add_index(&mut self, index: Arc<Index>) { pub fn add_index(&mut self, index: Arc<Index>) {
let table_name = normalize_ident(&index.table_name); let table_name = normalize_ident(&index.table_name);
self.indexes self.indexes
@@ -126,15 +120,17 @@ impl Schema {
.retain_mut(|other_idx| other_idx.name != idx.name); .retain_mut(|other_idx| other_idx.name != idx.name);
} }
#[cfg(not(feature = "index_experimental"))]
pub fn table_has_indexes(&self, table_name: &str) -> bool { pub fn table_has_indexes(&self, table_name: &str) -> bool {
self.has_indexes.contains(table_name) self.has_indexes.contains(table_name)
} }
#[cfg(not(feature = "index_experimental"))]
pub fn table_set_has_index(&mut self, table_name: &str) { pub fn table_set_has_index(&mut self, table_name: &str) {
self.has_indexes.insert(table_name.to_string()); self.has_indexes.insert(table_name.to_string());
} }
pub fn indexes_enabled(&self) -> bool {
self.indexes_enabled
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View File

@@ -6583,7 +6583,7 @@ mod tests {
.unwrap(); .unwrap();
} }
let io: Arc<dyn IO> = Arc::new(PlatformIO::new().unwrap()); let io: Arc<dyn IO> = Arc::new(PlatformIO::new().unwrap());
let db = Database::open_file(io.clone(), path.to_str().unwrap(), false).unwrap(); let db = Database::open_file(io.clone(), path.to_str().unwrap(), false, false).unwrap();
db db
} }
@@ -7123,7 +7123,6 @@ mod tests {
} }
} }
#[cfg(feature = "index_experimental")]
fn btree_index_insert_fuzz_run(attempts: usize, inserts: usize) { fn btree_index_insert_fuzz_run(attempts: usize, inserts: usize) {
use crate::storage::pager::CreateBTreeFlags; use crate::storage::pager::CreateBTreeFlags;
@@ -7311,7 +7310,6 @@ mod tests {
} }
#[test] #[test]
#[cfg(feature = "index_experimental")]
pub fn btree_index_insert_fuzz_run_equal_size() { pub fn btree_index_insert_fuzz_run_equal_size() {
btree_index_insert_fuzz_run(2, 1024); btree_index_insert_fuzz_run(2, 1024);
} }
@@ -7347,7 +7345,6 @@ mod tests {
#[test] #[test]
#[ignore] #[ignore]
#[cfg(feature = "index_experimental")]
pub fn fuzz_long_btree_index_insert_fuzz_run_equal_size() { pub fn fuzz_long_btree_index_insert_fuzz_run_equal_size() {
btree_index_insert_fuzz_run(2, 10_000); btree_index_insert_fuzz_run(2, 10_000);
} }

View File

@@ -24,16 +24,13 @@ pub fn translate_alter_table(
) -> Result<ProgramBuilder> { ) -> Result<ProgramBuilder> {
let (table_name, alter_table) = alter; let (table_name, alter_table) = alter;
let ast::Name(table_name) = table_name.name; let ast::Name(table_name) = table_name.name;
#[cfg(not(feature = "index_experimental"))] if schema.table_has_indexes(&table_name) && !schema.indexes_enabled() {
{
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 // Let's disable altering a table with indices altogether instead of checking column by
// column to be extra safe. // column to be extra safe.
crate::bail_parse_error!( crate::bail_parse_error!(
"Alter table disabled for table with indexes without index_experimental feature flag" "ALTER TABLE for table with indexes is disabled by default. Run with `--experimental-indexes` to enable this feature."
); );
} }
}
let Some(original_btree) = schema let Some(original_btree) = schema
.get_table(&table_name) .get_table(&table_name)

View File

@@ -154,7 +154,7 @@ fn emit_compound_select(
(cursor_id, index.clone()) (cursor_id, index.clone())
} }
_ => { _ => {
if cfg!(not(feature = "index_experimental")) { if !schema.indexes_enabled() {
crate::bail_parse_error!("UNION not supported without indexes"); crate::bail_parse_error!("UNION not supported without indexes");
} else { } else {
new_dedupe_index = true; new_dedupe_index = true;

View File

@@ -18,16 +18,13 @@ pub fn translate_delete(
syms: &SymbolTable, syms: &SymbolTable,
mut program: ProgramBuilder, mut program: ProgramBuilder,
) -> Result<ProgramBuilder> { ) -> Result<ProgramBuilder> {
#[cfg(not(feature = "index_experimental"))] if schema.table_has_indexes(&tbl_name.name.to_string()) && !schema.indexes_enabled() {
{
if schema.table_has_indexes(&tbl_name.name.to_string()) {
// Let's disable altering a table with indices altogether instead of checking column by // Let's disable altering a table with indices altogether instead of checking column by
// column to be extra safe. // column to be extra safe.
crate::bail_parse_error!( crate::bail_parse_error!(
"DELETE into table disabled for table with indexes and without index_experimental feature flag" "DELETE for table with indexes is disabled by default. Run with `--experimental-indexes` to enable this feature."
); );
} }
}
let mut delete_plan = prepare_delete_plan( let mut delete_plan = prepare_delete_plan(
schema, schema,
tbl_name, tbl_name,

View File

@@ -23,8 +23,10 @@ pub fn translate_create_index(
schema: &Schema, schema: &Schema,
mut program: ProgramBuilder, mut program: ProgramBuilder,
) -> crate::Result<ProgramBuilder> { ) -> crate::Result<ProgramBuilder> {
if cfg!(not(feature = "index_experimental")) { if !schema.indexes_enabled() {
crate::bail_parse_error!("CREATE INDEX enabled only with index_experimental feature"); crate::bail_parse_error!(
"CREATE INDEX is disabled by default. Run with `--experimental-indexes` to enable this feature."
);
} }
let idx_name = normalize_ident(idx_name); let idx_name = normalize_ident(idx_name);
let tbl_name = normalize_ident(tbl_name); let tbl_name = normalize_ident(tbl_name);
@@ -299,8 +301,10 @@ pub fn translate_drop_index(
schema: &Schema, schema: &Schema,
mut program: ProgramBuilder, mut program: ProgramBuilder,
) -> crate::Result<ProgramBuilder> { ) -> crate::Result<ProgramBuilder> {
if cfg!(not(feature = "index_experimental")) { if !schema.indexes_enabled() {
crate::bail_parse_error!("DROP INDEX enabled only with index_experimental feature"); crate::bail_parse_error!(
"DROP INDEX is disabled by default. Run with `--experimental-indexes` to enable this feature."
);
} }
let idx_name = normalize_ident(idx_name); let idx_name = normalize_ident(idx_name);
let opts = crate::vdbe::builder::ProgramBuilderOpts { let opts = crate::vdbe::builder::ProgramBuilderOpts {

View File

@@ -58,16 +58,13 @@ pub fn translate_insert(
crate::bail_parse_error!("ON CONFLICT clause is not supported"); 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()) && !schema.indexes_enabled() {
{
if schema.table_has_indexes(&tbl_name.name.to_string()) {
// Let's disable altering a table with indices altogether instead of checking column by // Let's disable altering a table with indices altogether instead of checking column by
// column to be extra safe. // column to be extra safe.
crate::bail_parse_error!( crate::bail_parse_error!(
"INSERT table disabled for table with indexes and without index_experimental feature flag" "INSERT to table with indexes is disabled by default. Run with `--experimental-indexes` to enable this feature."
); );
} }
}
let table_name = &tbl_name.name; let table_name = &tbl_name.name;
let table = match schema.get_table(table_name.0.as_str()) { let table = match schema.get_table(table_name.0.as_str()) {
Some(table) => table, Some(table) => table,

View File

@@ -71,6 +71,7 @@ pub fn optimize_select_plan(plan: &mut SelectPlan, schema: &Schema) -> Result<()
} }
let best_join_order = optimize_table_access( let best_join_order = optimize_table_access(
schema,
&mut plan.table_references, &mut plan.table_references,
&schema.indexes, &schema.indexes,
&mut plan.where_clause, &mut plan.where_clause,
@@ -119,6 +120,7 @@ fn optimize_update_plan(plan: &mut UpdatePlan, schema: &Schema) -> Result<()> {
optimize_select_plan(ephemeral_plan, schema)?; optimize_select_plan(ephemeral_plan, schema)?;
} }
let _ = optimize_table_access( let _ = optimize_table_access(
schema,
&mut plan.table_references, &mut plan.table_references,
&schema.indexes, &schema.indexes,
&mut plan.where_clause, &mut plan.where_clause,
@@ -150,6 +152,7 @@ fn optimize_subqueries(plan: &mut SelectPlan, schema: &Schema) -> Result<()> {
/// ///
/// Returns the join order if it was optimized, or None if the default join order was considered best. /// Returns the join order if it was optimized, or None if the default join order was considered best.
fn optimize_table_access( fn optimize_table_access(
schema: &Schema,
table_references: &mut TableReferences, table_references: &mut TableReferences,
available_indexes: &HashMap<String, Vec<Arc<Index>>>, available_indexes: &HashMap<String, Vec<Arc<Index>>>,
where_clause: &mut [WhereTerm], where_clause: &mut [WhereTerm],
@@ -240,8 +243,7 @@ fn optimize_table_access(
let table_idx = join_order_member.original_idx; let table_idx = join_order_member.original_idx;
let access_method = &access_methods_arena.borrow()[best_access_methods[i]]; let access_method = &access_methods_arena.borrow()[best_access_methods[i]];
if access_method.is_scan() { if access_method.is_scan() {
#[cfg(feature = "index_experimental")] let try_to_build_ephemeral_index = if schema.indexes_enabled() {
let try_to_build_ephemeral_index = {
let is_leftmost_table = i == 0; let is_leftmost_table = i == 0;
let uses_index = access_method.index.is_some(); let uses_index = access_method.index.is_some();
let source_table_is_from_clause_subquery = matches!( let source_table_is_from_clause_subquery = matches!(
@@ -249,9 +251,9 @@ fn optimize_table_access(
Table::FromClauseSubquery(_) Table::FromClauseSubquery(_)
); );
!is_leftmost_table && !uses_index && !source_table_is_from_clause_subquery !is_leftmost_table && !uses_index && !source_table_is_from_clause_subquery
} else {
false
}; };
#[cfg(not(feature = "index_experimental"))]
let try_to_build_ephemeral_index = false;
if !try_to_build_ephemeral_index { if !try_to_build_ephemeral_index {
joined_tables[table_idx].op = Operation::Scan { joined_tables[table_idx].op = Operation::Scan {

View File

@@ -25,7 +25,11 @@ use limbo_sqlite3_parser::ast::{
pub const ROWID: &str = "rowid"; pub const ROWID: &str = "rowid";
pub fn resolve_aggregates(top_level_expr: &Expr, aggs: &mut Vec<Aggregate>) -> Result<bool> { pub fn resolve_aggregates(
schema: &Schema,
top_level_expr: &Expr,
aggs: &mut Vec<Aggregate>,
) -> Result<bool> {
let mut contains_aggregates = false; let mut contains_aggregates = false;
walk_expr(top_level_expr, &mut |expr: &Expr| -> Result<WalkControl> { walk_expr(top_level_expr, &mut |expr: &Expr| -> Result<WalkControl> {
if aggs if aggs
@@ -51,14 +55,11 @@ pub fn resolve_aggregates(top_level_expr: &Expr, aggs: &mut Vec<Aggregate>) -> R
{ {
Ok(Func::Agg(f)) => { Ok(Func::Agg(f)) => {
let distinctness = Distinctness::from_ast(distinctness.as_ref()); let distinctness = Distinctness::from_ast(distinctness.as_ref());
#[cfg(not(feature = "index_experimental"))] if !schema.indexes_enabled() && distinctness.is_distinct() {
{
if distinctness.is_distinct() {
crate::bail_parse_error!( crate::bail_parse_error!(
"SELECT with DISTINCT is not allowed without indexes enabled" "SELECT with DISTINCT is not allowed without indexes enabled"
); );
} }
}
let num_args = args.as_ref().map_or(0, |args| args.len()); let num_args = args.as_ref().map_or(0, |args| args.len());
if distinctness.is_distinct() && num_args != 1 { if distinctness.is_distinct() && num_args != 1 {
crate::bail_parse_error!( crate::bail_parse_error!(
@@ -76,7 +77,7 @@ pub fn resolve_aggregates(top_level_expr: &Expr, aggs: &mut Vec<Aggregate>) -> R
_ => { _ => {
if let Some(args) = args { if let Some(args) = args {
for arg in args.iter() { for arg in args.iter() {
contains_aggregates |= resolve_aggregates(arg, aggs)?; contains_aggregates |= resolve_aggregates(schema, arg, aggs)?;
} }
} }
} }

View File

@@ -93,7 +93,7 @@ pub fn translate_create_table(
let index_regs = check_automatic_pk_index_required(&body, &mut program, &tbl_name.name.0)?; 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 let Some(index_regs) = index_regs.as_ref() {
if cfg!(not(feature = "index_experimental")) { if !schema.indexes_enabled() {
bail_parse_error!("Constraints UNIQUE and PRIMARY KEY (unless INTEGER PRIMARY KEY) on table are not supported without indexes"); 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() { for index_reg in index_regs.clone() {
@@ -613,14 +613,11 @@ pub fn translate_drop_table(
schema: &Schema, schema: &Schema,
mut program: ProgramBuilder, mut program: ProgramBuilder,
) -> Result<ProgramBuilder> { ) -> Result<ProgramBuilder> {
#[cfg(not(feature = "index_experimental"))] if !schema.indexes_enabled() && schema.table_has_indexes(&tbl_name.name.to_string()) {
{
if schema.table_has_indexes(&tbl_name.name.to_string()) {
bail_parse_error!( bail_parse_error!(
"DROP Table with indexes on the table enabled only with index_experimental feature" "DROP TABLE with indexes on the table is disabled by default. Run with `--experimental-indexes` to enable this feature."
); );
} }
}
let opts = ProgramBuilderOpts { let opts = ProgramBuilderOpts {
query_mode, query_mode,
num_cursors: 3, num_cursors: 3,

View File

@@ -203,14 +203,11 @@ fn prepare_one_select_plan(
distinctness, distinctness,
.. ..
} = *select_inner; } = *select_inner;
#[cfg(not(feature = "index_experimental"))] if !schema.indexes_enabled() && distinctness.is_some() {
{
if distinctness.is_some() {
crate::bail_parse_error!( crate::bail_parse_error!(
"SELECT with DISTINCT is not allowed without indexes enabled" "SELECT with DISTINCT is not allowed without indexes enabled"
); );
} }
}
let col_count = columns.len(); let col_count = columns.len();
if col_count == 0 { if col_count == 0 {
crate::bail_parse_error!("SELECT without columns is not allowed"); crate::bail_parse_error!("SELECT without columns is not allowed");
@@ -346,14 +343,11 @@ fn prepare_one_select_plan(
}; };
let distinctness = Distinctness::from_ast(distinctness.as_ref()); let distinctness = Distinctness::from_ast(distinctness.as_ref());
#[cfg(not(feature = "index_experimental"))] if !schema.indexes_enabled() && distinctness.is_distinct() {
{
if distinctness.is_distinct() {
crate::bail_parse_error!( crate::bail_parse_error!(
"SELECT with DISTINCT is not allowed without indexes enabled" "SELECT with DISTINCT is not allowed without indexes enabled"
); );
} }
}
if distinctness.is_distinct() && args_count != 1 { if distinctness.is_distinct() && args_count != 1 {
crate::bail_parse_error!("DISTINCT aggregate functions must have exactly one argument"); crate::bail_parse_error!("DISTINCT aggregate functions must have exactly one argument");
} }
@@ -393,8 +387,11 @@ fn prepare_one_select_plan(
}); });
} }
Ok(_) => { Ok(_) => {
let contains_aggregates = let contains_aggregates = resolve_aggregates(
resolve_aggregates(expr, &mut aggregate_expressions)?; schema,
expr,
&mut aggregate_expressions,
)?;
plan.result_columns.push(ResultSetColumn { plan.result_columns.push(ResultSetColumn {
alias: maybe_alias.as_ref().map(|alias| match alias { alias: maybe_alias.as_ref().map(|alias| match alias {
ast::As::Elided(alias) => alias.0.clone(), ast::As::Elided(alias) => alias.0.clone(),
@@ -409,6 +406,7 @@ fn prepare_one_select_plan(
{ {
if let ExtFunc::Scalar(_) = f.as_ref().func { if let ExtFunc::Scalar(_) = f.as_ref().func {
let contains_aggregates = resolve_aggregates( let contains_aggregates = resolve_aggregates(
schema,
expr, expr,
&mut aggregate_expressions, &mut aggregate_expressions,
)?; )?;
@@ -486,7 +484,7 @@ fn prepare_one_select_plan(
} }
expr => { expr => {
let contains_aggregates = let contains_aggregates =
resolve_aggregates(expr, &mut aggregate_expressions)?; resolve_aggregates(schema, expr, &mut aggregate_expressions)?;
plan.result_columns.push(ResultSetColumn { plan.result_columns.push(ResultSetColumn {
alias: maybe_alias.as_ref().map(|alias| match alias { alias: maybe_alias.as_ref().map(|alias| match alias {
ast::As::Elided(alias) => alias.0.clone(), ast::As::Elided(alias) => alias.0.clone(),
@@ -532,7 +530,7 @@ fn prepare_one_select_plan(
Some(&plan.result_columns), Some(&plan.result_columns),
)?; )?;
let contains_aggregates = let contains_aggregates =
resolve_aggregates(expr, &mut aggregate_expressions)?; resolve_aggregates(schema, expr, &mut aggregate_expressions)?;
if !contains_aggregates { if !contains_aggregates {
// TODO: sqlite allows HAVING clauses with non aggregate expressions like // TODO: sqlite allows HAVING clauses with non aggregate expressions like
// HAVING id = 5. We should support this too eventually (I guess). // HAVING id = 5. We should support this too eventually (I guess).
@@ -567,7 +565,7 @@ fn prepare_one_select_plan(
&mut plan.table_references, &mut plan.table_references,
Some(&plan.result_columns), Some(&plan.result_columns),
)?; )?;
resolve_aggregates(&o.expr, &mut plan.aggregates)?; resolve_aggregates(schema, &o.expr, &mut plan.aggregates)?;
key.push((o.expr, o.order.unwrap_or(ast::SortOrder::Asc))); key.push((o.expr, o.order.unwrap_or(ast::SortOrder::Asc)));
} }

View File

@@ -104,16 +104,13 @@ pub fn prepare_update_plan(
bail_parse_error!("ON CONFLICT clause is not supported"); bail_parse_error!("ON CONFLICT clause is not supported");
} }
let table_name = &body.tbl_name.name; let table_name = &body.tbl_name.name;
#[cfg(not(feature = "index_experimental"))] if schema.table_has_indexes(&table_name.to_string()) && !schema.indexes_enabled() {
{
if schema.table_has_indexes(&table_name.to_string()) {
// Let's disable altering a table with indices altogether instead of checking column by // Let's disable altering a table with indices altogether instead of checking column by
// column to be extra safe. // column to be extra safe.
bail_parse_error!( bail_parse_error!(
"UPDATE table disabled for table with indexes and without index_experimental feature flag" "UPDATE table disabled for table with indexes is disabled by default. Run with `--experimental-indexes` to enable this feature."
); );
} }
}
let table = match schema.get_table(table_name.0.as_str()) { let table = match schema.get_table(table_name.0.as_str()) {
Some(table) => table, Some(table) => table,
None => bail_parse_error!("Parse error: no such table: {}", table_name), None => bail_parse_error!("Parse error: no such table: {}", table_name),

View File

@@ -138,10 +138,9 @@ pub fn parse_schema_rows(
} }
} }
for unparsed_sql_from_index in from_sql_indexes { for unparsed_sql_from_index in from_sql_indexes {
#[cfg(not(feature = "index_experimental"))] if !schema.indexes_enabled() {
schema.table_set_has_index(&unparsed_sql_from_index.table_name); schema.table_set_has_index(&unparsed_sql_from_index.table_name);
#[cfg(feature = "index_experimental")] } else {
{
let table = schema let table = schema
.get_btree_table(&unparsed_sql_from_index.table_name) .get_btree_table(&unparsed_sql_from_index.table_name)
.unwrap(); .unwrap();
@@ -154,10 +153,9 @@ pub fn parse_schema_rows(
} }
} }
for automatic_index in automatic_indices { for automatic_index in automatic_indices {
#[cfg(not(feature = "index_experimental"))] if !schema.indexes_enabled() {
schema.table_set_has_index(&automatic_index.0); schema.table_set_has_index(&automatic_index.0);
#[cfg(feature = "index_experimental")] } else {
{
let table = schema.get_btree_table(&automatic_index.0).unwrap(); let table = schema.get_btree_table(&automatic_index.0).unwrap();
let ret_index = schema::Index::automatic_from_primary_key_and_unique( let ret_index = schema::Index::automatic_from_primary_key_and_unique(
table.as_ref(), table.as_ref(),

View File

@@ -4946,7 +4946,7 @@ pub fn op_parse_schema(
} }
} else { } else {
let stmt = conn.prepare("SELECT * FROM sqlite_schema")?; let stmt = conn.prepare("SELECT * FROM sqlite_schema")?;
let mut new = Schema::new(); let mut new = Schema::new(conn.schema.read().indexes_enabled());
// TODO: This function below is synchronous, make it async // TODO: This function below is synchronous, make it async
{ {

View File

@@ -3,9 +3,6 @@ name = "limbo-multitenancy"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[features]
index_experimental = ["limbo_core/index_experimental"]
[dependencies] [dependencies]
clap = { version = "4.5", features = ["derive"] } clap = { version = "4.5", features = ["derive"] }
env_logger = "0.11.0" env_logger = "0.11.0"

View File

@@ -2,7 +2,7 @@
# if RUST_LOG is non-empty, enable tracing output # if RUST_LOG is non-empty, enable tracing output
if [ -n "$RUST_LOG" ]; then if [ -n "$RUST_LOG" ]; then
target/debug/limbo_index_experimental -m list -t testing/test.log "$@" target/debug/limbo --experimental-indexes -m list -t testing/test.log "$@"
else else
target/debug/limbo_index_experimental -m list "$@" target/debug/limbo --experimental-indexes -m list "$@"
fi fi

View File

@@ -648,6 +648,7 @@ impl Interaction {
env.io.clone(), env.io.clone(),
&db_path, &db_path,
false, false,
false,
) { ) {
Ok(db) => db, Ok(db) => db,
Err(e) => { Err(e) => {

View File

@@ -135,7 +135,7 @@ impl SimulatorEnv {
std::fs::remove_file(wal_path).unwrap(); std::fs::remove_file(wal_path).unwrap();
} }
let db = match Database::open_file(io.clone(), db_path.to_str().unwrap(), false) { let db = match Database::open_file(io.clone(), db_path.to_str().unwrap(), false, false) {
Ok(db) => db, Ok(db) => db,
Err(e) => { Err(e) => {
panic!("error opening simulator test file {:?}: {:?}", db_path, e); panic!("error opening simulator test file {:?}: {:?}", db_path, e);

View File

@@ -126,7 +126,7 @@ pub unsafe extern "C" fn sqlite3_open(
Err(_) => return SQLITE_CANTOPEN, Err(_) => return SQLITE_CANTOPEN,
}, },
}; };
match limbo_core::Database::open_file(io.clone(), filename, false) { match limbo_core::Database::open_file(io.clone(), filename, false, false) {
Ok(db) => { Ok(db) => {
let conn = db.connect().unwrap(); let conn = db.connect().unwrap();
*db_out = Box::leak(Box::new(sqlite3::new(io, db, conn))); *db_out = Box::leak(Box::new(sqlite3::new(io, db, conn)));

View File

@@ -14,8 +14,6 @@ publish = false
name = "limbo_stress" name = "limbo_stress"
path = "main.rs" path = "main.rs"
[features]
index_experimental = ["limbo/index_experimental"]
[dependencies] [dependencies]
antithesis_sdk = "0.2.5" antithesis_sdk = "0.2.5"
clap = { version = "4.5", features = ["derive"] } clap = { version = "4.5", features = ["derive"] }

View File

@@ -165,7 +165,8 @@ impl ArbitrarySchema {
.map(|col| { .map(|col| {
let mut col_def = let mut col_def =
format!(" {} {}", col.name, data_type_to_sql(&col.data_type)); format!(" {} {}", col.name, data_type_to_sql(&col.data_type));
if cfg!(feature = "index_experimental") { if false {
/* FIXME */
for constraint in &col.constraints { for constraint in &col.constraints {
col_def.push(' '); col_def.push(' ');
col_def.push_str(&constraint_to_sql(constraint)); col_def.push_str(&constraint_to_sql(constraint));

View File

@@ -14,9 +14,6 @@ path = "lib.rs"
name = "integration_tests" name = "integration_tests"
path = "integration/mod.rs" path = "integration/mod.rs"
[features]
index_experimental = ["limbo_core/index_experimental"]
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
env_logger = "0.10.1" env_logger = "0.10.1"

View File

@@ -18,11 +18,11 @@ unsafe impl Send for TempDatabase {}
#[allow(dead_code, clippy::arc_with_non_send_sync)] #[allow(dead_code, clippy::arc_with_non_send_sync)]
impl TempDatabase { impl TempDatabase {
pub fn new_empty() -> Self { pub fn new_empty(enable_indexes: bool) -> Self {
Self::new(&format!("test-{}.db", rng().next_u32())) Self::new(&format!("test-{}.db", rng().next_u32()), enable_indexes)
} }
pub fn new(db_name: &str) -> Self { pub fn new(db_name: &str, enable_indexes: bool) -> Self {
let mut path = TempDir::new().unwrap().keep(); let mut path = TempDir::new().unwrap().keep();
path.push(db_name); path.push(db_name);
let io: Arc<dyn IO + Send> = Arc::new(limbo_core::PlatformIO::new().unwrap()); let io: Arc<dyn IO + Send> = Arc::new(limbo_core::PlatformIO::new().unwrap());
@@ -31,19 +31,33 @@ impl TempDatabase {
path.to_str().unwrap(), path.to_str().unwrap(),
limbo_core::OpenFlags::default(), limbo_core::OpenFlags::default(),
false, false,
enable_indexes,
) )
.unwrap(); .unwrap();
Self { path, io, db } Self { path, io, db }
} }
pub fn new_with_existent(db_path: &Path) -> Self { pub fn new_with_existent(db_path: &Path, enable_indexes: bool) -> Self {
Self::new_with_existent_with_flags(db_path, limbo_core::OpenFlags::default()) Self::new_with_existent_with_flags(
db_path,
limbo_core::OpenFlags::default(),
enable_indexes,
)
} }
pub fn new_with_existent_with_flags(db_path: &Path, flags: limbo_core::OpenFlags) -> Self { pub fn new_with_existent_with_flags(
db_path: &Path,
flags: limbo_core::OpenFlags,
enable_indexes: bool,
) -> Self {
let io: Arc<dyn IO + Send> = Arc::new(limbo_core::PlatformIO::new().unwrap()); let io: Arc<dyn IO + Send> = Arc::new(limbo_core::PlatformIO::new().unwrap());
let db = let db = Database::open_file_with_flags(
Database::open_file_with_flags(io.clone(), db_path.to_str().unwrap(), flags, false) io.clone(),
db_path.to_str().unwrap(),
flags,
false,
enable_indexes,
)
.unwrap(); .unwrap();
Self { Self {
path: db_path.to_path_buf(), path: db_path.to_path_buf(),
@@ -52,7 +66,7 @@ impl TempDatabase {
} }
} }
pub fn new_with_rusqlite(table_sql: &str) -> Self { pub fn new_with_rusqlite(table_sql: &str, enable_indexes: bool) -> Self {
let _ = tracing_subscriber::fmt() let _ = tracing_subscriber::fmt()
.with_max_level(tracing::Level::TRACE) .with_max_level(tracing::Level::TRACE)
.finish(); .finish();
@@ -71,6 +85,7 @@ impl TempDatabase {
path.to_str().unwrap(), path.to_str().unwrap(),
limbo_core::OpenFlags::default(), limbo_core::OpenFlags::default(),
false, false,
enable_indexes,
) )
.unwrap(); .unwrap();
@@ -85,9 +100,15 @@ impl TempDatabase {
conn conn
} }
pub fn limbo_database(&self) -> Arc<limbo_core::Database> { pub fn limbo_database(&self, enable_indexes: bool) -> Arc<limbo_core::Database> {
log::debug!("conneting to limbo"); log::debug!("conneting to limbo");
Database::open_file(self.io.clone(), self.path.to_str().unwrap(), false).unwrap() Database::open_file(
self.io.clone(),
self.path.to_str().unwrap(),
false,
enable_indexes,
)
.unwrap()
} }
} }
@@ -231,6 +252,7 @@ mod tests {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let tmp_db = TempDatabase::new_with_rusqlite( let tmp_db = TempDatabase::new_with_rusqlite(
"create table test (foo integer, bar integer, baz integer);", "create table test (foo integer, bar integer, baz integer);",
false,
); );
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
@@ -268,8 +290,11 @@ mod tests {
fn test_limbo_open_read_only() -> anyhow::Result<()> { fn test_limbo_open_read_only() -> anyhow::Result<()> {
let path = TempDir::new().unwrap().keep().join("temp_read_only"); let path = TempDir::new().unwrap().keep().join("temp_read_only");
{ {
let db = let db = TempDatabase::new_with_existent_with_flags(
TempDatabase::new_with_existent_with_flags(&path, limbo_core::OpenFlags::default()); &path,
limbo_core::OpenFlags::default(),
false,
);
let conn = db.connect_limbo(); let conn = db.connect_limbo();
let ret = limbo_exec_rows(&db, &conn, "CREATE table t(a)"); let ret = limbo_exec_rows(&db, &conn, "CREATE table t(a)");
assert!(ret.is_empty(), "{:?}", ret); assert!(ret.is_empty(), "{:?}", ret);
@@ -281,6 +306,7 @@ mod tests {
let db = TempDatabase::new_with_existent_with_flags( let db = TempDatabase::new_with_existent_with_flags(
&path, &path,
limbo_core::OpenFlags::default() | limbo_core::OpenFlags::ReadOnly, limbo_core::OpenFlags::default() | limbo_core::OpenFlags::ReadOnly,
false,
); );
let conn = db.connect_limbo(); let conn = db.connect_limbo();
let ret = limbo_exec_rows(&db, &conn, "SELECT * from t"); let ret = limbo_exec_rows(&db, &conn, "SELECT * from t");
@@ -293,11 +319,10 @@ mod tests {
} }
#[test] #[test]
#[cfg(feature = "index_experimental")]
fn test_unique_index_ordering() -> anyhow::Result<()> { fn test_unique_index_ordering() -> anyhow::Result<()> {
use rand::Rng; use rand::Rng;
let db = TempDatabase::new_empty(); let db = TempDatabase::new_empty(true);
let conn = db.connect_limbo(); let conn = db.connect_limbo();
let _ = limbo_exec_rows(&db, &conn, "CREATE TABLE t(x INTEGER UNIQUE)"); let _ = limbo_exec_rows(&db, &conn, "CREATE TABLE t(x INTEGER UNIQUE)");
@@ -336,10 +361,9 @@ mod tests {
} }
#[test] #[test]
#[cfg(feature = "index_experimental")]
fn test_large_unique_blobs() -> anyhow::Result<()> { fn test_large_unique_blobs() -> anyhow::Result<()> {
let path = TempDir::new().unwrap().keep().join("temp_read_only"); let path = TempDir::new().unwrap().keep().join("temp_read_only");
let db = TempDatabase::new_with_existent(&path); let db = TempDatabase::new_with_existent(&path, true);
let conn = db.connect_limbo(); let conn = db.connect_limbo();
let _ = limbo_exec_rows(&db, &conn, "CREATE TABLE t(x BLOB UNIQUE)"); let _ = limbo_exec_rows(&db, &conn, "CREATE TABLE t(x BLOB UNIQUE)");

View File

@@ -6,6 +6,7 @@ fn test_last_insert_rowid_basic() -> anyhow::Result<()> {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let tmp_db = TempDatabase::new_with_rusqlite( let tmp_db = TempDatabase::new_with_rusqlite(
"CREATE TABLE test_rowid (id INTEGER PRIMARY KEY, val TEXT);", "CREATE TABLE test_rowid (id INTEGER PRIMARY KEY, val TEXT);",
false,
); );
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
@@ -90,7 +91,7 @@ fn test_last_insert_rowid_basic() -> anyhow::Result<()> {
fn test_integer_primary_key() -> anyhow::Result<()> { fn test_integer_primary_key() -> anyhow::Result<()> {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let tmp_db = let tmp_db =
TempDatabase::new_with_rusqlite("CREATE TABLE test_rowid (id INTEGER PRIMARY KEY);"); TempDatabase::new_with_rusqlite("CREATE TABLE test_rowid (id INTEGER PRIMARY KEY);", false);
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
for query in &[ for query in &[

View File

@@ -2,9 +2,7 @@ pub mod grammar_generator;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#[cfg(feature = "index_experimental")]
use rand::seq::IndexedRandom; use rand::seq::IndexedRandom;
#[cfg(feature = "index_experimental")]
use std::collections::HashSet; use std::collections::HashSet;
use rand::{Rng, SeedableRng}; use rand::{Rng, SeedableRng};
@@ -30,7 +28,7 @@ mod tests {
/// [See this issue for more info](https://github.com/tursodatabase/limbo/issues/1763) /// [See this issue for more info](https://github.com/tursodatabase/limbo/issues/1763)
#[test] #[test]
pub fn fuzz_failure_issue_1763() { pub fn fuzz_failure_issue_1763() {
let db = TempDatabase::new_empty(); let db = TempDatabase::new_empty(false);
let limbo_conn = db.connect_limbo(); let limbo_conn = db.connect_limbo();
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap(); let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
let offending_query = "SELECT ((ceil(pow((((2.0))), (-2.0 - -1.0) / log(0.5)))) - -2.0)"; let offending_query = "SELECT ((ceil(pow((((2.0))), (-2.0 - -1.0) / log(0.5)))) - -2.0)";
@@ -45,7 +43,7 @@ mod tests {
#[test] #[test]
pub fn arithmetic_expression_fuzz_ex1() { pub fn arithmetic_expression_fuzz_ex1() {
let db = TempDatabase::new_empty(); let db = TempDatabase::new_empty(false);
let limbo_conn = db.connect_limbo(); let limbo_conn = db.connect_limbo();
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap(); let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
@@ -65,7 +63,7 @@ mod tests {
#[test] #[test]
pub fn rowid_seek_fuzz() { pub fn rowid_seek_fuzz() {
let db = TempDatabase::new_with_rusqlite("CREATE TABLE t(x INTEGER PRIMARY KEY)"); // INTEGER PRIMARY KEY is a rowid alias, so an index is not created let db = TempDatabase::new_with_rusqlite("CREATE TABLE t(x INTEGER PRIMARY KEY)", false); // INTEGER PRIMARY KEY is a rowid alias, so an index is not created
let sqlite_conn = rusqlite::Connection::open(db.path.clone()).unwrap(); let sqlite_conn = rusqlite::Connection::open(db.path.clone()).unwrap();
let insert = format!( let insert = format!(
@@ -184,9 +182,8 @@ mod tests {
} }
#[test] #[test]
#[cfg(feature = "index_experimental")]
pub fn index_scan_fuzz() { pub fn index_scan_fuzz() {
let db = TempDatabase::new_with_rusqlite("CREATE TABLE t(x PRIMARY KEY)"); let db = TempDatabase::new_with_rusqlite("CREATE TABLE t(x PRIMARY KEY)", true);
let sqlite_conn = rusqlite::Connection::open(db.path.clone()).unwrap(); let sqlite_conn = rusqlite::Connection::open(db.path.clone()).unwrap();
let insert = format!( let insert = format!(
@@ -232,7 +229,6 @@ mod tests {
} }
#[test] #[test]
#[cfg(feature = "index_experimental")]
/// A test for verifying that index seek+scan works correctly for compound keys /// A test for verifying that index seek+scan works correctly for compound keys
/// on indexes with various column orderings. /// on indexes with various column orderings.
pub fn index_scan_compound_key_fuzz() { pub fn index_scan_compound_key_fuzz() {
@@ -254,14 +250,14 @@ mod tests {
]; ];
// Create all different 3-column primary key permutations // Create all different 3-column primary key permutations
let dbs = [ let dbs = [
TempDatabase::new_with_rusqlite(table_defs[0]), TempDatabase::new_with_rusqlite(table_defs[0], true),
TempDatabase::new_with_rusqlite(table_defs[1]), TempDatabase::new_with_rusqlite(table_defs[1], true),
TempDatabase::new_with_rusqlite(table_defs[2]), TempDatabase::new_with_rusqlite(table_defs[2], true),
TempDatabase::new_with_rusqlite(table_defs[3]), TempDatabase::new_with_rusqlite(table_defs[3], true),
TempDatabase::new_with_rusqlite(table_defs[4]), TempDatabase::new_with_rusqlite(table_defs[4], true),
TempDatabase::new_with_rusqlite(table_defs[5]), TempDatabase::new_with_rusqlite(table_defs[5], true),
TempDatabase::new_with_rusqlite(table_defs[6]), TempDatabase::new_with_rusqlite(table_defs[6], true),
TempDatabase::new_with_rusqlite(table_defs[7]), TempDatabase::new_with_rusqlite(table_defs[7], true),
]; ];
let mut pk_tuples = HashSet::new(); let mut pk_tuples = HashSet::new();
while pk_tuples.len() < 100000 { while pk_tuples.len() < 100000 {
@@ -512,7 +508,6 @@ mod tests {
} }
#[test] #[test]
#[cfg(feature = "index_experimental")]
pub fn compound_select_fuzz() { pub fn compound_select_fuzz() {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let (mut rng, seed) = rng_from_time(); let (mut rng, seed) = rng_from_time();
@@ -528,7 +523,7 @@ mod tests {
const MAX_SELECTS_IN_UNION_EXTRA: usize = 2; const MAX_SELECTS_IN_UNION_EXTRA: usize = 2;
const MAX_LIMIT_VALUE: usize = 50; const MAX_LIMIT_VALUE: usize = 50;
let db = TempDatabase::new_empty(); let db = TempDatabase::new_empty(true);
let limbo_conn = db.connect_limbo(); let limbo_conn = db.connect_limbo();
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap(); let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
@@ -672,7 +667,7 @@ mod tests {
let sql = g.create().concat(" ").push_str("SELECT").push(expr).build(); let sql = g.create().concat(" ").push_str("SELECT").push(expr).build();
let db = TempDatabase::new_empty(); let db = TempDatabase::new_empty(false);
let limbo_conn = db.connect_limbo(); let limbo_conn = db.connect_limbo();
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap(); let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
@@ -693,7 +688,7 @@ mod tests {
#[test] #[test]
pub fn fuzz_ex() { pub fn fuzz_ex() {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let db = TempDatabase::new_empty(); let db = TempDatabase::new_empty(false);
let limbo_conn = db.connect_limbo(); let limbo_conn = db.connect_limbo();
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap(); let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
@@ -793,7 +788,7 @@ mod tests {
let sql = g.create().concat(" ").push_str("SELECT").push(expr).build(); let sql = g.create().concat(" ").push_str("SELECT").push(expr).build();
let db = TempDatabase::new_empty(); let db = TempDatabase::new_empty(false);
let limbo_conn = db.connect_limbo(); let limbo_conn = db.connect_limbo();
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap(); let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
@@ -957,7 +952,7 @@ mod tests {
let sql = g.create().concat(" ").push_str("SELECT").push(expr).build(); let sql = g.create().concat(" ").push_str("SELECT").push(expr).build();
let db = TempDatabase::new_empty(); let db = TempDatabase::new_empty(false);
let limbo_conn = db.connect_limbo(); let limbo_conn = db.connect_limbo();
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap(); let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
@@ -991,7 +986,6 @@ mod tests {
pub cast_expr: SymbolHandle, pub cast_expr: SymbolHandle,
pub case_expr: SymbolHandle, pub case_expr: SymbolHandle,
pub cmp_op: SymbolHandle, pub cmp_op: SymbolHandle,
#[cfg(feature = "index_experimental")]
pub number: SymbolHandle, pub number: SymbolHandle,
} }
@@ -1226,12 +1220,10 @@ mod tests {
cast_expr, cast_expr,
case_expr, case_expr,
cmp_op, cmp_op,
#[cfg(feature = "index_experimental")]
number, number,
} }
} }
#[cfg(feature = "index_experimental")]
fn predicate_builders(g: &GrammarGenerator, tables: Option<&[TestTable]>) -> PredicateBuilders { fn predicate_builders(g: &GrammarGenerator, tables: Option<&[TestTable]>) -> PredicateBuilders {
let (in_op, in_op_builder) = g.create_handle(); let (in_op, in_op_builder) = g.create_handle();
let (column, column_builder) = g.create_handle(); let (column, column_builder) = g.create_handle();
@@ -1330,7 +1322,7 @@ mod tests {
.push(expr) .push(expr)
.build(); .build();
let db = TempDatabase::new_empty(); let db = TempDatabase::new_empty(false);
let limbo_conn = db.connect_limbo(); let limbo_conn = db.connect_limbo();
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap(); let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
@@ -1365,7 +1357,7 @@ mod tests {
"SELECT * FROM t", "SELECT * FROM t",
], ],
] { ] {
let db = TempDatabase::new_empty(); let db = TempDatabase::new_empty(false);
let limbo_conn = db.connect_limbo(); let limbo_conn = db.connect_limbo();
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap(); let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
for query in queries.iter() { for query in queries.iter() {
@@ -1381,7 +1373,6 @@ mod tests {
} }
#[test] #[test]
#[cfg(feature = "index_experimental")]
pub fn table_logical_expression_fuzz_run() { pub fn table_logical_expression_fuzz_run() {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let g = GrammarGenerator::new(); let g = GrammarGenerator::new();
@@ -1393,7 +1384,7 @@ mod tests {
let predicate = predicate_builders(&g, Some(&tables)); let predicate = predicate_builders(&g, Some(&tables));
let expr = build_logical_expr(&g, &builders, Some(&predicate)); let expr = build_logical_expr(&g, &builders, Some(&predicate));
let db = TempDatabase::new_empty(); let db = TempDatabase::new_empty(true);
let limbo_conn = db.connect_limbo(); let limbo_conn = db.connect_limbo();
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap(); let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
for table in tables.iter() { for table in tables.iter() {

View File

@@ -3,7 +3,7 @@ use limbo_core::{StepResult, Value};
#[test] #[test]
fn test_statement_reset_bind() -> anyhow::Result<()> { fn test_statement_reset_bind() -> anyhow::Result<()> {
let tmp_db = TempDatabase::new_with_rusqlite("create table test (i integer);"); let tmp_db = TempDatabase::new_with_rusqlite("create table test (i integer);", false);
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
let mut stmt = conn.prepare("select ?")?; let mut stmt = conn.prepare("select ?")?;
@@ -47,7 +47,7 @@ fn test_statement_reset_bind() -> anyhow::Result<()> {
#[test] #[test]
fn test_statement_bind() -> anyhow::Result<()> { fn test_statement_bind() -> anyhow::Result<()> {
let tmp_db = TempDatabase::new_with_rusqlite("create table test (i integer);"); let tmp_db = TempDatabase::new_with_rusqlite("create table test (i integer);", false);
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
let mut stmt = conn.prepare("select ?, ?1, :named, ?3, ?4")?; let mut stmt = conn.prepare("select ?, ?1, :named, ?3, ?4")?;
@@ -112,6 +112,7 @@ fn test_insert_parameter_remap() -> anyhow::Result<()> {
let tmp_db = TempDatabase::new_with_rusqlite( let tmp_db = TempDatabase::new_with_rusqlite(
"create table test (a integer, b integer, c integer, d integer);", "create table test (a integer, b integer, c integer, d integer);",
false,
); );
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
@@ -176,6 +177,7 @@ fn test_insert_parameter_remap_all_params() -> anyhow::Result<()> {
let tmp_db = TempDatabase::new_with_rusqlite( let tmp_db = TempDatabase::new_with_rusqlite(
"create table test (a integer, b integer, c integer, d integer);", "create table test (a integer, b integer, c integer, d integer);",
false,
); );
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
let mut ins = conn.prepare("insert into test (d, a, c, b) values (?, ?, ?, ?);")?; let mut ins = conn.prepare("insert into test (d, a, c, b) values (?, ?, ?, ?);")?;
@@ -243,6 +245,7 @@ fn test_insert_parameter_multiple_remap_backwards() -> anyhow::Result<()> {
let tmp_db = TempDatabase::new_with_rusqlite( let tmp_db = TempDatabase::new_with_rusqlite(
"create table test (a integer, b integer, c integer, d integer);", "create table test (a integer, b integer, c integer, d integer);",
false,
); );
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
let mut ins = conn.prepare("insert into test (d,c,b,a) values (?, ?, ?, ?);")?; let mut ins = conn.prepare("insert into test (d,c,b,a) values (?, ?, ?, ?);")?;
@@ -309,6 +312,7 @@ fn test_insert_parameter_multiple_no_remap() -> anyhow::Result<()> {
let tmp_db = TempDatabase::new_with_rusqlite( let tmp_db = TempDatabase::new_with_rusqlite(
"create table test (a integer, b integer, c integer, d integer);", "create table test (a integer, b integer, c integer, d integer);",
false,
); );
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
let mut ins = conn.prepare("insert into test (a,b,c,d) values (?, ?, ?, ?);")?; let mut ins = conn.prepare("insert into test (a,b,c,d) values (?, ?, ?, ?);")?;
@@ -375,6 +379,7 @@ fn test_insert_parameter_multiple_row() -> anyhow::Result<()> {
let tmp_db = TempDatabase::new_with_rusqlite( let tmp_db = TempDatabase::new_with_rusqlite(
"create table test (a integer, b integer, c integer, d integer);", "create table test (a integer, b integer, c integer, d integer);",
false,
); );
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
let mut ins = conn.prepare("insert into test (b,a,d,c) values (?, ?, ?, ?), (?, ?, ?, ?);")?; let mut ins = conn.prepare("insert into test (b,a,d,c) values (?, ?, ?, ?), (?, ?, ?, ?);")?;
@@ -440,7 +445,7 @@ fn test_insert_parameter_multiple_row() -> anyhow::Result<()> {
#[test] #[test]
fn test_bind_parameters_update_query() -> anyhow::Result<()> { fn test_bind_parameters_update_query() -> anyhow::Result<()> {
let tmp_db = TempDatabase::new_with_rusqlite("create table test (a integer, b text);"); let tmp_db = TempDatabase::new_with_rusqlite("create table test (a integer, b text);", false);
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
let mut ins = conn.prepare("insert into test (a, b) values (3, 'test1');")?; let mut ins = conn.prepare("insert into test (a, b) values (3, 'test1');")?;
loop { loop {
@@ -484,6 +489,7 @@ fn test_bind_parameters_update_query() -> anyhow::Result<()> {
fn test_bind_parameters_update_query_multiple_where() -> anyhow::Result<()> { fn test_bind_parameters_update_query_multiple_where() -> anyhow::Result<()> {
let tmp_db = TempDatabase::new_with_rusqlite( let tmp_db = TempDatabase::new_with_rusqlite(
"create table test (a integer, b text, c integer, d integer);", "create table test (a integer, b text, c integer, d integer);",
false,
); );
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
let mut ins = conn.prepare("insert into test (a, b, c, d) values (3, 'test1', 4, 5);")?; let mut ins = conn.prepare("insert into test (a, b, c, d) values (3, 'test1', 4, 5);")?;
@@ -529,8 +535,10 @@ fn test_bind_parameters_update_query_multiple_where() -> anyhow::Result<()> {
#[test] #[test]
fn test_bind_parameters_update_rowid_alias() -> anyhow::Result<()> { fn test_bind_parameters_update_rowid_alias() -> anyhow::Result<()> {
let tmp_db = let tmp_db = TempDatabase::new_with_rusqlite(
TempDatabase::new_with_rusqlite("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT);"); "CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT);",
false,
);
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
let mut ins = conn.prepare("insert into test (id, name) values (1, 'test');")?; let mut ins = conn.prepare("insert into test (id, name) values (1, 'test');")?;
loop { loop {
@@ -588,6 +596,7 @@ fn test_bind_parameters_update_rowid_alias() -> anyhow::Result<()> {
fn test_bind_parameters_update_rowid_alias_seek_rowid() -> anyhow::Result<()> { fn test_bind_parameters_update_rowid_alias_seek_rowid() -> anyhow::Result<()> {
let tmp_db = TempDatabase::new_with_rusqlite( let tmp_db = TempDatabase::new_with_rusqlite(
"CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT, age integer);", "CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT, age integer);",
false,
); );
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
conn.execute("insert into test (id, name, age) values (1, 'test', 4);")?; conn.execute("insert into test (id, name, age) values (1, 'test', 4);")?;
@@ -655,6 +664,7 @@ fn test_bind_parameters_update_rowid_alias_seek_rowid() -> anyhow::Result<()> {
fn test_bind_parameters_delete_rowid_alias_seek_out_of_order() -> anyhow::Result<()> { fn test_bind_parameters_delete_rowid_alias_seek_out_of_order() -> anyhow::Result<()> {
let tmp_db = TempDatabase::new_with_rusqlite( let tmp_db = TempDatabase::new_with_rusqlite(
"CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT, age integer);", "CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT, age integer);",
false,
); );
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
conn.execute("insert into test (id, name, age) values (1, 'correct', 4);")?; conn.execute("insert into test (id, name, age) values (1, 'correct', 4);")?;

View File

@@ -20,8 +20,10 @@ macro_rules! change_state {
#[ignore] #[ignore]
fn test_simple_overflow_page() -> anyhow::Result<()> { fn test_simple_overflow_page() -> anyhow::Result<()> {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let tmp_db = let tmp_db = TempDatabase::new_with_rusqlite(
TempDatabase::new_with_rusqlite("CREATE TABLE test (x INTEGER PRIMARY KEY, t TEXT);"); "CREATE TABLE test (x INTEGER PRIMARY KEY, t TEXT);",
false,
);
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
let mut huge_text = String::new(); let mut huge_text = String::new();
@@ -82,8 +84,10 @@ fn test_simple_overflow_page() -> anyhow::Result<()> {
fn test_sequential_overflow_page() -> anyhow::Result<()> { fn test_sequential_overflow_page() -> anyhow::Result<()> {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
maybe_setup_tracing(); maybe_setup_tracing();
let tmp_db = let tmp_db = TempDatabase::new_with_rusqlite(
TempDatabase::new_with_rusqlite("CREATE TABLE test (x INTEGER PRIMARY KEY, t TEXT);"); "CREATE TABLE test (x INTEGER PRIMARY KEY, t TEXT);",
false,
);
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
let iterations = 10_usize; let iterations = 10_usize;
@@ -152,7 +156,8 @@ fn test_sequential_write() -> anyhow::Result<()> {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
maybe_setup_tracing(); maybe_setup_tracing();
let tmp_db = TempDatabase::new_with_rusqlite("CREATE TABLE test (x INTEGER PRIMARY KEY);"); let tmp_db =
TempDatabase::new_with_rusqlite("CREATE TABLE test (x INTEGER PRIMARY KEY);", false);
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
let list_query = "SELECT * FROM test"; let list_query = "SELECT * FROM test";
@@ -187,7 +192,7 @@ fn test_sequential_write() -> anyhow::Result<()> {
/// https://github.com/tursodatabase/limbo/pull/679 /// https://github.com/tursodatabase/limbo/pull/679
fn test_regression_multi_row_insert() -> anyhow::Result<()> { fn test_regression_multi_row_insert() -> anyhow::Result<()> {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let tmp_db = TempDatabase::new_with_rusqlite("CREATE TABLE test (x REAL);"); let tmp_db = TempDatabase::new_with_rusqlite("CREATE TABLE test (x REAL);", false);
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
let insert_query = "INSERT INTO test VALUES (-2), (-3), (-1)"; let insert_query = "INSERT INTO test VALUES (-2), (-3), (-1)";
@@ -220,7 +225,7 @@ fn test_regression_multi_row_insert() -> anyhow::Result<()> {
#[test] #[test]
fn test_statement_reset() -> anyhow::Result<()> { fn test_statement_reset() -> anyhow::Result<()> {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let tmp_db = TempDatabase::new_with_rusqlite("create table test (i integer);"); let tmp_db = TempDatabase::new_with_rusqlite("create table test (i integer);", false);
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
conn.execute("insert into test values (1)")?; conn.execute("insert into test values (1)")?;
@@ -267,7 +272,8 @@ fn test_statement_reset() -> anyhow::Result<()> {
#[ignore] #[ignore]
fn test_wal_checkpoint() -> anyhow::Result<()> { fn test_wal_checkpoint() -> anyhow::Result<()> {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let tmp_db = TempDatabase::new_with_rusqlite("CREATE TABLE test (x INTEGER PRIMARY KEY);"); let tmp_db =
TempDatabase::new_with_rusqlite("CREATE TABLE test (x INTEGER PRIMARY KEY);", false);
// threshold is 1000 by default // threshold is 1000 by default
let iterations = 1001_usize; let iterations = 1001_usize;
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
@@ -294,7 +300,8 @@ fn test_wal_checkpoint() -> anyhow::Result<()> {
#[test] #[test]
fn test_wal_restart() -> anyhow::Result<()> { fn test_wal_restart() -> anyhow::Result<()> {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let tmp_db = TempDatabase::new_with_rusqlite("CREATE TABLE test (x INTEGER PRIMARY KEY);"); let tmp_db =
TempDatabase::new_with_rusqlite("CREATE TABLE test (x INTEGER PRIMARY KEY);", false);
// threshold is 1000 by default // threshold is 1000 by default
fn insert(i: usize, conn: &Arc<Connection>, tmp_db: &TempDatabase) -> anyhow::Result<()> { fn insert(i: usize, conn: &Arc<Connection>, tmp_db: &TempDatabase) -> anyhow::Result<()> {
@@ -339,7 +346,7 @@ fn test_wal_restart() -> anyhow::Result<()> {
#[test] #[test]
fn test_insert_after_big_blob() -> anyhow::Result<()> { fn test_insert_after_big_blob() -> anyhow::Result<()> {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let tmp_db = TempDatabase::new_with_rusqlite("CREATE TABLE temp (t1 BLOB, t2 INTEGER)"); let tmp_db = TempDatabase::new_with_rusqlite("CREATE TABLE temp (t1 BLOB, t2 INTEGER)", false);
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
conn.execute("insert into temp(t1) values (zeroblob (262144))")?; conn.execute("insert into temp(t1) values (zeroblob (262144))")?;
@@ -355,7 +362,7 @@ fn test_write_delete_with_index() -> anyhow::Result<()> {
maybe_setup_tracing(); maybe_setup_tracing();
let tmp_db = TempDatabase::new_with_rusqlite("CREATE TABLE test (x PRIMARY KEY);"); let tmp_db = TempDatabase::new_with_rusqlite("CREATE TABLE test (x PRIMARY KEY);", false);
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
let list_query = "SELECT * FROM test"; let list_query = "SELECT * FROM test";
@@ -404,13 +411,13 @@ fn test_write_delete_with_index() -> anyhow::Result<()> {
} }
#[test] #[test]
#[cfg(feature = "index_experimental")]
fn test_update_with_index() -> anyhow::Result<()> { fn test_update_with_index() -> anyhow::Result<()> {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
maybe_setup_tracing(); maybe_setup_tracing();
let tmp_db = TempDatabase::new_with_rusqlite("CREATE TABLE test (x REAL PRIMARY KEY, y TEXT);"); let tmp_db =
TempDatabase::new_with_rusqlite("CREATE TABLE test (x REAL PRIMARY KEY, y TEXT);", true);
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
run_query(&tmp_db, &conn, "INSERT INTO test VALUES (1.0, 'foo')")?; run_query(&tmp_db, &conn, "INSERT INTO test VALUES (1.0, 'foo')")?;
@@ -446,7 +453,7 @@ fn test_delete_with_index() -> anyhow::Result<()> {
maybe_setup_tracing(); maybe_setup_tracing();
let tmp_db = TempDatabase::new_with_rusqlite("CREATE TABLE t(x UNIQUE)"); let tmp_db = TempDatabase::new_with_rusqlite("CREATE TABLE t(x UNIQUE)", true);
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
run_query(&tmp_db, &conn, "INSERT INTO t VALUES (1), (2)")?; run_query(&tmp_db, &conn, "INSERT INTO t VALUES (1), (2)")?;
@@ -462,7 +469,7 @@ fn test_delete_with_index() -> anyhow::Result<()> {
#[test] #[test]
fn test_update_regression() -> anyhow::Result<()> { fn test_update_regression() -> anyhow::Result<()> {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let tmp_db = TempDatabase::new_with_rusqlite("CREATE TABLE imaginative_baroja (blithesome_hall BLOB,remarkable_lester INTEGER,generous_balagun TEXT,ample_earth INTEGER,marvelous_khadzhiev BLOB,glowing_parissi TEXT,insightful_ryner BLOB)"); let tmp_db = TempDatabase::new_with_rusqlite("CREATE TABLE imaginative_baroja (blithesome_hall BLOB,remarkable_lester INTEGER,generous_balagun TEXT,ample_earth INTEGER,marvelous_khadzhiev BLOB,glowing_parissi TEXT,insightful_ryner BLOB)", false);
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
conn.execute("INSERT INTO imaginative_baroja VALUES (X'617070726F61636861626C655F6F6D6164', 5581285929211692372, 'approachable_podur', -4145754929970306534, X'666F72747569746F75735F7368617270', 'sensible_amesly', X'636F6D70657469746976655F6669746368'), (X'6D6972746866756C5F686F6673746565', -8554670009677647372, 'shimmering_modkraftdk', 4993627046425025026, X'636F6E73696465726174655F63616765', 'breathtaking_boggs', X'616D617A696E675F73696D6F6E65'), (X'7669766163696F75735F7363687761727A', 5860599187854155616, 'sparkling_aurora', 3757552048117668067, X'756E697175655F6769617A', 'lovely_leroy', X'68617264776F726B696E675F6D696C6C6572'), (X'677265676172696F75735F7061657065', -488992130149088413, 'focused_brinker', 4503849242092922100, X'66756E6E795F6A616B736963', 'competitive_communications', X'657863656C6C656E745F7873696C656E74'), (X'7374756E6E696E675F74616E6E656E6261756D', -5634782647279946253, 'fabulous_crute', -3978009805517476564, X'72656C617865645F63617272796F7574', 'spellbinding_erkan', X'66756E6E795F646F626273'), (X'696D6167696E61746976655F746F6C6F6B6F6E6E696B6F7661', 4236471363502323025, 'excellent_wolke', 7606168469334609395, X'736C65656B5F6D6361666565', 'magnificent_riley', X'616D6961626C655F706173736164616B6973'), (X'77696C6C696E675F736872657665', 5048296470820985219, 'ambitious_jeppesen', 6961857167361512834, X'70617469656E745F6272696E6B6572', 'giving_kramm', X'726573706F6E7369626C655F7363686D696474'), (X'73656E7369626C655F6D757865726573', -5519194136843846790, 'frank_ruggero', 4354855935194921345, X'76697669645F63617365', 'focused_lovecruft', X'6D61676E69666963656E745F736B79')")?; conn.execute("INSERT INTO imaginative_baroja VALUES (X'617070726F61636861626C655F6F6D6164', 5581285929211692372, 'approachable_podur', -4145754929970306534, X'666F72747569746F75735F7368617270', 'sensible_amesly', X'636F6D70657469746976655F6669746368'), (X'6D6972746866756C5F686F6673746565', -8554670009677647372, 'shimmering_modkraftdk', 4993627046425025026, X'636F6E73696465726174655F63616765', 'breathtaking_boggs', X'616D617A696E675F73696D6F6E65'), (X'7669766163696F75735F7363687761727A', 5860599187854155616, 'sparkling_aurora', 3757552048117668067, X'756E697175655F6769617A', 'lovely_leroy', X'68617264776F726B696E675F6D696C6C6572'), (X'677265676172696F75735F7061657065', -488992130149088413, 'focused_brinker', 4503849242092922100, X'66756E6E795F6A616B736963', 'competitive_communications', X'657863656C6C656E745F7873696C656E74'), (X'7374756E6E696E675F74616E6E656E6261756D', -5634782647279946253, 'fabulous_crute', -3978009805517476564, X'72656C617865645F63617272796F7574', 'spellbinding_erkan', X'66756E6E795F646F626273'), (X'696D6167696E61746976655F746F6C6F6B6F6E6E696B6F7661', 4236471363502323025, 'excellent_wolke', 7606168469334609395, X'736C65656B5F6D6361666565', 'magnificent_riley', X'616D6961626C655F706173736164616B6973'), (X'77696C6C696E675F736872657665', 5048296470820985219, 'ambitious_jeppesen', 6961857167361512834, X'70617469656E745F6272696E6B6572', 'giving_kramm', X'726573706F6E7369626C655F7363686D696474'), (X'73656E7369626C655F6D757865726573', -5519194136843846790, 'frank_ruggero', 4354855935194921345, X'76697669645F63617365', 'focused_lovecruft', X'6D61676E69666963656E745F736B79')")?;
@@ -568,7 +575,7 @@ fn test_write_concurrent_connections() -> anyhow::Result<()> {
maybe_setup_tracing(); maybe_setup_tracing();
let tmp_db = TempDatabase::new_with_rusqlite("CREATE TABLE t(x)"); let tmp_db = TempDatabase::new_with_rusqlite("CREATE TABLE t(x)", false);
let num_connections = 4; let num_connections = 4;
let num_inserts_per_connection = 100; let num_inserts_per_connection = 100;
let mut connections = vec![]; let mut connections = vec![];

View File

@@ -9,7 +9,7 @@ use std::sync::{Arc, Mutex};
#[test] #[test]
fn test_wal_checkpoint_result() -> Result<()> { fn test_wal_checkpoint_result() -> Result<()> {
maybe_setup_tracing(); maybe_setup_tracing();
let tmp_db = TempDatabase::new("test_wal.db"); let tmp_db = TempDatabase::new("test_wal.db", false);
let conn = tmp_db.connect_limbo(); let conn = tmp_db.connect_limbo();
conn.execute("CREATE TABLE t1 (id text);")?; conn.execute("CREATE TABLE t1 (id text);")?;
@@ -36,8 +36,8 @@ fn test_wal_checkpoint_result() -> Result<()> {
#[ignore = "ignored for now because it's flaky"] #[ignore = "ignored for now because it's flaky"]
fn test_wal_1_writer_1_reader() -> Result<()> { fn test_wal_1_writer_1_reader() -> Result<()> {
maybe_setup_tracing(); maybe_setup_tracing();
let tmp_db = Arc::new(Mutex::new(TempDatabase::new("test_wal.db"))); let tmp_db = Arc::new(Mutex::new(TempDatabase::new("test_wal.db", false)));
let db = tmp_db.lock().unwrap().limbo_database(); let db = tmp_db.lock().unwrap().limbo_database(false);
{ {
let conn = db.connect().unwrap(); let conn = db.connect().unwrap();