risingwavelabs/risingwave · error · SinkError::Config

Primary key not defined for upsert clickhouse sink (please d

Error message

Primary key not defined for upsert clickhouse sink (please define in `primary_key` field)

What it means

The ClickHouse sink's validate() requires an upsert sink to have a primary key. If the sink is not append-only and no primary key columns were derived (pk_indices empty), validation fails, telling users to define it via the `primary_key` field.

Source

Thrown at src/connector/src/sink/clickhouse.rs:568

                ck_column.name, fields_type, ck_column.r#type
            )));
        }

        Ok(())
    }
}

impl Sink for ClickHouseSink {
    type LogSinker = DecoupleCheckpointLogSinkerOf<ClickHouseSinkWriter>;

    const SINK_NAME: &'static str = CLICKHOUSE_SINK;

    crate::impl_validate_sink_unknown_fields!();

    async fn validate(&self) -> Result<()> {
        // For upsert clickhouse sink, the primary key must be defined.
        if !self.is_append_only && self.pk_indices.is_empty() {
            return Err(SinkError::Config(anyhow!(
                "Primary key not defined for upsert clickhouse sink (please define in `primary_key` field)"
            )));
        }

        // check reachability
        let client = self.config.common.build_client()?;

        let (clickhouse_column, clickhouse_engine) =
            query_column_engine_from_ck(client, &self.config).await?;
        if clickhouse_engine.is_shared_tree() {
            risingwave_common::license::Feature::ClickHouseSharedEngine
                .check_available()
                .map_err(|e| anyhow::anyhow!(e))?;
        }

        if !self.is_append_only
            && !clickhouse_engine.is_collapsing_engine()
            && !clickhouse_engine.is_delete_replacing_engine()

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Add primary_key='col1,col2' to the WITH options listing the key columns.
  2. Switch the sink to type='append-only' if upsert semantics are not needed.
  3. Create the sink from a materialized view that includes a primary key.

Example fix

// before
WITH (connector='clickhouse', type='upsert', ...)
// after
WITH (connector='clickhouse', type='upsert', primary_key='user_id', ...)
Defensive patterns

Strategy: validation

Validate before calling

if (props.type === 'upsert' && !props.primary_key) throw new Error("ClickHouse upsert sink requires primary_key in WITH options");

Prevention

When it happens

Trigger: CREATE SINK ... WITH (connector='clickhouse', type='upsert', ...) where neither the WITH options nor the underlying table supply primary key columns, leaving pk_indices empty.

Common situations: Sinking a table without a primary key in upsert mode; forgetting the `primary_key` property; expecting ClickHouse's own ORDER BY to substitute for a RisingWave primary key.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


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