diff --git a/core/translate/optimizer/mod.rs b/core/translate/optimizer/mod.rs index 2b79c1463..cc706b898 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(_) => false, } } /// 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)); +}