risingwavelabs/risingwave · error · SinkError::Config

Turbopuffer sink requires only one of namespace or namespace

Error message

Turbopuffer sink requires only one of namespace or namespace_column

What it means

The Turbopuffer sink accepts the namespace either as a static literal (`namespace`) or dynamically per row (`namespace_column`), but not both. In `try_from`, the (Some, Some) arm of the namespace match rejects sinks that specify both options together.

Source

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

                    .fields()
                    .iter()
                    .position(|field| field.name == *namespace_column)
                    .ok_or_else(|| {
                        SinkError::Config(anyhow!(
                            "Turbopuffer namespace_column '{}' not found in sink schema",
                            namespace_column
                        ))
                    })?;
                if schema[index].data_type != DataType::Varchar {
                    return Err(SinkError::Config(anyhow!(
                        "Turbopuffer namespace_column must be varchar, got {:?}",
                        schema[index].data_type
                    )));
                }
                TurbopufferNamespace::Dynamic { index }
            }
            (Some(_), Some(_)) => {
                return Err(SinkError::Config(anyhow!(
                    "Turbopuffer sink requires only one of namespace or namespace_column"
                )));
            }
            (None, None) => {
                return Err(SinkError::Config(anyhow!(
                    "Turbopuffer sink requires either namespace or namespace_column"
                )));
            }
        };

        // Turbopuffer treats `id` as the document ID in write requests; it is not a schema
        // attribute. Dynamic namespace is also metadata for routing, not a document attribute.
        let excluded_indices = match &namespace {
            TurbopufferNamespace::Static(_) => HashSet::from([pk_index]),
            TurbopufferNamespace::Dynamic { index } => HashSet::from([pk_index, *index]),
        };
        let attribute_indices = (0..schema.len())
            .filter(|idx| !excluded_indices.contains(idx))

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Remove the `namespace` option and keep `namespace_column` if namespaces vary per row.
  2. Remove the `namespace_column` option and keep the static `namespace` if one namespace is enough.
  3. Review the final WITH clause with SHOW SINK / the catalog to ensure only one namespace option remains.

Example fix

// before
WITH (
  connector = 'turbopuffer',
  namespace = 'docs',
  namespace_column = 'ns'
)

// after
WITH (
  connector = 'turbopuffer',
  namespace_column = 'ns'
)
Defensive patterns

Strategy: validation

Validate before calling

// Ensure only one namespace option appears in the WITH clause
const opts = ['namespace', 'namespace_column'];
if (opts.filter(o => withClause[o] != null).length > 1) throw new Error('specify only one of namespace / namespace_column');

Prevention

When it happens

Trigger: Creating a Turbopuffer sink whose WITH options include both `namespace = 'x'` and `namespace_column = 'col'`.

Common situations: Merging config snippets from two examples; adding namespace_column to an existing sink that already had a static namespace; templated sink definitions that inject both keys.

Related errors


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