{"record":{"id":"7369701dfc5db916","repo":"risingwavelabs/risingwave","slug":"conditions-containing-now-must-be-in-the-form-of","errorCode":null,"errorMessage":"Conditions containing now must be in the form of `input_expr cmp now_expr` or `now_expr cmp input_expr`, where `input_expr` references a column and contains no `now()`, and `now_expr` is a non-decreasing expression contains `now()`.","messagePattern":"Conditions containing now must be in the form of `input_expr cmp now_expr` or `now_expr cmp input_expr`, where `input_expr` references a column and contains no `now\\(\\)`, and `now_expr` is a non-decreasing expression contains `now\\(\\)`\\.","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/frontend/src/optimizer/plan_node/logical_filter.rs","lineNumber":60,"sourceCode":"pub struct LogicalFilter {\n    pub base: PlanBase<Logical>,\n    core: generic::Filter<PlanRef>,\n}\n\nimpl LogicalFilter {\n    pub fn new(input: PlanRef, predicate: Condition) -> Self {\n        let _ctx = input.ctx();\n        for cond in &predicate.conjunctions {\n            assert_input_ref!(cond, input.schema().fields().len());\n        }\n        let core = generic::Filter { predicate, input };\n        let base = PlanBase::new_logical_with_core(&core);\n        LogicalFilter { base, core }\n    }\n\n    pub fn check_stream_predicate(predicate: &Condition) -> Result<()> {\n        if predicate.conjunctions.iter().any(|cond| cond.has_now()) {\n            bail!(\n                \"Conditions containing now must be in the form of `input_expr cmp now_expr` or \\\n                `now_expr cmp input_expr`, where `input_expr` references a column and contains \\\n                no `now()`, and `now_expr` is a non-decreasing expression contains `now()`.\"\n            );\n        }\n        Ok(())\n    }\n\n    /// Create a `LogicalFilter` unless the predicate is always true\n    pub fn create(input: PlanRef, predicate: Condition) -> PlanRef {\n        if predicate.always_true() {\n            input\n        } else {\n            LogicalFilter::new(input, predicate).into()\n        }\n    }\n\n    /// Create a `LogicalFilter` to filter out rows where all keys are null.","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/frontend/src/optimizer/plan_node/logical_filter.rs#L42-L78","documentation":"`check_stream_predicate` validates that any predicate containing `now()` is streamable. Only the strict forms `input_expr cmp now_expr` or `now_expr cmp input_expr` (input side referencing a column without now(); now side a non-decreasing now()-containing expression) are supported by temporal filtering; anything else containing `now()` is rejected with this bail.","triggerScenarios":"Creating a materialized view or streaming query whose WHERE/JOIN condition contains `now()` in a non-conforming form, e.g. `now() - interval '5 minutes' > created_at` with the now-expression embedded in arithmetic, or `now() + a > b` where a side mixes columns and now().","commonSituations":"Users write time-window style filters like `WHERE created_at > now() - INTERVAL '1 hour'` on materialized views; the form must be rewritten because the now-expression is inside subtraction.","solutions":["Rewrite the condition so now() appears alone on one side of the comparison: `created_at > now() - interval '1 hour'` becomes an arrangement-friendly form by isolating now(), e.g. `now() < created_at + interval '1 hour'` only if the right side is non-decreasing; otherwise restructure.","Ensure the non-now side references a column and contains no now().","Move complex now()-based logic to query-time filters in batch queries, or use a refresh-scheduled materialized view pattern instead."],"exampleFix":"-- before\nCREATE MATERIALIZED VIEW mv AS\n  SELECT * FROM events WHERE created_at > now() - INTERVAL '1 hour';\n\n-- after (isolate now() on one side)\nCREATE MATERIALIZED VIEW mv AS\n  SELECT * FROM events WHERE now() - INTERVAL '1 hour' < created_at\n  -- or restructure to `now() < created_at + INTERVAL '1 hour'` if valid and non-decreasing;","handlingStrategy":"validation","validationCode":"fn is_now_stream_safe(cond: &Expr) -> bool {\n    if !cond.has_now() { return true; }\n    matches!(\n        cond.as_comparison(),\n        Some((lhs, _)) | Some((_, rhs))\n        if lhs.references_column_only() && !lhs.has_now()\n           || rhs.references_column_only() && !rhs.has_now()\n    )\n}","typeGuard":null,"tryCatchPattern":"match LogicalFilter::check_stream_predicate(&pred) {\n    Err(e) => {\n        eprintln!(\"predicate not streamable: {e}; isolate now() on one side of cmp\");\n        return Err(e);\n    }\n    Ok(()) => proceed(),\n}","preventionTips":["Keep now() isolated on one side of comparisons in streaming predicates","Never embed now() in arithmetic on the column side","Test MV DDL with now()-filters before deploying","Prefer batch queries for complex time-based filters"],"tags":["rust","streaming","temporal-filter","now"],"backgroundTag":"invalid-argument-value","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}