From b3380bc398776d36c78d572be76cea1994f12086 Mon Sep 17 00:00:00 2001 From: Nikita Sivukhin Date: Wed, 12 Nov 2025 02:30:15 +0400 Subject: [PATCH 1/2] treat parameters as "constant" within a query --- core/translate/optimizer/mod.rs | 6 +-- .../query_processing/test_read_path.rs | 44 +++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/core/translate/optimizer/mod.rs b/core/translate/optimizer/mod.rs index 2b79c1463..ecf6afca3 100644 --- a/core/translate/optimizer/mod.rs +++ b/core/translate/optimizer/mod.rs @@ -1071,7 +1071,7 @@ impl Optimizable for ast::Expr { Expr::Register(..) => false, // Register values can be null } } - /// Returns true if the expression is a constant i.e. does not depend on variables or columns etc. + /// Returns true if the expression is a constant i.e. does not depend on columns and can be evaluated only once during the execution fn is_constant(&self, resolver: &Resolver<'_>) -> bool { match self { Expr::SubqueryResult { .. } => false, @@ -1142,8 +1142,8 @@ impl Optimizable for ast::Expr { Expr::Raise(_, expr) => expr.as_ref().is_none_or(|expr| expr.is_constant(resolver)), Expr::Subquery(_) => false, Expr::Unary(_, expr) => expr.is_constant(resolver), - Expr::Variable(_) => false, - Expr::Register(_) => false, // Register values are not constants + Expr::Variable(_) => true, + Expr::Register(_) => true, } } /// Returns true if the expression is a constant expression that, when evaluated as a condition, is always true or false diff --git a/tests/integration/query_processing/test_read_path.rs b/tests/integration/query_processing/test_read_path.rs index 69c9dfe39..523597ae1 100644 --- a/tests/integration/query_processing/test_read_path.rs +++ b/tests/integration/query_processing/test_read_path.rs @@ -1018,3 +1018,47 @@ fn test_many_columns() { ]] ); } + +#[test] +fn test_eval_param_only_once() { + let tmp_db = TempDatabase::new("test_eval_param_only_once"); + let conn = tmp_db.connect_limbo(); + conn.execute("CREATE TABLE t(x)").unwrap(); + conn.execute("INSERT INTO t SELECT value FROM generate_series(1, 10000)") + .unwrap(); + let mut stmt = conn + .query("SELECT COUNT(*) FROM t WHERE LENGTH(zeroblob(?)) = ?") + .unwrap() + .unwrap(); + stmt.bind_at( + 1.try_into().unwrap(), + turso_core::Value::Integer(100_000_000), + ); + stmt.bind_at( + 2.try_into().unwrap(), + turso_core::Value::Integer(100_000_000), + ); + let start_time = std::time::Instant::now(); + loop { + match stmt.step().unwrap() { + StepResult::IO => { + stmt.run_once().unwrap(); + } + StepResult::Done => break, + StepResult::Row => { + let values = stmt + .row() + .unwrap() + .get_values() + .cloned() + .collect::>(); + assert_eq!(values, vec![turso_core::Value::Integer(10000)]); + } + _ => unreachable!(), + } + } + let end_time = std::time::Instant::now(); + let elapsed = end_time.duration_since(start_time); + // the test will allocate 10^8 * 10^4 bytes in case if parameter will be evaluated for every row + assert!(elapsed < std::time::Duration::from_millis(100)); +} From e1f77d8776443a32d8fe9edbe916925076e66110 Mon Sep 17 00:00:00 2001 From: Nikita Sivukhin Date: Wed, 12 Nov 2025 10:51:51 +0400 Subject: [PATCH 2/2] do not treat registers as constant --- core/translate/optimizer/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/translate/optimizer/mod.rs b/core/translate/optimizer/mod.rs index ecf6afca3..cc706b898 100644 --- a/core/translate/optimizer/mod.rs +++ b/core/translate/optimizer/mod.rs @@ -1143,7 +1143,7 @@ impl Optimizable for ast::Expr { Expr::Subquery(_) => false, Expr::Unary(_, expr) => expr.is_constant(resolver), Expr::Variable(_) => true, - Expr::Register(_) => true, + Expr::Register(_) => false, } } /// Returns true if the expression is a constant expression that, when evaluated as a condition, is always true or false