From 003ad6cc64974e6a09d8fb8c7f6a3b1e31284b62 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Thu, 26 Dec 2024 15:10:03 -0500 Subject: [PATCH 01/18] allow failure assertions in the simulator, add creating the same table two times to the list of checked properties as a failure property example --- Cargo.lock | 1 + simulator/Cargo.toml | 1 + simulator/generation/plan.rs | 38 +++++++++++++++++++++++++++++++++--- simulator/main.rs | 2 +- simulator/model/query.rs | 2 +- 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6053a3e93..ced458793 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1171,6 +1171,7 @@ dependencies = [ "log", "rand", "rand_chacha", + "regex", "tempfile", ] diff --git a/simulator/Cargo.toml b/simulator/Cargo.toml index 31a54f1e6..dcb948bbe 100644 --- a/simulator/Cargo.toml +++ b/simulator/Cargo.toml @@ -23,3 +23,4 @@ tempfile = "3.0.7" env_logger = "0.10.1" anarchist-readable-name-generator-lib = "0.1.2" clap = { version = "4.5", features = ["derive"] } +regex = "1.10.5" \ No newline at end of file diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index 61b115f01..2a39ff759 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -16,7 +16,7 @@ use crate::generation::{frequency, Arbitrary, ArbitraryFrom}; use super::{pick, pick_index}; -pub(crate) type ResultSet = Vec>; +pub(crate) type ResultSet = Result>>; pub(crate) struct InteractionPlan { pub(crate) plan: Vec, @@ -195,7 +195,7 @@ impl ArbitraryFrom for InteractionPlan { } impl Interaction { - pub(crate) fn execute_query(&self, conn: &mut Rc) -> Result { + pub(crate) fn execute_query(&self, conn: &mut Rc) -> ResultSet { match self { Interaction::Query(query) => { let query_str = query.to_string(); @@ -340,13 +340,41 @@ fn property_insert_select(rng: &mut R, env: &SimulatorEnv) -> Inte ), func: Box::new(move |stack: &Vec| { let rows = stack.last().unwrap(); - rows.iter().any(|r| r == &row) + match rows { + Ok(rows) => rows.iter().any(|r| r == &row), + Err(_) => false, + } }), }); Interactions(vec![insert_query, select_query, assertion]) } +fn property_double_create_failure(rng: &mut R, env: &SimulatorEnv) -> Interactions { + let create_query = Create::arbitrary(rng); + let cq1 = Interaction::Query(Query::Create(create_query.clone())); + let cq2 = Interaction::Query(Query::Create(create_query.clone())); + + let assertion = Interaction::Assertion(Assertion { + message: + "creating two tables with the name should result in a failure for the second query" + .to_string(), + func: Box::new(move |stack: &Vec| { + let last = stack.last().unwrap(); + println!("last: {:?}", last); + match last { + Ok(_) => false, + Err(e) => { + let re = regex::Regex::new("Table .* already exists").unwrap(); + re.is_match(&e.to_string()) + } + } + }), + }); + + Interactions(vec![cq1, cq2, assertion]) +} + fn create_table(rng: &mut R, env: &SimulatorEnv) -> Interactions { let create_query = Interaction::Query(Query::Create(Create::arbitrary(rng))); Interactions(vec![create_query]) @@ -399,6 +427,10 @@ impl ArbitraryFrom<(&SimulatorEnv, InteractionStats)> for Interactions { Box::new(|rng: &mut R| create_table(rng, env)), ), (1, Box::new(|rng: &mut R| random_fault(rng, env))), + ( + 1, + Box::new(|rng: &mut R| property_double_create_failure(rng, env)), + ), ], rng, ) diff --git a/simulator/main.rs b/simulator/main.rs index 49f738c56..ec5dc845f 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -269,7 +269,7 @@ fn execute_interaction( }; log::debug!("{}", interaction); - let results = interaction.execute_query(conn)?; + let results = interaction.execute_query(conn); log::debug!("{:?}", results); stack.push(results); } diff --git a/simulator/model/query.rs b/simulator/model/query.rs index eeec68d08..e948c3318 100644 --- a/simulator/model/query.rs +++ b/simulator/model/query.rs @@ -61,7 +61,7 @@ pub(crate) enum Query { Delete(Delete), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) struct Create { pub(crate) table: Table, } From 12fee4df3760d6ff86eb87f1d60eae8116c5beb6 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Thu, 26 Dec 2024 15:20:19 -0500 Subject: [PATCH 02/18] remove the regex dependency as functionality is possible without it --- Cargo.lock | 1 - simulator/Cargo.toml | 3 +-- simulator/generation/plan.rs | 5 ++--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da64f8e3b..0f034d89d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1175,7 +1175,6 @@ dependencies = [ "log", "rand", "rand_chacha", - "regex", "tempfile", ] diff --git a/simulator/Cargo.toml b/simulator/Cargo.toml index dcb948bbe..462ceaec7 100644 --- a/simulator/Cargo.toml +++ b/simulator/Cargo.toml @@ -22,5 +22,4 @@ log = "0.4.20" tempfile = "3.0.7" env_logger = "0.10.1" anarchist-readable-name-generator-lib = "0.1.2" -clap = { version = "4.5", features = ["derive"] } -regex = "1.10.5" \ No newline at end of file +clap = { version = "4.5", features = ["derive"] } \ No newline at end of file diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index 2a39ff759..7be3ad420 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -352,6 +352,7 @@ fn property_insert_select(rng: &mut R, env: &SimulatorEnv) -> Inte fn property_double_create_failure(rng: &mut R, env: &SimulatorEnv) -> Interactions { let create_query = Create::arbitrary(rng); + let table_name = create_query.table.name.clone(); let cq1 = Interaction::Query(Query::Create(create_query.clone())); let cq2 = Interaction::Query(Query::Create(create_query.clone())); @@ -361,12 +362,10 @@ fn property_double_create_failure(rng: &mut R, env: &SimulatorEnv) .to_string(), func: Box::new(move |stack: &Vec| { let last = stack.last().unwrap(); - println!("last: {:?}", last); match last { Ok(_) => false, Err(e) => { - let re = regex::Regex::new("Table .* already exists").unwrap(); - re.is_match(&e.to_string()) + e.to_string().contains(&format!("Table {table_name} already exists")) } } }), From ad0288b39ca55cb7eae9d6189ec64b5f3db1b205 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Thu, 26 Dec 2024 15:23:10 -0500 Subject: [PATCH 03/18] fix formatting --- simulator/generation/plan.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index 7be3ad420..1857b4a00 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -364,9 +364,9 @@ fn property_double_create_failure(rng: &mut R, env: &SimulatorEnv) let last = stack.last().unwrap(); match last { Ok(_) => false, - Err(e) => { - e.to_string().contains(&format!("Table {table_name} already exists")) - } + Err(e) => e + .to_string() + .contains(&format!("Table {table_name} already exists")), } }), }); From 0beec6deaf7b2d10fd5b79bb4246880ff8131992 Mon Sep 17 00:00:00 2001 From: sonhmai <> Date: Fri, 27 Dec 2024 16:23:01 +0700 Subject: [PATCH 04/18] doc: add contributing guide for functions --- docs/internals/functions.md | 118 +++++++++++++++++++++ docs/internals/functions_compat_change.png | Bin 0 -> 25571 bytes 2 files changed, 118 insertions(+) create mode 100644 docs/internals/functions.md create mode 100644 docs/internals/functions_compat_change.png diff --git a/docs/internals/functions.md b/docs/internals/functions.md new file mode 100644 index 000000000..c8b871c16 --- /dev/null +++ b/docs/internals/functions.md @@ -0,0 +1,118 @@ +# How to contribute a SQL function implementation? + +Steps +1. Pick a `SQL functions` in [COMPAT.md](../../COMPAT.md) file with a No (not implemented yet) status. +2. Create an issue for that function. +3. Implement the function in a feature branch. +4. Push it as a Merge Request, get it review. + +## An example with function `unixepoch(..)` + +> Note that the files, code location, steps might be not exactly the same because of refactor but the idea of the changes needed in each layer stays. + +[Issue #158](https://github.com/tursodatabase/limbo/issues/158) was created for it. +Refer to commit [525f860](https://github.com/tursodatabase/limbo/commit/525f8600cacaff1dffc9e7fe9d274d89ed519149). + +``` +SQL_function --parser--> Func_enum ----> Instruction --VDBE--> Result +``` + +TODO for implementing the function: +- analysis + - read and try out how the function works in SQLite. + - compare `explain` output of SQLite and Limbo. +- add/ update the function definition in `functions.rs`. +- add/ update how to function is translated from `definition` to `instruction` in virtual machine layer VDBE. +- add/ update the function Rust execution code and tests in vdbe layer. +- add/ update how the bytecode `Program` executes when steps into the function. +- add/ update TCL tests for this function in limbo/testing. +- update doc for function compatibility. + +### Analysis + +How `unixepoch` works in SQLite? +```bash +> sqlite3 + +sqlite> explain select unixepoch('now'); +addr opcode p1 p2 p3 p4 p5 comment +---- ------------- ---- ---- ---- ------------- -- ------------- +0 Init 0 6 0 0 Start at 6 +1 Once 0 3 0 0 +2 Function 0 0 2 unixepoch(-1) 0 r[2]=func() +3 Copy 2 1 0 0 r[1]=r[2] +4 ResultRow 1 1 0 0 output=r[1] +5 Halt 0 0 0 0 +6 Goto 0 1 0 0 +``` + +Comparing that with `Limbo`: +```bash +# created a sqlite database file +> cargo run database.db + +Limbo v0.0.2 +Enter ".help" for usage hints. +limbo> explain select unixtimestamp('now'); +Parse error: unknown function unixtimestamp +``` + +We can see that the function is not implemented yet so the Parser did not understand it and throw an error `Parse error: unknown function unixtimestamp`. +- we only need to pay attention to opcode `Function` at addr 2. The rest is already set up in limbo. +- we have up to 5 registers p1 to p5 for each opcode. + +### Function definition + +For limbo to understand the meaning of `unixtimestamp`, we need to define it as a Function somewhere. +That place can be found currently in `core/functions.rs`. We need to edit 3 places +1. add to ScalarFunc as `unixtimestamp` is a scalar function. +2. add to Display to show the function as string in our program. +3. add to `fn resolve_function(..)` of `impl Func` to enable parsing from str to this function. + +https://github.com/tursodatabase/limbo/blob/69e3dd28f77e59927da4313e517b2b428ede480d/core/function.rs#L86 + +https://github.com/tursodatabase/limbo/blob/69e3dd28f77e59927da4313e517b2b428ede480d/core/function.rs#L131 + +https://github.com/tursodatabase/limbo/blob/69e3dd28f77e59927da4313e517b2b428ede480d/core/function.rs#L331 + +### Function translation + +How to translate the function into bytecode `Instruction`? + +https://github.com/tursodatabase/limbo/blob/525f8600cacaff1dffc9e7fe9d274d89ed519149/core/translate/expr.rs#L971C1-L989C48 + +### Function execution + +https://github.com/tursodatabase/limbo/commit/525f8600cacaff1dffc9e7fe9d274d89ed519149#diff-839435241d4ffb648ad2d162bc6ba6a94f052309865251dc2aff36eaa14fa3c5L94-R111 + +### Program bytecode execution + +https://github.com/tursodatabase/limbo/commit/525f8600cacaff1dffc9e7fe9d274d89ed519149#diff-14ede55920ec82e719d3d39a4c38a6b5c0d3e4fa1e7ff4d75e7f436820920fa7L33-R1392 + +If there is no `time value` (no start register) , we want to execute the function with default param `'now'` as in [SQLite spec](https://www.sqlite.org/lang_datefunc.html#time_values). + +> In all functions other than timediff(), the time-value (and all modifiers) may be omitted, in which case a time value of 'now' is assumed. + +```rust +if *start_reg == 0 { + let unixepoch: String = + exec_unixepoch(&OwnedValue::Text(Rc::new("now".to_string())))?; + state.registers[*dest] = OwnedValue::Text(Rc::new(unixepoch)); +} +``` + +### Adding tests + +Tests for `unixepoch` functions can be referenced from SQLite source code which is already very comprehensive. +- https://github.com/sqlite/sqlite/blob/f2b21a5f57e1a1db1a286c42af40563077635c3d/test/date3.test#L36 +- https://github.com/sqlite/sqlite/blob/f2b21a5f57e1a1db1a286c42af40563077635c3d/test/date.test#L604 + +https://github.com/tursodatabase/limbo/commit/525f8600cacaff1dffc9e7fe9d274d89ed519149#diff-a262766efd02e804b8dc2ac5642f2061fb59a9388e437e9f000ff289110c9ec0L123-R145 + +### Updating doc + +Update the COMPAT.md file to mark this function as implemented. Change Status to +- `Yes` if it is fully supported, +- `Partial` if supported but not fully yet compared to SQLite. + +![functions_compat_change.png](functions_compat_change.png) diff --git a/docs/internals/functions_compat_change.png b/docs/internals/functions_compat_change.png new file mode 100644 index 0000000000000000000000000000000000000000..729f0c4125a07d02302020f38a346ca7fae36b4a GIT binary patch literal 25571 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 literal 0 HcmV?d00001 From 38e9ed7d5b33131ad208dae174826fe752538b6a Mon Sep 17 00:00:00 2001 From: sonhmai <14060682+sonhmai@users.noreply.github.com> Date: Sat, 28 Dec 2024 11:28:54 +0700 Subject: [PATCH 05/18] make functions contrib doc better --- CONTRIBUTING.md | 4 + docs/internals/functions.md | 164 +++++++++++++++------ docs/internals/functions_compat_change.png | Bin 25571 -> 0 bytes 3 files changed, 125 insertions(+), 43 deletions(-) delete mode 100644 docs/internals/functions_compat_change.png diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e79e1f0c9..f1d77fe2e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,6 +17,10 @@ If you are new to Rust, the following books are recommended reading: * Jim Blandy et al. [Programming Rust, 2nd Edition](https://www.oreilly.com/library/view/programming-rust-2nd/9781492052586/). 2021 * Steve Klabnik and Carol Nichols. [The Rust Programming Language](https://doc.rust-lang.org/book/#the-rust-programming-language). 2022 +Examples of contributing + +* [How to contribute a SQL function implementation](docs/internals/functions.md) + ## Finding things to work on The issue tracker has issues tagged with [good first issue](https://github.com/penberg/limbo/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22), diff --git a/docs/internals/functions.md b/docs/internals/functions.md index 0cf1a174e..ff71b6864 100644 --- a/docs/internals/functions.md +++ b/docs/internals/functions.md @@ -6,15 +6,21 @@ Steps 3. Implement the function in a feature branch. 4. Push it as a Merge Request, get it review. -## An example with function `unixepoch(..)` +## An example with function `date(..)` > Note that the files, code location, steps might be not exactly the same because of refactor but the idea of the changes needed in each layer stays. [Issue #158](https://github.com/tursodatabase/limbo/issues/158) was created for it. -Refer to commit [525f860](https://github.com/tursodatabase/limbo/commit/525f8600cacaff1dffc9e7fe9d274d89ed519149). +Refer to commit [4ff7058](https://github.com/tursodatabase/limbo/commit/4ff705868a054643f6113cbe009655c32bc5f235). ``` -SQL_function --parser--> Func_enum ----> Instruction --VDBE--> Result +sql function: string +--Parser--> +enum Func +--translate--> +Instruction +--VDBE--> +Result ``` TODO for implementing the function: @@ -30,16 +36,16 @@ TODO for implementing the function: ### Analysis -How `unixepoch` works in SQLite? +How `date` works in SQLite? ```bash > sqlite3 -sqlite> explain select unixepoch('now'); +sqlite> explain select date('now'); addr opcode p1 p2 p3 p4 p5 comment ---- ------------- ---- ---- ---- ------------- -- ------------- 0 Init 0 6 0 0 Start at 6 1 Once 0 3 0 0 -2 Function 0 0 2 unixepoch(-1) 0 r[2]=func() +2 Function 0 0 2 date(-1) 0 r[2]=func() 3 Copy 2 1 0 0 r[1]=r[2] 4 ResultRow 1 1 0 0 output=r[1] 5 Halt 0 0 0 0 @@ -48,100 +54,172 @@ addr opcode p1 p2 p3 p4 p5 comment Comparing that with `Limbo`: ```bash -# created a sqlite database file +# created a sqlite database file database.db +# or cargo run to use the memory mode if it is already available. > cargo run database.db -Limbo v0.0.2 Enter ".help" for usage hints. -limbo> explain select unixtimestamp('now'); -Parse error: unknown function unixtimestamp +limbo> explain select date('now'); +Parse error: unknown function date ``` -We can see that the function is not implemented yet so the Parser did not understand it and throw an error `Parse error: unknown function unixtimestamp`. +We can see that the function is not implemented yet so the Parser did not understand it and throw an error `Parse error: unknown function date`. - we only need to pay attention to opcode `Function` at addr 2. The rest is already set up in limbo. - we have up to 5 registers p1 to p5 for each opcode. ### Function definition -For limbo to understand the meaning of `unixtimestamp`, we need to define it as a Function somewhere. +For limbo to understand the meaning of `date`, we need to define it as a Function somewhere. That place can be found currently in `core/functions.rs`. We need to edit 3 places -1. add to ScalarFunc as `unixtimestamp` is a scalar function. +1. add to ScalarFunc as `date` is a scalar function. ```diff +// file core/functions.rs pub enum ScalarFunc { // other funcs... - SqliteVersion, -+ UnixEpoch, - Hex + Soundex, ++ Date, + Time, // other funcs... } ``` 2. add to Display to show the function as string in our program. ```diff +// file core/functions.rs impl Display for ScalarFunc { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let str = match self { // ... - ScalarFunc::SqliteVersion => "sqlite_version".to_string(), -+ ScalarFunc::UnixEpoch => "unixepoch".to_string(), - ScalarFunc::Hex => "hex".to_string(), + ScalarFunc::Soundex => "soundex".to_string(), ++ ScalarFunc::Date => "date".to_string(), + ScalarFunc::Time => "time".to_string(), // ... } ``` 3. add to `fn resolve_function(..)` of `impl Func` to enable parsing from str to this function. ```diff +// file core/functions.rs impl Func { pub fn resolve_function(name: &str, arg_count: usize) -> Result { match name { // ... -+ "unixepoch" => Ok(Func::Scalar(ScalarFunc::UnixEpoch)), ++ "date" => Ok(Func::Scalar(ScalarFunc::Date)), // ... } ``` -https://github.com/tursodatabase/limbo/blob/69e3dd28f77e59927da4313e517b2b428ede480d/core/function.rs#L86 - -https://github.com/tursodatabase/limbo/blob/69e3dd28f77e59927da4313e517b2b428ede480d/core/function.rs#L131 - -https://github.com/tursodatabase/limbo/blob/69e3dd28f77e59927da4313e517b2b428ede480d/core/function.rs#L331 - ### Function translation How to translate the function into bytecode `Instruction`? +- `date` function can have zero to many arguments. +- in case there are arguments, we loop through the args and allocate a register `let target_reg = program.alloc_register();` +for each argument expression. +- then we emit the bytecode instruction for Function `program.emit_insn(Insn::Function {...})` -https://github.com/tursodatabase/limbo/blob/525f8600cacaff1dffc9e7fe9d274d89ed519149/core/translate/expr.rs#L971C1-L989C48 +https://github.com/tursodatabase/limbo/blob/69e3dd28f77e59927da4313e517b2b428ede480d/core/translate/expr.rs#L1235C1-L1256C26 + + +```diff +// file core/translate/expr.rs +pub fn translate_expr(...) -> Result { + // ... + match expr { + // .. + ast::Expr::FunctionCall { + // ... + match &func_ctx.func { + // ... + Func::Scalar(srf) => { + // ... ++ ScalarFunc::Date => { ++ if let Some(args) = args { ++ for arg in args.iter() { ++ // register containing result of each argument expression ++ let target_reg = program.alloc_register(); ++ _ = translate_expr( ++ program, ++ referenced_tables, ++ arg, ++ target_reg, ++ precomputed_exprs_to_registers, ++ )?; ++ } ++ } ++ program.emit_insn(Insn::Function { ++ constant_mask: 0, ++ start_reg: target_register + 1, ++ dest: target_register, ++ func: func_ctx, ++ }); ++ Ok(target_register) ++ } +// ... +``` ### Function execution -https://github.com/tursodatabase/limbo/commit/525f8600cacaff1dffc9e7fe9d274d89ed519149#diff-839435241d4ffb648ad2d162bc6ba6a94f052309865251dc2aff36eaa14fa3c5L94-R111 +The function execution code is implemented in `vdbe/datetime.rs` file [here](https://github.com/tursodatabase/limbo/commit/9cc965186fecf4ba4dd81c783a841c71575123bf#diff-839435241d4ffb648ad2d162bc6ba6a94f052309865251dc2aff36eaa14fa3c5R11-R30) as we already implemented the datetime features in this file. +Note that for other functions it might be implemented in other location in vdbe module. + +```diff +// file vdbe/datetime.rs +// ... ++ pub fn exec_date(values: &[OwnedValue]) -> OwnedValue { ++ // ... implementation ++ } + +// ... +``` ### Program bytecode execution -https://github.com/tursodatabase/limbo/commit/525f8600cacaff1dffc9e7fe9d274d89ed519149#diff-14ede55920ec82e719d3d39a4c38a6b5c0d3e4fa1e7ff4d75e7f436820920fa7L33-R1392 - -If there is no `time value` (no start register) , we want to execute the function with default param `'now'` as in [SQLite spec](https://www.sqlite.org/lang_datefunc.html#time_values). +Next step is to implement how the virtual machine (VDBE layer) executes the bytecode `Program` when the program step into the function instruction `Insn::Function` date `ScalarFunc::Date`. +Per [SQLite spec](https://www.sqlite.org/lang_datefunc.html#time_values) if there is no `time value` (no start register) , we want to execute the function with default param `'now'`. > In all functions other than timediff(), the time-value (and all modifiers) may be omitted, in which case a time value of 'now' is assumed. -```rust -if *start_reg == 0 { - let unixepoch: String = - exec_unixepoch(&OwnedValue::Text(Rc::new("now".to_string())))?; - state.registers[*dest] = OwnedValue::Text(Rc::new(unixepoch)); -} +```diff +// file vdbe/mod.rs +impl Program { + pub fn step<'a>(...) { + loop { + // ... + match isin { + // ... + Insn::Function { + // ... ++ ScalarFunc::Date => { ++ let result = ++ exec_date(&state.registers[*start_reg..*start_reg + arg_count]); ++ state.registers[*dest] = result; ++ } + // ... ``` ### Adding tests -Tests for `unixepoch` functions can be referenced from SQLite source code which is already very comprehensive. -- https://github.com/sqlite/sqlite/blob/f2b21a5f57e1a1db1a286c42af40563077635c3d/test/date3.test#L36 -- https://github.com/sqlite/sqlite/blob/f2b21a5f57e1a1db1a286c42af40563077635c3d/test/date.test#L604 +There are 2 kind of tests we need to add +1. tests for Rust code +2. TCL tests for executing the sql function -https://github.com/tursodatabase/limbo/commit/525f8600cacaff1dffc9e7fe9d274d89ed519149#diff-a262766efd02e804b8dc2ac5642f2061fb59a9388e437e9f000ff289110c9ec0L123-R145 +One test for the Rust code is shown as example below +https://github.com/tursodatabase/limbo/blob/69e3dd28f77e59927da4313e517b2b428ede480d/core/vdbe/datetime.rs#L620C1-L661C1 + +TCL tests for `date` functions can be referenced from SQLite source code which is already very comprehensive. +- https://github.com/sqlite/sqlite/blob/f2b21a5f57e1a1db1a286c42af40563077635c3d/test/date3.test#L36 +- https://github.com/sqlite/sqlite/blob/f2b21a5f57e1a1db1a286c42af40563077635c3d/test/date.test#L611C1-L652C73 ### Updating doc -Update the COMPAT.md file to mark this function as implemented. Change Status to +Update the [COMPAT.md](../../COMPAT.md) file to mark this function as implemented. Change Status to - `Yes` if it is fully supported, - `Partial` if supported but not fully yet compared to SQLite. -![functions_compat_change.png](functions_compat_change.png) +An example: +```diff +// file COMPAT.md +| Function | Status | Comment | +|------------------------------|---------|------------------------------| +- | date() | No | | ++ | date() | Yes | partially supports modifiers | +... +``` \ No newline at end of file diff --git a/docs/internals/functions_compat_change.png b/docs/internals/functions_compat_change.png deleted file mode 100644 index 729f0c4125a07d02302020f38a346ca7fae36b4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25571 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 From 854005b977e40ac702e03fe604c7e0be7666550b Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Sun, 29 Dec 2024 18:54:08 +0200 Subject: [PATCH 06/18] Run `cargo clippy --fix && cargo fmt` --- core/json/de.rs | 2 +- core/json/ser.rs | 16 ++++++++-------- core/storage/btree.rs | 8 ++++---- core/translate/emitter.rs | 12 ++++++------ core/translate/insert.rs | 4 ++-- core/translate/optimizer.rs | 2 +- core/translate/planner.rs | 4 ++-- core/types.rs | 2 +- core/vdbe/likeop.rs | 8 +++----- core/vdbe/mod.rs | 6 +++--- 10 files changed, 31 insertions(+), 33 deletions(-) diff --git a/core/json/de.rs b/core/json/de.rs index 02bad4adb..b96a29792 100644 --- a/core/json/de.rs +++ b/core/json/de.rs @@ -171,7 +171,7 @@ impl<'de> Deserializer<'de> { } } -impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { +impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { type Error = Error; fn deserialize_any(self, visitor: V) -> Result diff --git a/core/json/ser.rs b/core/json/ser.rs index 1afb8b497..3d5646584 100644 --- a/core/json/ser.rs +++ b/core/json/ser.rs @@ -30,7 +30,7 @@ impl Serializer { } } -impl<'a> ser::Serializer for &'a mut Serializer { +impl ser::Serializer for &mut Serializer { type Ok = (); type Error = Error; @@ -237,7 +237,7 @@ impl<'a> ser::Serializer for &'a mut Serializer { } } -impl<'a> ser::SerializeSeq for &'a mut Serializer { +impl ser::SerializeSeq for &mut Serializer { type Ok = (); type Error = Error; @@ -257,7 +257,7 @@ impl<'a> ser::SerializeSeq for &'a mut Serializer { } } -impl<'a> ser::SerializeTuple for &'a mut Serializer { +impl ser::SerializeTuple for &mut Serializer { type Ok = (); type Error = Error; @@ -273,7 +273,7 @@ impl<'a> ser::SerializeTuple for &'a mut Serializer { } } -impl<'a> ser::SerializeTupleStruct for &'a mut Serializer { +impl ser::SerializeTupleStruct for &mut Serializer { type Ok = (); type Error = Error; @@ -289,7 +289,7 @@ impl<'a> ser::SerializeTupleStruct for &'a mut Serializer { } } -impl<'a> ser::SerializeTupleVariant for &'a mut Serializer { +impl ser::SerializeTupleVariant for &mut Serializer { type Ok = (); type Error = Error; @@ -306,7 +306,7 @@ impl<'a> ser::SerializeTupleVariant for &'a mut Serializer { } } -impl<'a> ser::SerializeMap for &'a mut Serializer { +impl ser::SerializeMap for &mut Serializer { type Ok = (); type Error = Error; @@ -334,7 +334,7 @@ impl<'a> ser::SerializeMap for &'a mut Serializer { } } -impl<'a> ser::SerializeStruct for &'a mut Serializer { +impl ser::SerializeStruct for &mut Serializer { type Ok = (); type Error = Error; @@ -351,7 +351,7 @@ impl<'a> ser::SerializeStruct for &'a mut Serializer { } } -impl<'a> ser::SerializeStructVariant for &'a mut Serializer { +impl ser::SerializeStructVariant for &mut Serializer { type Ok = (); type Error = Error; diff --git a/core/storage/btree.rs b/core/storage/btree.rs index 77f459968..ed9070514 100644 --- a/core/storage/btree.rs +++ b/core/storage/btree.rs @@ -1378,10 +1378,10 @@ impl BTreeCursor { PageType::IndexLeaf => todo!(), }; cbrk -= size; - if cbrk < first_cell as u64 || pc + size > usable_space { + if cbrk < first_cell || pc + size > usable_space { todo!("corrupt"); } - assert!(cbrk + size <= usable_space && cbrk >= first_cell as u64); + assert!(cbrk + size <= usable_space && cbrk >= first_cell); // set new pointer write_buf[cell_idx..cell_idx + 2].copy_from_slice(&(cbrk as u16).to_be_bytes()); // copy payload @@ -1394,7 +1394,7 @@ impl BTreeCursor { // if( data[hdr+7]+cbrk-iCellFirst!=pPage->nFree ){ // return SQLITE_CORRUPT_PAGE(pPage); // } - assert!(cbrk >= first_cell as u64); + assert!(cbrk >= first_cell); let write_buf = page.as_ptr(); // set new first byte of cell content @@ -1437,7 +1437,7 @@ impl BTreeCursor { // #3. freeblocks (linked list of blocks of at least 4 bytes within the cell content area that are not in use due to e.g. deletions) let mut free_space_bytes = - page.unallocated_region_size() as usize + page.num_frag_free_bytes() as usize; + page.unallocated_region_size() + page.num_frag_free_bytes() as usize; // #3 is computed by iterating over the freeblocks linked list let mut cur_freeblock_ptr = page.first_freeblock() as usize; diff --git a/core/translate/emitter.rs b/core/translate/emitter.rs index c9b66d98e..e8d0a788c 100644 --- a/core/translate/emitter.rs +++ b/core/translate/emitter.rs @@ -663,7 +663,7 @@ fn open_loop( }); } - return Ok(()); + Ok(()) } SourceOperator::Scan { id, @@ -722,7 +722,7 @@ fn open_loop( } } - return Ok(()); + Ok(()) } SourceOperator::Search { id, @@ -905,10 +905,10 @@ fn open_loop( } } - return Ok(()); + Ok(()) } SourceOperator::Nothing => { - return Ok(()); + Ok(()) } } } @@ -978,14 +978,14 @@ fn inner_loop_emit( ); } // if we have neither, we emit a ResultRow. In that case, if we have a Limit, we handle that with DecrJumpZero. - return inner_loop_source_emit( + inner_loop_source_emit( program, &plan.result_columns, &plan.aggregates, metadata, InnerLoopEmitTarget::ResultRow { limit: plan.limit }, &plan.referenced_tables, - ); + ) } /// This is a helper function for inner_loop_emit, diff --git a/core/translate/insert.rs b/core/translate/insert.rs index 31b263195..50d0277f7 100644 --- a/core/translate/insert.rs +++ b/core/translate/insert.rs @@ -349,7 +349,7 @@ pub fn translate_insert( // Create new rowid if a) not provided by user or b) provided by user but is NULL program.emit_insn(Insn::NewRowid { cursor: cursor_id, - rowid_reg: rowid_reg, + rowid_reg, prev_largest_reg: 0, }); @@ -366,7 +366,7 @@ pub fn translate_insert( program.emit_insn_with_label_dependency( Insn::NotExists { cursor: cursor_id, - rowid_reg: rowid_reg, + rowid_reg, target_pc: make_record_label, }, make_record_label, diff --git a/core/translate/optimizer.rs b/core/translate/optimizer.rs index 1463c0402..217241f2a 100644 --- a/core/translate/optimizer.rs +++ b/core/translate/optimizer.rs @@ -666,7 +666,7 @@ impl Optimizable for ast::Expr { if id.0.eq_ignore_ascii_case("false") { return Ok(Some(ConstantPredicate::AlwaysFalse)); } - return Ok(None); + Ok(None) } Self::Literal(lit) => match lit { ast::Literal::Null => Ok(Some(ConstantPredicate::AlwaysFalse)), diff --git a/core/translate/planner.rs b/core/translate/planner.rs index 154f7e1c0..24b5a5d73 100644 --- a/core/translate/planner.rs +++ b/core/translate/planner.rs @@ -476,7 +476,7 @@ pub fn prepare_select_plan<'a>(schema: &Schema, select: ast::Select) -> Result

{ Blob(&'a Vec), } -impl<'a> Display for Value<'a> { +impl Display for Value<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Null => write!(f, "NULL"), diff --git a/core/vdbe/likeop.rs b/core/vdbe/likeop.rs index f4ef62f8d..2d01e6c0d 100644 --- a/core/vdbe/likeop.rs +++ b/core/vdbe/likeop.rs @@ -8,11 +8,9 @@ pub fn construct_like_escape_arg(escape_value: &OwnedValue) -> Result Ok(escape), - _ => { - return Result::Err(LimboError::Constraint( - "ESCAPE expression must be a single character".to_string(), - )) - } + _ => Result::Err(LimboError::Constraint( + "ESCAPE expression must be a single character".to_string(), + )), } } _ => { diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 2d731d133..8a8c60c97 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -2683,7 +2683,7 @@ pub fn exec_soundex(reg: &OwnedValue) -> OwnedValue { let word: String = s .value .chars() - .filter(|c| !c.is_digit(10)) + .filter(|c| !c.is_ascii_digit()) .collect::() .replace(" ", ""); if word.is_empty() { @@ -2725,7 +2725,7 @@ pub fn exec_soundex(reg: &OwnedValue) -> OwnedValue { // Remove adjacent same digits let tmp = tmp.chars().fold(String::new(), |mut acc, ch| { - if acc.chars().last() != Some(ch) { + if !acc.ends_with(ch) { acc.push(ch); } acc @@ -2741,7 +2741,7 @@ pub fn exec_soundex(reg: &OwnedValue) -> OwnedValue { // If the first symbol is a digit, replace it with the saved first letter if let Some(first_digit) = result.chars().next() { - if first_digit.is_digit(10) { + if first_digit.is_ascii_digit() { result.replace_range(0..1, &first_letter.to_string()); } } From 55916fdd9d6276887693090149c0e19cf6134995 Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Sun, 29 Dec 2024 18:57:02 +0200 Subject: [PATCH 07/18] Remove function parameter as it's only used in recursion --- core/translate/emitter.rs | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/core/translate/emitter.rs b/core/translate/emitter.rs index e8d0a788c..9d1c124da 100644 --- a/core/translate/emitter.rs +++ b/core/translate/emitter.rs @@ -241,12 +241,7 @@ fn emit_program_for_select( inner_loop_emit(&mut program, &mut plan, &mut metadata)?; // Clean up and close the main execution loop - close_loop( - &mut program, - &plan.source, - &mut metadata, - &plan.referenced_tables, - )?; + close_loop(&mut program, &plan.source, &mut metadata)?; if let Some(skip_loops_label) = skip_loops_label { program.resolve_label(skip_loops_label, program.offset()); @@ -338,12 +333,7 @@ fn emit_program_for_delete( emit_delete_insns(&mut program, &plan.source, &plan.limit, &metadata)?; // Clean up and close the main execution loop - close_loop( - &mut program, - &plan.source, - &mut metadata, - &plan.referenced_tables, - )?; + close_loop(&mut program, &plan.source, &mut metadata)?; if let Some(skip_loops_label) = skip_loops_label { program.resolve_label(skip_loops_label, program.offset()); @@ -907,9 +897,7 @@ fn open_loop( Ok(()) } - SourceOperator::Nothing => { - Ok(()) - } + SourceOperator::Nothing => Ok(()), } } @@ -1111,7 +1099,6 @@ fn close_loop( program: &mut ProgramBuilder, source: &SourceOperator, metadata: &mut Metadata, - referenced_tables: &[BTreeTableReference], ) -> Result<()> { match source { SourceOperator::Join { @@ -1121,7 +1108,7 @@ fn close_loop( outer, .. } => { - close_loop(program, right, metadata, referenced_tables)?; + close_loop(program, right, metadata)?; if *outer { let lj_meta = metadata.left_joins.get(id).unwrap(); @@ -1168,7 +1155,7 @@ fn close_loop( assert!(program.offset() == jump_offset); } - close_loop(program, left, metadata, referenced_tables)?; + close_loop(program, left, metadata)?; Ok(()) } From 10065bda35f3ccd9d9a88848742b9b322a7bf05c Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Sun, 29 Dec 2024 19:03:47 +0200 Subject: [PATCH 08/18] Don't use inaccurate pi values, make clippy happy --- core/types.rs | 9 +++++---- core/vdbe/mod.rs | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/types.rs b/core/types.rs index fc0f5301a..7b5814397 100644 --- a/core/types.rs +++ b/core/types.rs @@ -647,7 +647,8 @@ mod tests { #[test] fn test_serialize_float() { - let record = OwnedRecord::new(vec![OwnedValue::Float(3.14159)]); + #[warn(clippy::approx_constant)] + let record = OwnedRecord::new(vec![OwnedValue::Float(3.15555)]); let mut buf = Vec::new(); record.serialize(&mut buf); @@ -660,7 +661,7 @@ mod tests { // Check that the bytes after the header can be interpreted as the float let float_bytes = &buf[header_length..header_length + size_of::()]; let float = f64::from_be_bytes(float_bytes.try_into().unwrap()); - assert_eq!(float, 3.14159); + assert_eq!(float, 3.15555); // Check that buffer length is correct assert_eq!(buf.len(), header_length + size_of::()); } @@ -709,7 +710,7 @@ mod tests { let record = OwnedRecord::new(vec![ OwnedValue::Null, OwnedValue::Integer(42), - OwnedValue::Float(3.14), + OwnedValue::Float(3.15), OwnedValue::Text(LimboText::new(text.clone())), ]); let mut buf = Vec::new(); @@ -741,7 +742,7 @@ mod tests { let val_text = String::from_utf8(text_bytes.to_vec()).unwrap(); assert_eq!(val_int8, 42); - assert_eq!(val_float, 3.14); + assert_eq!(val_float, 3.15); assert_eq!(val_text, "test"); // Check that buffer length is correct diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 8a8c60c97..609b8ad86 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -4097,7 +4097,7 @@ mod tests { expected_len: 2, }, TestCase { - input: OwnedValue::Float(-3.14), + input: OwnedValue::Float(-3.15), expected_len: 1, }, TestCase { From db384c6828a21d449d1c7d984650c6730b41fad0 Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Sun, 29 Dec 2024 19:08:34 +0200 Subject: [PATCH 09/18] Simplify init of `delimiter_expr`s Clippy: https://rust-lang.github.io/rust-clippy/master/index.html#needless_late_init --- core/translate/expr.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/core/translate/expr.rs b/core/translate/expr.rs index ff17aa0b0..8b91a2969 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -2265,14 +2265,10 @@ pub fn translate_aggregation( let delimiter_reg = program.alloc_register(); let expr = &agg.args[0]; - let delimiter_expr: ast::Expr; - - match &agg.args[1] { - ast::Expr::Column { .. } => { - delimiter_expr = agg.args[1].clone(); - } + let delimiter_expr = match &agg.args[1] { + ast::Expr::Column { .. } => agg.args[1].clone(), ast::Expr::Literal(ast::Literal::String(s)) => { - delimiter_expr = ast::Expr::Literal(ast::Literal::String(s.to_string())); + ast::Expr::Literal(ast::Literal::String(s.to_string())) } _ => crate::bail_parse_error!("Incorrect delimiter parameter"), }; @@ -2448,14 +2444,10 @@ pub fn translate_aggregation_groupby( let expr_reg = program.alloc_register(); let delimiter_reg = program.alloc_register(); - let delimiter_expr: ast::Expr; - - match &agg.args[1] { - ast::Expr::Column { .. } => { - delimiter_expr = agg.args[1].clone(); - } + let delimiter_expr = match &agg.args[1] { + ast::Expr::Column { .. } => agg.args[1].clone(), ast::Expr::Literal(ast::Literal::String(s)) => { - delimiter_expr = ast::Expr::Literal(ast::Literal::String(s.to_string())); + ast::Expr::Literal(ast::Literal::String(s.to_string())) } _ => crate::bail_parse_error!("Incorrect delimiter parameter"), }; From d3fee3b33192bc572f8c33ea61cebcd684fb283e Mon Sep 17 00:00:00 2001 From: alpaylan Date: Sun, 29 Dec 2024 14:00:57 -0500 Subject: [PATCH 10/18] add empty line at the end of cargo.toml, add create counts to the interaction stats, turn the percentages into f64 --- simulator/Cargo.toml | 2 +- simulator/generation/mod.rs | 20 +++++++++++++++----- simulator/generation/plan.rs | 35 +++++++++++++++++++++-------------- simulator/main.rs | 18 +++++++++++------- simulator/runner/env.rs | 7 ++++--- 5 files changed, 52 insertions(+), 30 deletions(-) diff --git a/simulator/Cargo.toml b/simulator/Cargo.toml index 462ceaec7..31a54f1e6 100644 --- a/simulator/Cargo.toml +++ b/simulator/Cargo.toml @@ -22,4 +22,4 @@ log = "0.4.20" tempfile = "3.0.7" env_logger = "0.10.1" anarchist-readable-name-generator-lib = "0.1.2" -clap = { version = "4.5", features = ["derive"] } \ No newline at end of file +clap = { version = "4.5", features = ["derive"] } diff --git a/simulator/generation/mod.rs b/simulator/generation/mod.rs index 07a93492b..68f9c84fe 100644 --- a/simulator/generation/mod.rs +++ b/simulator/generation/mod.rs @@ -1,5 +1,10 @@ +use std::{ + iter::Sum, + ops::{Add, Sub, SubAssign}, +}; + use anarchist_readable_name_generator_lib::readable_name_custom; -use rand::Rng; +use rand::{distributions::uniform::SampleUniform, Rng}; pub mod plan; pub mod query; @@ -13,12 +18,17 @@ pub trait ArbitraryFrom { fn arbitrary_from(rng: &mut R, t: &T) -> Self; } -pub(crate) fn frequency<'a, T, R: rand::Rng>( - choices: Vec<(usize, Box T + 'a>)>, +pub(crate) fn frequency< + 'a, + T, + R: rand::Rng, + N: Sum + PartialOrd + Copy + Default + SampleUniform + SubAssign, +>( + choices: Vec<(N, Box T + 'a>)>, rng: &mut R, ) -> T { - let total = choices.iter().map(|(weight, _)| weight).sum::(); - let mut choice = rng.gen_range(0..total); + let total = choices.iter().map(|(weight, _)| *weight).sum::(); + let mut choice = rng.gen_range(N::default()..total); for (weight, f) in choices { if choice < weight { diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index 1857b4a00..759ff5e2b 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -9,7 +9,7 @@ use crate::{ query::{Create, Insert, Predicate, Query, Select}, table::Value, }, - SimConnection, SimulatorEnv, SimulatorOpts, + SimConnection, SimulatorEnv, }; use crate::generation::{frequency, Arbitrary, ArbitraryFrom}; @@ -45,14 +45,15 @@ pub(crate) struct InteractionStats { pub(crate) read_count: usize, pub(crate) write_count: usize, pub(crate) delete_count: usize, + pub(crate) create_count: usize, } impl Display for InteractionStats { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "Read: {}, Write: {}, Delete: {}", - self.read_count, self.write_count, self.delete_count + "Read: {}, Write: {}, Delete: {}, Create: {}", + self.read_count, self.write_count, self.delete_count, self.create_count ) } } @@ -135,6 +136,7 @@ impl InteractionPlan { let mut read = 0; let mut write = 0; let mut delete = 0; + let mut create = 0; for interaction in &self.plan { match interaction { @@ -142,7 +144,7 @@ impl InteractionPlan { Query::Select(_) => read += 1, Query::Insert(_) => write += 1, Query::Delete(_) => delete += 1, - Query::Create(_) => {} + Query::Create(_) => create += 1, }, Interaction::Assertion(_) => {} Interaction::Fault(_) => {} @@ -153,6 +155,7 @@ impl InteractionPlan { read_count: read, write_count: write, delete_count: delete, + create_count: create, } } } @@ -400,17 +403,21 @@ impl ArbitraryFrom<(&SimulatorEnv, InteractionStats)> for Interactions { rng: &mut R, (env, stats): &(&SimulatorEnv, InteractionStats), ) -> Self { - let remaining_read = - ((((env.opts.max_interactions * env.opts.read_percent) as f64) / 100.0) as usize) - .saturating_sub(stats.read_count); - let remaining_write = ((((env.opts.max_interactions * env.opts.write_percent) as f64) - / 100.0) as usize) - .saturating_sub(stats.write_count); + let remaining_read = ((env.opts.max_interactions as f64 * env.opts.read_percent / 100.0) + - (stats.read_count as f64)) + .max(0.0); + let remaining_write = ((env.opts.max_interactions as f64 * env.opts.write_percent / 100.0) + - (stats.write_count as f64)) + .max(0.0); + let remaining_create = ((env.opts.max_interactions as f64 * env.opts.create_percent + / 100.0) + - (stats.create_count as f64)) + .max(0.0); frequency( vec![ ( - usize::min(remaining_read, remaining_write), + f64::min(remaining_read, remaining_write), Box::new(|rng: &mut R| property_insert_select(rng, env)), ), ( @@ -422,12 +429,12 @@ impl ArbitraryFrom<(&SimulatorEnv, InteractionStats)> for Interactions { Box::new(|rng: &mut R| random_write(rng, env)), ), ( - remaining_write / 10, + remaining_create, Box::new(|rng: &mut R| create_table(rng, env)), ), - (1, Box::new(|rng: &mut R| random_fault(rng, env))), + (1.0, Box::new(|rng: &mut R| random_fault(rng, env))), ( - 1, + remaining_create / 2.0, Box::new(|rng: &mut R| property_double_create_failure(rng, env)), ), ], diff --git a/simulator/main.rs b/simulator/main.rs index bda55d909..8dc6d85bc 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -1,7 +1,7 @@ use clap::Parser; use generation::plan::{Interaction, InteractionPlan, ResultSet}; use generation::{pick_index, ArbitraryFrom}; -use limbo_core::{Connection, Database, Result, RowResult, IO}; +use limbo_core::{Database, Result}; use model::table::Value; use rand::prelude::*; use rand_chacha::ChaCha8Rng; @@ -11,7 +11,6 @@ use runner::io::SimulatorIO; use std::backtrace::Backtrace; use std::io::Write; use std::path::Path; -use std::rc::Rc; use std::sync::Arc; use tempfile::TempDir; @@ -137,14 +136,18 @@ fn run_simulation( ) -> Result<()> { let mut rng = ChaCha8Rng::seed_from_u64(seed); - let (read_percent, write_percent, delete_percent) = { - let mut remaining = 100; - let read_percent = rng.gen_range(0..=remaining); + let (create_percent, read_percent, write_percent, delete_percent) = { + let mut remaining = 100.0; + let read_percent = rng.gen_range(0.0..=remaining); remaining -= read_percent; - let write_percent = rng.gen_range(0..=remaining); + let write_percent = rng.gen_range(0.0..=remaining); remaining -= write_percent; let delete_percent = remaining; - (read_percent, write_percent, delete_percent) + + let create_percent = write_percent / 10.0; + let write_percent = write_percent - create_percent; + + (create_percent, read_percent, write_percent, delete_percent) }; if cli_opts.maximum_size < 1 { @@ -156,6 +159,7 @@ fn run_simulation( max_connections: 1, // TODO: for now let's use one connection as we didn't implement // correct transactions procesing max_tables: rng.gen_range(0..128), + create_percent, read_percent, write_percent, delete_percent, diff --git a/simulator/runner/env.rs b/simulator/runner/env.rs index 0624b94b4..53d99b2f0 100644 --- a/simulator/runner/env.rs +++ b/simulator/runner/env.rs @@ -30,9 +30,10 @@ pub(crate) struct SimulatorOpts { pub(crate) max_tables: usize, // this next options are the distribution of workload where read_percent + write_percent + // delete_percent == 100% - pub(crate) read_percent: usize, - pub(crate) write_percent: usize, - pub(crate) delete_percent: usize, + pub(crate) create_percent: f64, + pub(crate) read_percent: f64, + pub(crate) write_percent: f64, + pub(crate) delete_percent: f64, pub(crate) max_interactions: usize, pub(crate) page_size: usize, } From 4e77840ee531d14482978b5fc9e63f42e5bb53cd Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Sun, 29 Dec 2024 15:24:53 -0500 Subject: [PATCH 11/18] Setup io_uring with sqpoll enabled --- core/io/linux.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/io/linux.rs b/core/io/linux.rs index e765cc8ac..70de8ef79 100644 --- a/core/io/linux.rs +++ b/core/io/linux.rs @@ -11,6 +11,7 @@ use std::rc::Rc; use thiserror::Error; const MAX_IOVECS: usize = 128; +const SQPOLL_IDLE: u32 = 1000; #[derive(Debug, Error)] enum LinuxIOError { @@ -49,7 +50,13 @@ struct InnerLinuxIO { impl LinuxIO { pub fn new() -> Result { - let ring = io_uring::IoUring::new(MAX_IOVECS as u32)?; + let ring = match io_uring::IoUring::builder() + .setup_sqpoll(SQPOLL_IDLE) + .build(MAX_IOVECS as u32) + { + Ok(ring) => ring, + Err(_) => io_uring::IoUring::new(MAX_IOVECS as u32)?, + }; let inner = InnerLinuxIO { ring: WrappedIOUring { ring, From c01f2d4ac22bccf7d5691d424391b1f44c639abf Mon Sep 17 00:00:00 2001 From: alpaylan Date: Sun, 29 Dec 2024 16:09:49 -0500 Subject: [PATCH 12/18] fix formatting --- simulator/generation/plan.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index f393809bd..bb9efbe0f 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -379,7 +379,6 @@ fn property_double_create_failure(rng: &mut R, _env: &SimulatorEnv Interactions(vec![cq1, cq2, assertion]) } - fn create_table(rng: &mut R, _env: &SimulatorEnv) -> Interactions { let create_query = Interaction::Query(Query::Create(Create::arbitrary(rng))); Interactions(vec![create_query]) From 58f23983e1eefd896ae2c55c7a11e044047aaa0c Mon Sep 17 00:00:00 2001 From: alpaylan Date: Mon, 30 Dec 2024 00:36:43 -0500 Subject: [PATCH 13/18] minor changes, add maximum time bound to the simulator, fix bug in the table create shadowing --- simulator/generation/mod.rs | 7 ++----- simulator/generation/plan.rs | 6 ++++-- simulator/main.rs | 37 +++++++++++++++++++++++++++++++++--- simulator/runner/cli.rs | 16 +++++++++++++++- simulator/runner/env.rs | 1 + 5 files changed, 56 insertions(+), 11 deletions(-) diff --git a/simulator/generation/mod.rs b/simulator/generation/mod.rs index 68f9c84fe..8158b2d17 100644 --- a/simulator/generation/mod.rs +++ b/simulator/generation/mod.rs @@ -1,7 +1,4 @@ -use std::{ - iter::Sum, - ops::{Add, Sub, SubAssign}, -}; +use std::{iter::Sum, ops::SubAssign}; use anarchist_readable_name_generator_lib::readable_name_custom; use rand::{distributions::uniform::SampleUniform, Rng}; @@ -48,7 +45,7 @@ pub(crate) fn one_of<'a, T, R: rand::Rng>( choices[index](rng) } -pub(crate) fn pick<'a, T, R: rand::Rng>(choices: &'a Vec, rng: &mut R) -> &'a T { +pub(crate) fn pick<'a, T, R: rand::Rng>(choices: &'a [T], rng: &mut R) -> &'a T { let index = rng.gen_range(0..choices.len()); &choices[index] } diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index bb9efbe0f..dbf6dd7f6 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -101,7 +101,9 @@ impl Interactions { match interaction { Interaction::Query(query) => match query { Query::Create(create) => { - env.tables.push(create.table.clone()); + if !env.tables.iter().any(|t| t.name == create.table.name) { + env.tables.push(create.table.clone()); + } } Query::Insert(insert) => { let table = env @@ -175,7 +177,7 @@ impl ArbitraryFrom for InteractionPlan { rng: ChaCha8Rng::seed_from_u64(rng.next_u64()), }; - let num_interactions = rng.gen_range(0..env.opts.max_interactions); + let num_interactions = env.opts.max_interactions; // First create at least one table let create_query = Create::arbitrary(rng); diff --git a/simulator/main.rs b/simulator/main.rs index 992c5f3d5..52c33d5ec 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -121,6 +121,15 @@ fn main() { // Move the old database and plan file back std::fs::rename(&old_db_path, &db_path).unwrap(); std::fs::rename(&old_plan_path, &plan_path).unwrap(); + } else if let Ok(result) = result { + match result { + Ok(_) => { + log::info!("simulation completed successfully"); + } + Err(e) => { + log::error!("simulation failed: {:?}", e); + } + } } // Print the seed, the locations of the database and the plan file at the end again for easily accessing them. println!("database path: {:?}", db_path); @@ -150,12 +159,26 @@ fn run_simulation( (create_percent, read_percent, write_percent, delete_percent) }; + if cli_opts.minimum_size < 1 { + return Err(limbo_core::LimboError::InternalError( + "minimum size must be at least 1".to_string(), + )); + } + if cli_opts.maximum_size < 1 { - panic!("maximum size must be at least 1"); + return Err(limbo_core::LimboError::InternalError( + "maximum size must be at least 1".to_string(), + )); + } + + if cli_opts.maximum_size < cli_opts.minimum_size { + return Err(limbo_core::LimboError::InternalError( + "maximum size must be greater than or equal to minimum size".to_string(), + )); } let opts = SimulatorOpts { - ticks: rng.gen_range(1..=cli_opts.maximum_size), + ticks: rng.gen_range(cli_opts.minimum_size..=cli_opts.maximum_size), max_connections: 1, // TODO: for now let's use one connection as we didn't implement // correct transactions procesing max_tables: rng.gen_range(0..128), @@ -164,7 +187,8 @@ fn run_simulation( write_percent, delete_percent, page_size: 4096, // TODO: randomize this too - max_interactions: rng.gen_range(1..=cli_opts.maximum_size), + max_interactions: rng.gen_range(cli_opts.minimum_size..=cli_opts.maximum_size), + max_time_simulation: cli_opts.maximum_time, }; let io = Arc::new(SimulatorIO::new(seed, opts.page_size).unwrap()); @@ -212,12 +236,19 @@ fn run_simulation( } fn execute_plans(env: &mut SimulatorEnv, plans: &mut [InteractionPlan]) -> Result<()> { + let now = std::time::Instant::now(); // todo: add history here by recording which interaction was executed at which tick for _tick in 0..env.opts.ticks { // Pick the connection to interact with let connection_index = pick_index(env.connections.len(), &mut env.rng); // Execute the interaction for the selected connection execute_plan(env, connection_index, plans)?; + // Check if the maximum time for the simulation has been reached + if now.elapsed().as_secs() >= env.opts.max_time_simulation as u64 { + return Err(limbo_core::LimboError::InternalError( + "maximum time for simulation reached".into(), + )); + } } Ok(()) diff --git a/simulator/runner/cli.rs b/simulator/runner/cli.rs index f977937bb..8ad42c8b3 100644 --- a/simulator/runner/cli.rs +++ b/simulator/runner/cli.rs @@ -15,10 +15,24 @@ pub struct SimulatorCLI { )] pub doublecheck: bool, #[clap( - short, + short = 'n', long, help = "change the maximum size of the randomly generated sequence of interactions", default_value_t = 1024 )] pub maximum_size: usize, + #[clap( + short = 'k', + long, + help = "change the minimum size of the randomly generated sequence of interactions", + default_value_t = 1 + )] + pub minimum_size: usize, + #[clap( + short = 't', + long, + help = "change the maximum time of the simulation(in seconds)", + default_value_t = 60 * 60 // default to 1 hour + )] + pub maximum_time: usize, } diff --git a/simulator/runner/env.rs b/simulator/runner/env.rs index 53d99b2f0..7edad025f 100644 --- a/simulator/runner/env.rs +++ b/simulator/runner/env.rs @@ -36,4 +36,5 @@ pub(crate) struct SimulatorOpts { pub(crate) delete_percent: f64, pub(crate) max_interactions: usize, pub(crate) page_size: usize, + pub(crate) max_time_simulation: usize, } From d8ce88c057ce2fa9c76eb1839d18e8d734b027ec Mon Sep 17 00:00:00 2001 From: alpaylan Date: Mon, 30 Dec 2024 00:41:21 -0500 Subject: [PATCH 14/18] fix clippy warning --- simulator/generation/table.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/simulator/generation/table.rs b/simulator/generation/table.rs index 332aeb1f3..179c53436 100644 --- a/simulator/generation/table.rs +++ b/simulator/generation/table.rs @@ -41,11 +41,7 @@ impl Arbitrary for Column { impl Arbitrary for ColumnType { fn arbitrary(rng: &mut R) -> Self { - pick( - &vec![Self::Integer, Self::Float, Self::Text, Self::Blob], - rng, - ) - .to_owned() + pick(&[Self::Integer, Self::Float, Self::Text, Self::Blob], rng).to_owned() } } From cb69d8b0dd66f9e7e898ec1591bd10991947c688 Mon Sep 17 00:00:00 2001 From: JeanArhancet Date: Mon, 23 Dec 2024 17:05:21 +0100 Subject: [PATCH 15/18] feat(python): add in-memory mode --- bindings/python/build.rs | 1 - bindings/python/src/lib.rs | 86 ++++++++++++++----------- bindings/python/tests/hello.db | Bin 0 -> 12288 bytes bindings/python/tests/test_database.py | 14 ++++ 4 files changed, 64 insertions(+), 37 deletions(-) create mode 100644 bindings/python/tests/hello.db diff --git a/bindings/python/build.rs b/bindings/python/build.rs index 8f01c1a67..0475124bb 100644 --- a/bindings/python/build.rs +++ b/bindings/python/build.rs @@ -1,4 +1,3 @@ fn main() { pyo3_build_config::use_pyo3_cfgs(); - println!("cargo::rustc-check-cfg=cfg(allocator, values(\"default\", \"mimalloc\"))"); } diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index 1b3514032..9d1cba7b3 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -4,6 +4,7 @@ use limbo_core::IO; use pyo3::prelude::*; use pyo3::types::PyList; use pyo3::types::PyTuple; +use std::cell::RefCell; use std::rc::Rc; use std::sync::{Arc, Mutex}; @@ -78,7 +79,7 @@ pub struct Cursor { #[pyo3(get)] rowcount: i64, - smt: Option>>, + smt: Option>>, } // SAFETY: The limbo_core crate guarantees that `Cursor` is thread-safe. @@ -90,26 +91,33 @@ impl Cursor { #[pyo3(signature = (sql, parameters=None))] pub fn execute(&mut self, sql: &str, parameters: Option>) -> Result { let stmt_is_dml = stmt_is_dml(sql); + let stmt_is_ddl = stmt_is_ddl(sql); - let conn_lock = - self.conn.conn.lock().map_err(|_| { - PyErr::new::("Failed to acquire connection lock") - })?; - - let statement = conn_lock.prepare(sql).map_err(|e| { + let statement = self.conn.conn.prepare(sql).map_err(|e| { PyErr::new::(format!("Failed to prepare statement: {:?}", e)) })?; - self.smt = Some(Arc::new(Mutex::new(statement))); + let stmt = Rc::new(RefCell::new(statement)); - // TODO: use stmt_is_dml to set rowcount - if stmt_is_dml { - return Err(PyErr::new::( - "DML statements (INSERT/UPDATE/DELETE) are not fully supported in this version", - ) - .into()); + // For DDL and DML statements, + // we need to execute the statement immediately + if stmt_is_ddl || stmt_is_dml { + loop { + match stmt.borrow_mut().step().map_err(|e| { + PyErr::new::(format!("Step error: {:?}", e)) + })? { + limbo_core::RowResult::IO => { + self.conn.io.run_once().map_err(|e| { + PyErr::new::(format!("IO error: {:?}", e)) + })?; + } + _ => break, + } + } } + self.smt = Some(stmt); + Ok(Cursor { smt: self.smt.clone(), conn: self.conn.clone(), @@ -121,11 +129,8 @@ impl Cursor { pub fn fetchone(&mut self, py: Python) -> Result> { if let Some(smt) = &self.smt { - let mut smt_lock = smt.lock().map_err(|_| { - PyErr::new::("Failed to acquire statement lock") - })?; loop { - match smt_lock.step().map_err(|e| { + match smt.borrow_mut().step().map_err(|e| { PyErr::new::(format!("Step error: {:?}", e)) })? { limbo_core::StepResult::Row(row) => { @@ -157,14 +162,9 @@ impl Cursor { pub fn fetchall(&mut self, py: Python) -> Result> { let mut results = Vec::new(); - if let Some(smt) = &self.smt { - let mut smt_lock = smt.lock().map_err(|_| { - PyErr::new::("Failed to acquire statement lock") - })?; - loop { - match smt_lock.step().map_err(|e| { + match smt.borrow_mut().step().map_err(|e| { PyErr::new::(format!("Step error: {:?}", e)) })? { limbo_core::StepResult::Row(row) => { @@ -221,11 +221,17 @@ fn stmt_is_dml(sql: &str) -> bool { sql.starts_with("INSERT") || sql.starts_with("UPDATE") || sql.starts_with("DELETE") } +fn stmt_is_ddl(sql: &str) -> bool { + let sql = sql.trim(); + let sql = sql.to_uppercase(); + sql.starts_with("CREATE") || sql.starts_with("ALTER") || sql.starts_with("DROP") +} + #[pyclass] #[derive(Clone)] pub struct Connection { - conn: Arc>>, - io: Arc, + conn: Rc, + io: Arc, } // SAFETY: The limbo_core crate guarantees that `Connection` is thread-safe. @@ -263,16 +269,24 @@ impl Connection { #[allow(clippy::arc_with_non_send_sync)] #[pyfunction] pub fn connect(path: &str) -> Result { - let io = Arc::new(limbo_core::PlatformIO::new().map_err(|e| { - PyErr::new::(format!("IO initialization failed: {:?}", e)) - })?); - let db = limbo_core::Database::open_file(io.clone(), path) - .map_err(|e| PyErr::new::(format!("Failed to open database: {:?}", e)))?; - let conn: Rc = db.connect(); - Ok(Connection { - conn: Arc::new(Mutex::new(conn)), - io, - }) + match path { + ":memory:" => { + let io: Arc = Arc::new(limbo_core::MemoryIO::new()?); + let db = limbo_core::Database::open_file(io.clone(), path).map_err(|e| { + PyErr::new::(format!("Failed to open database: {:?}", e)) + })?; + let conn: Rc = db.connect(); + Ok(Connection { conn, io }) + } + path => { + let io: Arc = Arc::new(limbo_core::PlatformIO::new()?); + let db = limbo_core::Database::open_file(io.clone(), path).map_err(|e| { + PyErr::new::(format!("Failed to open database: {:?}", e)) + })?; + let conn: Rc = db.connect(); + Ok(Connection { conn, io }) + } + } } fn row_to_py(py: Python, row: &limbo_core::Row) -> PyObject { diff --git a/bindings/python/tests/hello.db b/bindings/python/tests/hello.db new file mode 100644 index 0000000000000000000000000000000000000000..55538d0cffb2128facdeb01a7a07ec836d9c8ae1 GIT binary patch literal 12288 zcmeI#ze@u#6u|N1lp=+4-Lk&Xf(YU)cuI!iDSBpTr{km%4*COoSJA0|fPb6+kE54B z=kCY%L0)(X3FMRMy}smi>*am3T~)1ItFh8r-HIrsbT>}Aafnc#9*S-b^|8?E^7Chu z{;4G0t8}0K9y`E30tg_000IagfB*srAb-X|51Q0*~0R#|0 O009ILKmY**qQF0p!Z6AJ literal 0 HcmV?d00001 diff --git a/bindings/python/tests/test_database.py b/bindings/python/tests/test_database.py index 601c67135..a09eea08e 100644 --- a/bindings/python/tests/test_database.py +++ b/bindings/python/tests/test_database.py @@ -27,6 +27,20 @@ def test_fetchall_select_user_ids(provider): assert user_ids == [(1,), (2,)] +@pytest.mark.parametrize("provider", ["sqlite3", "limbo"]) +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("INSERT INTO users VALUES (1, 'alice')") + + cursor.execute("SELECT * FROM users") + + alice = cursor.fetchone() + assert alice + assert alice == (1, "alice") + + @pytest.mark.parametrize("provider", ["sqlite3", "limbo"]) def test_fetchone_select_all_users(provider): conn = connect(provider, "tests/database.db") From 2a0402ce7f0510b645bcad99c09120028d1977aa Mon Sep 17 00:00:00 2001 From: JeanArhancet Date: Sat, 28 Dec 2024 17:17:57 +0100 Subject: [PATCH 16/18] fix: python lint --- bindings/python/src/lib.rs | 2 +- bindings/python/tests/hello.db | Bin 12288 -> 0 bytes bindings/python/tests/test_database.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 bindings/python/tests/hello.db diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index 9d1cba7b3..7c2658a9a 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -106,7 +106,7 @@ impl Cursor { match stmt.borrow_mut().step().map_err(|e| { PyErr::new::(format!("Step error: {:?}", e)) })? { - limbo_core::RowResult::IO => { + limbo_core::StepResult::IO => { self.conn.io.run_once().map_err(|e| { PyErr::new::(format!("IO error: {:?}", e)) })?; diff --git a/bindings/python/tests/hello.db b/bindings/python/tests/hello.db deleted file mode 100644 index 55538d0cffb2128facdeb01a7a07ec836d9c8ae1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI#ze@u#6u|N1lp=+4-Lk&Xf(YU)cuI!iDSBpTr{km%4*COoSJA0|fPb6+kE54B z=kCY%L0)(X3FMRMy}smi>*am3T~)1ItFh8r-HIrsbT>}Aafnc#9*S-b^|8?E^7Chu z{;4G0t8}0K9y`E30tg_000IagfB*srAb-X|51Q0*~0R#|0 O009ILKmY**qQF0p!Z6AJ diff --git a/bindings/python/tests/test_database.py b/bindings/python/tests/test_database.py index a09eea08e..63241f19a 100644 --- a/bindings/python/tests/test_database.py +++ b/bindings/python/tests/test_database.py @@ -33,7 +33,7 @@ def test_in_memory_fetchone_select_all_users(provider): cursor = conn.cursor() cursor.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT)") cursor.execute("INSERT INTO users VALUES (1, 'alice')") - + cursor.execute("SELECT * FROM users") alice = cursor.fetchone() From 9a70dc8f78372de143a5919af3d3b21090be19dc Mon Sep 17 00:00:00 2001 From: JeanArhancet Date: Mon, 30 Dec 2024 10:22:36 +0100 Subject: [PATCH 17/18] fix: clippy error --- bindings/python/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index 7c2658a9a..764fcfcf5 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -1,12 +1,11 @@ use anyhow::Result; use errors::*; -use limbo_core::IO; use pyo3::prelude::*; use pyo3::types::PyList; use pyo3::types::PyTuple; use std::cell::RefCell; use std::rc::Rc; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; mod errors; From 3ac3fdf0a25cc0fe6426408399b76cf3cb160d83 Mon Sep 17 00:00:00 2001 From: psvri Date: Mon, 30 Dec 2024 16:48:08 +0530 Subject: [PATCH 18/18] Fix glob --- core/vdbe/likeop.rs | 132 ++++++++++++++++++++++++++++++++++++++++++++ core/vdbe/mod.rs | 27 +-------- testing/glob.test | 72 ++++++++++++++++++++++++ 3 files changed, 205 insertions(+), 26 deletions(-) diff --git a/core/vdbe/likeop.rs b/core/vdbe/likeop.rs index f4ef62f8d..d7885caec 100644 --- a/core/vdbe/likeop.rs +++ b/core/vdbe/likeop.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use regex::{Regex, RegexBuilder}; use crate::{types::OwnedValue, LimboError}; @@ -63,6 +65,124 @@ fn construct_like_regex_with_escape(pattern: &str, escape: char) -> Regex { .unwrap() } +// Implements GLOB pattern matching. Caches the constructed regex if a cache is provided +pub fn exec_glob( + regex_cache: Option<&mut HashMap>, + pattern: &str, + text: &str, +) -> bool { + if let Some(cache) = regex_cache { + match cache.get(pattern) { + Some(re) => re.is_match(text), + None => match construct_glob_regex(pattern) { + Ok(re) => { + let res = re.is_match(text); + cache.insert(pattern.to_string(), re); + res + } + Err(_) => false, + }, + } + } else { + construct_glob_regex(pattern) + .map(|re| re.is_match(text)) + .unwrap_or(false) + } +} + +fn push_char_to_regex_pattern(c: char, regex_pattern: &mut String) { + if regex_syntax::is_meta_character(c) { + regex_pattern.push('\\'); + } + regex_pattern.push(c); +} + +fn construct_glob_regex(pattern: &str) -> Result { + let mut regex_pattern = String::with_capacity(pattern.len() * 2); + + regex_pattern.push('^'); + + let mut chars = pattern.chars(); + let mut bracket_closed = true; + + while let Some(ch) = chars.next() { + match ch { + '[' => { + bracket_closed = false; + regex_pattern.push('['); + if let Some(next_ch) = chars.next() { + match next_ch { + ']' => { + // The string enclosed by the brackets cannot be empty; + // therefore ']' can be allowed between the brackets, + // provided that it is the first character. + // so this means + // - `[]]` will be translated to `[\]]` + // - `[[]` will be translated to `[\[]` + regex_pattern.push_str("\\]"); + } + '^' => { + // For the most cases we can pass `^` directly to regex + // but in certain cases like [^][a] , `[^]` will make regex crate + // throw unenclosed character class. So this means + // - `[^][a]` will be translated to `[^\]a]` + regex_pattern.push('^'); + if let Some(next_ch_2) = chars.next() { + match next_ch_2 { + ']' => { + regex_pattern.push('\\'); + regex_pattern.push(']'); + } + c => { + push_char_to_regex_pattern(c, &mut regex_pattern); + } + } + } + } + c => { + push_char_to_regex_pattern(c, &mut regex_pattern); + } + } + }; + + while let Some(next_ch) = chars.next() { + match next_ch { + ']' => { + bracket_closed = true; + regex_pattern.push(']'); + break; + } + '-' => { + regex_pattern.push('-'); + } + c => { + push_char_to_regex_pattern(c, &mut regex_pattern); + } + } + } + } + '?' => { + regex_pattern.push('.'); + } + '*' => { + regex_pattern.push_str(".*"); + } + c => { + push_char_to_regex_pattern(c, &mut regex_pattern); + } + } + } + regex_pattern.push('$'); + + if bracket_closed { + Ok(Regex::new(®ex_pattern).unwrap()) + } else { + Result::Err(LimboError::Constraint( + "blob pattern is not closed".to_string(), + )) + } +} + #[cfg(test)] mod test { use super::*; @@ -84,4 +204,16 @@ mod test { assert!(!exec_like_with_escape("abcXX", "abc", 'X')); assert!(!exec_like_with_escape("abcXX", "abcXX", 'X')); } + + #[test] + fn test_glob_no_cache() { + assert!(exec_glob(None, r#"?*/abc/?*"#, r#"x//a/ab/abc/y"#)); + assert!(exec_glob(None, r#"a[1^]"#, r#"a1"#)); + assert!(exec_glob(None, r#"a[1^]*"#, r#"a^"#)); + assert!(!exec_glob(None, r#"a[a*"#, r#"a["#)); + assert!(!exec_glob(None, r#"a[a"#, r#"a[a"#)); + assert!(exec_glob(None, r#"a[[]"#, r#"a["#)); + assert!(exec_glob(None, r#"abc[^][*?]efg"#, r#"abcdefg"#)); + assert!(!exec_glob(None, r#"abc[^][*?]efg"#, r#"abc]efg"#)); + } } diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 2d731d133..049fb0afd 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -42,7 +42,7 @@ use crate::vdbe::insn::Insn; use crate::{function::JsonFunc, json::get_json, json::json_array, json::json_array_length}; use crate::{Connection, Result, Rows, TransactionState, DATABASE_VERSION}; use datetime::{exec_date, exec_time, exec_unixepoch}; -use likeop::{construct_like_escape_arg, exec_like_with_escape}; +use likeop::{construct_like_escape_arg, exec_glob, exec_like_with_escape}; use rand::distributions::{Distribution, Uniform}; use rand::{thread_rng, Rng}; use regex::{Regex, RegexBuilder}; @@ -2880,31 +2880,6 @@ fn exec_like(regex_cache: Option<&mut HashMap>, pattern: &str, te } } -fn construct_glob_regex(pattern: &str) -> Regex { - let mut regex_pattern = String::from("^"); - regex_pattern.push_str(&pattern.replace('*', ".*").replace("?", ".")); - regex_pattern.push('$'); - Regex::new(®ex_pattern).unwrap() -} - -// Implements GLOB pattern matching. Caches the constructed regex if a cache is provided -fn exec_glob(regex_cache: Option<&mut HashMap>, pattern: &str, text: &str) -> bool { - if let Some(cache) = regex_cache { - match cache.get(pattern) { - Some(re) => re.is_match(text), - None => { - let re = construct_glob_regex(pattern); - let res = re.is_match(text); - cache.insert(pattern.to_string(), re); - res - } - } - } else { - let re = construct_glob_regex(pattern); - re.is_match(text) - } -} - fn exec_min(regs: Vec<&OwnedValue>) -> OwnedValue { regs.iter() .min() diff --git a/testing/glob.test b/testing/glob.test index 249ea8151..730fd20d6 100644 --- a/testing/glob.test +++ b/testing/glob.test @@ -68,3 +68,75 @@ Robert|Roberts} do_execsql_test where-glob-impossible { select * from products where 'foobar' glob 'fooba'; } {} + +foreach {testnum pattern text ans} { + 1 abcdefg abcdefg 1 + 2 abcdefG abcdefg 0 + 3 abcdef abcdefg 0 + 4 abcdefgh abcdefg 0 + 5 abcdef? abcdefg 1 + 6 abcdef? abcdef 0 + 7 abcdef? abcdefgh 0 + 8 abcdefg abcdef? 0 + 9 abcdef? abcdef? 1 + 10 abc/def abc/def 1 + 11 abc//def abc/def 0 + 12 */abc/* x/abc/y 1 + 13 */abc/* /abc/ 1 + 16 */abc/* x///a/ab/abc 0 + 17 */abc/* x//a/ab/abc/ 1 + 16 */abc/* x///a/ab/abc 0 + 17 */abc/* x//a/ab/abc/ 1 + 18 **/abc/** x//a/ab/abc/ 1 + 19 *?/abc/*? x//a/ab/abc/y 1 + 20 ?*/abc/?* x//a/ab/abc/y 1 + 21 {abc[cde]efg} abcbefg 0 + 22 {abc[cde]efg} abccefg 1 + 23 {abc[cde]efg} abcdefg 1 + 24 {abc[cde]efg} abceefg 1 + 25 {abc[cde]efg} abcfefg 0 + 26 {abc[^cde]efg} abcbefg 1 + 27 {abc[^cde]efg} abccefg 0 + 28 {abc[^cde]efg} abcdefg 0 + 29 {abc[^cde]efg} abceefg 0 + 30 {abc[^cde]efg} abcfefg 1 + 31 {abc[c-e]efg} abcbefg 0 + 32 {abc[c-e]efg} abccefg 1 + 33 {abc[c-e]efg} abcdefg 1 + 34 {abc[c-e]efg} abceefg 1 + 35 {abc[c-e]efg} abcfefg 0 + 36 {abc[^c-e]efg} abcbefg 1 + 37 {abc[^c-e]efg} abccefg 0 + 38 {abc[^c-e]efg} abcdefg 0 + 39 {abc[^c-e]efg} abceefg 0 + 40 {abc[^c-e]efg} abcfefg 1 + 41 {abc[c-e]efg} abc-efg 0 + 42 {abc[-ce]efg} abc-efg 1 + 43 {abc[ce-]efg} abc-efg 1 + 44 {abc[][*?]efg} {abc]efg} 1 + 45 {abc[][*?]efg} {abc*efg} 1 + 46 {abc[][*?]efg} {abc?efg} 1 + 47 {abc[][*?]efg} {abc[efg} 1 + 48 {abc[^][*?]efg} {abc]efg} 0 + 49 {abc[^][*?]efg} {abc*efg} 0 + 50 {abc[^][*?]efg} {abc?efg} 0 + 51 {abc[^][*?]efg} {abc[efg} 0 + 52 {abc[^][*?]efg} {abcdefg} 1 + 53 {*[xyz]efg} {abcxefg} 1 + 54 {*[xyz]efg} {abcwefg} 0 + 55 {[-c]} {c} 1 + 56 {[-c]} {-} 1 + 57 {[-c]} {x} 0 +} { + do_execsql_test glob-$testnum.1 "SELECT glob ( '$pattern' , '$text' )" $::ans +} + + +foreach {testnum pattern text ans} { + 1 {abc[} {abc[} 0 + 2 {abc[} {abc} 0 + 3 {a]b} {a]b} 1 + 4 {a]b} {a[b} 0 +} { + do_execsql_test glob-unenclosed-$testnum.1 "SELECT glob ( '$pattern' , '$text' )" $::ans +}