risingwavelabs/risingwave · error

received a DDL message, please set `canal.instance.filter.qu

Error message

received a DDL message, please set `canal.instance.filter.query.dml` to true.

What it means

The Canal JSON parser refuses messages whose `isDdl` flag is true. RisingWave consumes only DML change events; DDL statements must be filtered at the Canal server so they never appear on the Kafka topic.

Source

Thrown at src/connector/src/parser/canal/simd_json_parser.rs:71

        })
    }

    #[expect(clippy::unused_async)]
    pub async fn parse_inner(
        &self,
        mut payload: Vec<u8>,
        mut writer: SourceStreamChunkRowWriter<'_>,
    ) -> ConnectorResult<()> {
        let mut event: BorrowedValue<'_> =
            simd_json::to_borrowed_value(&mut payload[self.payload_start_idx..])
                .context("failed to parse canal json payload")?;

        let is_ddl = event
            .get(IS_DDL)
            .and_then(|v| v.as_bool())
            .context("field `isDdl` not found in canal json")?;
        if is_ddl {
            bail!("received a DDL message, please set `canal.instance.filter.query.dml` to true.");
        }

        let op = match event.get(OP).and_then(|v| v.as_str()) {
            Some(CANAL_INSERT_EVENT | CANAL_UPDATE_EVENT) => ChangeEventOperation::Upsert,
            Some(CANAL_DELETE_EVENT) => ChangeEventOperation::Delete,
            _ => bail!("op field not found in canal json"),
        };

        let events = event
            .get_mut(DATA)
            .and_then(|v| match v {
                BorrowedValue::Array(array) => Some(array),
                _ => None,
            })
            .context("field `data` is missing for creating event")?;

        let mut errors = Vec::new();
        for event in events.drain(..) {

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Set `canal.instance.filter.query.dml=true` in the Canal instance configuration and restart the instance
  2. Filter the topic on the producer side (e.g. Canal MQ routing rules) to drop DDL messages
  3. Use a separate Canal topic/instance for DDL if DDL capture is needed elsewhere

Example fix

// canal.properties / instance.properties (before)
# canal.instance.filter.query.dml unset
// after
canal.instance.filter.query.dml=true
Defensive patterns

Strategy: validation

Validate before calling

if (msg && msg.isDdl === true) { /* route to DDL handler or drop before feeding RW */ }

Type guard

const isDml = (m) => m && m.isDdl === false && typeof m.op === 'string';

Try / catch

if err.to_string().contains("DDL message") { drop_event_and_metric("canal_ddl_filtered"); } else { propagate }

Prevention

When it happens

Trigger: A Canal instance was configured without `canal.instance.filter.query.dml=true`, so DDL events (CREATE/ALTER/DROP) are serialized into the topic and parsed by parse_inner.

Common situations: Default Canal deployments that don't filter DDL; adding DDL to a replicated database after the source was set up; sharing a Canal topic between consumers where one needs DDL.

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/d7ac18cf81b46c30. Report an issue: GitHub.