risingwavelabs/risingwave · error

Conditions containing now must be in the form of…

Error message

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()`.

What it means

`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.

Solutions

  1. 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.
  2. Ensure the non-now side references a column and contains no now().
  3. Move complex now()-based logic to query-time filters in batch queries, or use a refresh-scheduled materialized view pattern instead.

Example fix

-- before
CREATE MATERIALIZED VIEW mv AS
  SELECT * FROM events WHERE created_at > now() - INTERVAL '1 hour';

-- after (isolate now() on one side)
CREATE MATERIALIZED VIEW mv AS
  SELECT * FROM events WHERE now() - INTERVAL '1 hour' < created_at
  -- or restructure to `now() < created_at + INTERVAL '1 hour'` if valid and non-decreasing;
Defensive patterns

Strategy: validation

Validate before calling

fn is_now_stream_safe(cond: &Expr) -> bool {
    if !cond.has_now() { return true; }
    matches!(
        cond.as_comparison(),
        Some((lhs, _)) | Some((_, rhs))
        if lhs.references_column_only() && !lhs.has_now()
           || rhs.references_column_only() && !rhs.has_now()
    )
}

Try / catch

match LogicalFilter::check_stream_predicate(&pred) {
    Err(e) => {
        eprintln!("predicate not streamable: {e}; isolate now() on one side of cmp");
        return Err(e);
    }
    Ok(()) => proceed(),
}

Prevention

When it happens

Trigger: 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().

Common situations: 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.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/7369701dfc5db916. Report an issue: GitHub.

Appendix: source

Thrown at src/frontend/src/optimizer/plan_node/logical_filter.rs:60

pub struct LogicalFilter {
    pub base: PlanBase<Logical>,
    core: generic::Filter<PlanRef>,
}

impl LogicalFilter {
    pub fn new(input: PlanRef, predicate: Condition) -> Self {
        let _ctx = input.ctx();
        for cond in &predicate.conjunctions {
            assert_input_ref!(cond, input.schema().fields().len());
        }
        let core = generic::Filter { predicate, input };
        let base = PlanBase::new_logical_with_core(&core);
        LogicalFilter { base, core }
    }

    pub fn check_stream_predicate(predicate: &Condition) -> Result<()> {
        if predicate.conjunctions.iter().any(|cond| cond.has_now()) {
            bail!(
                "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()`."
            );
        }
        Ok(())
    }

    /// Create a `LogicalFilter` unless the predicate is always true
    pub fn create(input: PlanRef, predicate: Condition) -> PlanRef {
        if predicate.always_true() {
            input
        } else {
            LogicalFilter::new(input, predicate).into()
        }
    }

    /// Create a `LogicalFilter` to filter out rows where all keys are null.

View on GitHub (pinned to 6469eb736d)