risingwavelabs/risingwave · error

`LogicalNow` can only be converted to stream

Error message

`LogicalNow` can only be converted to stream

What it means

LogicalNow represents the non-deterministic now() function. In RisingWave, now() is only meaningful in streaming plans (where it is materialized as a streaming expression with provisional/refresh semantics); there is no batch lowering, so to_batch explicitly errors. For batch queries users should use CURRENT_TIMESTAMP (fixed at query start) instead.

Solutions

  1. Use CURRENT_TIMESTAMP instead of now() in batch queries
  2. In INSERT ... SELECT batch statements, replace now() with CURRENT_TIMESTAMP
  3. Check frontend rewrite rules if you maintain the codebase: ensure now() in batch contexts is rewritten to CurrentTimestamp before ToBatch
  4. If it appears in a materialized view path that unexpectedly goes batch, verify the query is not being forced into batch mode

Example fix

-- before (fails in batch)
SELECT now();
INSERT INTO t SELECT now(), ...;
-- after
SELECT CURRENT_TIMESTAMP;
INSERT INTO t SELECT CURRENT_TIMESTAMP, ...;
Defensive patterns

Strategy: validation

Validate before calling

-- In batch queries, always use CURRENT_TIMESTAMP instead of now()
-- static check in app code:
-- if /\bnow\s*\(\s*\)/i.test(batchSql) throw new Error('use CURRENT_TIMESTAMP in batch queries');

Try / catch

try {
  await conn.query('SELECT now()');
} catch (e) {
  if (String(e.message).includes('`LogicalNow` can only be converted to stream')) {
    await conn.query('SELECT CURRENT_TIMESTAMP');
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling to_batch on a plan containing LogicalNow — typically using now() inside a batch SELECT, batch DML, or a query path that is forced through batch optimization (e.g. inside a batch exec of a DML statement where now() was not rewritten).

Common situations: Users write INSERT ... SELECT now() or batch SELECT now() expecting wall-clock time; frontend rewrites usually substitute CURRENT_TIMESTAMP for now() in batch contexts, and this error surfaces when a now() survives into batch optimization.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at src/frontend/src/optimizer/plan_node/logical_now.rs:98

    fn logical_rewrite_for_stream(
        &self,
        _ctx: &mut RewriteStreamContext,
    ) -> Result<(PlanRef, ColIndexMapping)> {
        Ok((self.clone().into(), ColIndexMapping::new(vec![Some(0)], 1)))
    }

    /// `to_stream` is equivalent to `to_stream_with_dist_required(RequiredDist::Any)`
    fn to_stream(
        &self,
        _ctx: &mut ToStreamContext,
    ) -> Result<crate::optimizer::plan_node::StreamPlanRef> {
        Ok(StreamNow::new(self.core.clone()).into())
    }
}

impl ToBatch for LogicalNow {
    fn to_batch(&self) -> Result<crate::optimizer::plan_node::BatchPlanRef> {
        bail!("`LogicalNow` can only be converted to stream")
    }
}

/// The trait for column pruning, only logical plan node will use it, though all plan node impl it.
impl ColPrunable for LogicalNow {
    fn prune_col(&self, required_cols: &[usize], _: &mut ColumnPruningContext) -> PlanRef {
        if required_cols.is_empty() {
            LogicalValues::new(vec![], Schema::empty().clone(), self.ctx()).into()
        } else {
            assert_eq!(required_cols, &[0], "we only output one column");
            self.clone().into()
        }
    }
}

View on GitHub (pinned to 6469eb736d)