vibesql_executor/evaluator/schema_context.rs
1//! Schema-expression evaluation context
2//!
3//! SQLite rejects non-deterministic uses of the date/time functions (a call
4//! that resolves its time-value to the current time — `'now'` or a
5//! zero-argument default like `date()` — or that applies a `'localtime'` /
6//! `'utc'` modifier) when the call is evaluated inside a schema-attached
7//! expression: a CHECK constraint, a generated column, or an index expression
8//! (including a partial index's WHERE predicate).
9//!
10//! Crucially this is an EVALUATION-time check, not a DDL-time one:
11//! `CREATE TABLE t(a CHECK(a < julianday('now')))` succeeds, and the error
12//! fires when the expression is first evaluated (INSERT/UPDATE/CREATE INDEX
13//! build). The trigger can come from row data (`date(x)` with `x = 'now'`),
14//! which no static schema scan can detect.
15//!
16//! `SchemaExprContext` is carried by `ExpressionEvaluator` and threaded into
17//! `eval_scalar_function`, where the date/time functions consult it.
18//!
19//! SQLite reference: date.c `setDateTimeToCurrent`/`toLocaltime` calling
20//! `sqlite3NotPureFunc` for expressions compiled with `OP_PureFunc`
21//! (`NC_IsCheck` / `NC_GenCol` / `NC_IdxExpr` / `NC_PartIdx`).
22
23use crate::errors::ExecutorError;
24
25/// The schema-attached expression context an expression is being evaluated
26/// in, if any. `None` (the default) means an ordinary query context where
27/// non-deterministic date/time functions are allowed.
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
29pub enum SchemaExprContext {
30 /// Ordinary evaluation (SELECT, VALUES, WHERE, ...): no restriction
31 #[default]
32 None,
33 /// A CHECK constraint expression
34 CheckConstraint,
35 /// A generated (computed) column expression
36 GeneratedColumn,
37 /// An index expression or a partial index's WHERE predicate
38 Index,
39}
40
41impl SchemaExprContext {
42 /// SQLite's human-readable name for the context, used in the
43 /// "non-deterministic use of f() in {ctx}" error message.
44 /// Returns `None` for the unrestricted context.
45 pub fn description(self) -> Option<&'static str> {
46 match self {
47 SchemaExprContext::None => None,
48 SchemaExprContext::CheckConstraint => Some("a CHECK constraint"),
49 SchemaExprContext::GeneratedColumn => Some("a generated column"),
50 // SQLite reports partial-index WHERE clauses as "an index" too
51 SchemaExprContext::Index => Some("an index"),
52 }
53 }
54
55 /// Build the SQLite-compatible rejection error for a non-deterministic
56 /// use of `func_name` in this context, or `None` when evaluation is
57 /// unrestricted. `func_name` may be uppercase (internal convention);
58 /// SQLite prints the lowercase function name.
59 pub fn non_deterministic_error(self, func_name: &str) -> Option<ExecutorError> {
60 self.description().map(|ctx| {
61 ExecutorError::SqliteCompatError(format!(
62 "non-deterministic use of {}() in {}",
63 func_name.to_ascii_lowercase(),
64 ctx
65 ))
66 })
67 }
68}