risingwavelabs/risingwave · error · SinkError::Config
Turbopuffer sink requires exactly one primary_key column
Error message
Turbopuffer sink requires exactly one primary_key column
What it means
The turbopuffer sink uses the primary key column as the document id, so it requires the sink schema to have exactly one primary key column. Schemas with zero or multiple PK columns are rejected at sink construction.
Source
Thrown at src/connector/src/sink/turbopuffer.rs:154
impl EnforceSecret for TurbopufferSink {
fn enforce_secret<'a>(
prop_iter: impl Iterator<Item = &'a str>,
) -> crate::error::ConnectorResult<()> {
for prop in prop_iter {
TurbopufferConfig::enforce_one(prop)?;
}
Ok(())
}
}
impl TryFrom<SinkParam> for TurbopufferSink {
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)?;View on GitHub (pinned to 6469eb736d)
Solutions
- Create the sink from a source with exactly one primary key column
- Reshape the query to produce a single-column unique key (e.g. concat/hash into one id column)
- Choose a different sink connector if composite keys are required
Example fix
// before -- MV with composite PK (a, b) CREATE SINK tp FROM mv WITH (connector='turbopuffer', ...); // after CREATE MATERIALIZED VIEW mv2 AS SELECT md5(concat(a::text, b::text)) AS id, ... FROM mv; CREATE SINK tp FROM mv2 WITH (connector='turbopuffer', primary_key = 'id', ...);
Defensive patterns
Strategy: validation
Validate before calling
const pks = schema.columns.filter(c => c.isPrimaryKey); if (pks.length !== 1) throw new Error('turbopuffer needs exactly one PK column') Type guard
const hasSinglePk = (schema) => schema.primaryKeyColumns?.length === 1;
Try / catch
catch (SinkError::Config(e)) if e.contains('exactly one primary_key') { reshape schema upstream } Prevention
- Ensure upstream MV/table has exactly one PK column before turbopuffer sinks
- Avoid composite keys; hash into a single id column
- Check PK count in pre-creation schema validation
When it happens
Trigger: CREATE SINK on a materialized view/table whose primary key has 0 or 2+ columns, with connector='turbopuffer'.
Common situations: Sink over an append-only MV without a PK; composite primary keys from a join; forgetting to define a PK before creating the sink.
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
- no value find in sink schema, index is {:?}
- Turbopuffer document id column must be an integer or varchar
- Turbopuffer document id column cannot be null
- Turbopuffer string document id exceeds 64 bytes
- Turbopuffer document id column must be an integer or varchar
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/0c7f1ab9e9e78b46.
Report an issue: GitHub.