Summary
Verified empirically (see #181/#182/#183 investigation) that ClickHouse SQL now() does not map to QueryExpr::EvalTime today — nothing in frontend-sql recognizes it. It falls into the generic, semantically-opaque catch-all at crates/frontend-sql/src/sql/expr.rs:176:
Expr::ScalarFunction(sf) => Ok(L2Expr::FunctionCall { name: sf.func.name().to_string(), args: args? }),
So WHERE time < NOW() lowers to Scan.predicates: [Compare { .., right: FunctionCall { name: "now", args: [] } }] — correct at runtime (DataFusion still evaluates now() properly), but the IR has no semantic tag that this predicate's truth value is evaluation-time-dependent, the same information EvalTime exists to carry for PromQL's time().
Why this isn't a one-line fix
EvalTime is a relational QueryExpr node (a leaf, like Scan) — that's what lets PromQL embed it directly, since PromQL's IR has no relational/scalar split: time() alone is the whole query (QueryExpr::EvalTime), and time() - 3600 is BinaryOp { lhs: EvalTime, rhs: Scalar(3600.0) } because BinaryOp operands are themselves QueryExpr.
SQL's IR is split: relational QueryExpr (Scan/Filter/Project/...) wraps a separate scalar-expression sub-language (L2Expr/L3Expr) for anything inside a predicate or projection list. NOW() almost never appears as an entire SQL query on its own — it appears embedded in that scalar layer (WHERE time < NOW(), SELECT ts, NOW() - ts AS age FROM ...). You can't drop a QueryExpr node into an L3Expr position; they're different types. So two genuinely different cases:
SELECT NOW() (bare, evaluation time is the whole query) — maps cleanly to top-level QueryExpr::EvalTime, same shape as PromQL's bare time(). (Currently this specific case doesn't even lower today — separately broken on an unrelated EmptyRelation plan-node gap, a FROM-less SELECT.)
NOW() embedded in a predicate/projection (the common case) — has no home in the current L3Expr vocabulary. Making this evaluation-time-aware would need a new L3Expr/L2Expr variant at the scalar layer (e.g. L3Expr::EvalTime), not a match arm added to the relational EvalTime node.
Summary
Verified empirically (see #181/#182/#183 investigation) that ClickHouse SQL
now()does not map toQueryExpr::EvalTimetoday — nothing infrontend-sqlrecognizes it. It falls into the generic, semantically-opaque catch-all atcrates/frontend-sql/src/sql/expr.rs:176:So
WHERE time < NOW()lowers toScan.predicates: [Compare { .., right: FunctionCall { name: "now", args: [] } }]— correct at runtime (DataFusion still evaluatesnow()properly), but the IR has no semantic tag that this predicate's truth value is evaluation-time-dependent, the same informationEvalTimeexists to carry for PromQL'stime().Why this isn't a one-line fix
EvalTimeis a relationalQueryExprnode (a leaf, likeScan) — that's what lets PromQL embed it directly, since PromQL's IR has no relational/scalar split:time()alone is the whole query (QueryExpr::EvalTime), andtime() - 3600isBinaryOp { lhs: EvalTime, rhs: Scalar(3600.0) }becauseBinaryOpoperands are themselvesQueryExpr.SQL's IR is split: relational
QueryExpr(Scan/Filter/Project/...) wraps a separate scalar-expression sub-language (L2Expr/L3Expr) for anything inside a predicate or projection list.NOW()almost never appears as an entire SQL query on its own — it appears embedded in that scalar layer (WHERE time < NOW(),SELECT ts, NOW() - ts AS age FROM ...). You can't drop aQueryExprnode into anL3Exprposition; they're different types. So two genuinely different cases:SELECT NOW()(bare, evaluation time is the whole query) — maps cleanly to top-levelQueryExpr::EvalTime, same shape as PromQL's baretime(). (Currently this specific case doesn't even lower today — separately broken on an unrelatedEmptyRelationplan-node gap, a FROM-lessSELECT.)NOW()embedded in a predicate/projection (the common case) — has no home in the currentL3Exprvocabulary. Making this evaluation-time-aware would need a newL3Expr/L2Exprvariant at the scalar layer (e.g.L3Expr::EvalTime), not a match arm added to the relationalEvalTimenode.