risingwavelabs/risingwave · error · SinkError::Config

Turbopuffer document id column must be an integer or varchar

Error message

Turbopuffer document id column must be an integer or varchar, got {:?}

What it means

Raised in TryFrom<SinkParam> for the Turbopuffer sink when the single primary-key column's data type is not one of the accepted integer/varchar types. Turbopuffer document IDs must be unsigned 64-bit integers, UUIDs, or strings up to 64 bytes, so a PK of e.g. floating-point or struct type cannot be used as the document id and sink creation fails.

Source

Thrown at src/connector/src/sink/turbopuffer.rs:166

    type Error = SinkError;

    fn try_from(param: SinkParam) -> std::result::Result<Self, Self::Error> {
        let schema = param.schema();
        let pk_indices = param.downstream_pk_or_empty();
        let [pk_index] = pk_indices.as_slice() else {
            return Err(SinkError::Config(anyhow!(
                "Turbopuffer sink requires exactly one primary_key column"
            )));
        };
        let pk_index = *pk_index;
        match schema[pk_index].data_type() {
            DataType::Int16
            | DataType::Int32
            | DataType::Int64
            | DataType::Serial
            | DataType::Varchar => {}
            data_type => {
                return Err(SinkError::Config(anyhow!(
                    "Turbopuffer document id column must be an integer or varchar, got {:?}",
                    data_type
                )));
            }
        };
        let config = TurbopufferConfig::from_btreemap(param.properties)?;

        let namespace = match (&config.namespace, &config.namespace_column) {
            (Some(namespace), None) => {
                validate_namespace(namespace)?;
                TurbopufferNamespace::Static(namespace.clone())
            }
            (None, Some(namespace_column)) => {
                let index = schema
                    .fields()
                    .iter()
                    .position(|field| field.name == *namespace_column)
                    .ok_or_else(|| {

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Cast the PK column to VARCHAR (or an integer type) in an upstream materialized view
  2. Change the id column type at the source to an integer or varchar
  3. Pick a different unique column of supported type as the primary key

Example fix

// before
CREATE MATERIALIZED VIEW mv AS SELECT created_at AS id, ... ; -- timestamp id
// after
CREATE MATERIALIZED VIEW mv AS SELECT created_at::VARCHAR AS id, ... ;
Defensive patterns

Strategy: validation

Validate before calling

const t = pkColumn.type; if (!['int16','int32','int64','serial','varchar'].includes(t)) throw new Error('unsupported id type: ' + t)

Type guard

const isSupportedIdType = (dt) => ['Int16','Int32','Int64','Serial','Varchar'].includes(dt);

Try / catch

catch (SinkError::Config(e)) if e.contains('integer or varchar') { cast id column upstream }

Prevention

When it happens

Trigger: CREATE SINK with connector='turbopuffer' where the single PK column has an unsupported data type like Decimal, Timestamp, Float64, or a struct.

Common situations: Using a timestamp or decimal as the natural id; composite key collapsed into a struct/array column.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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