risingwavelabs/risingwave · error · SinkError::Config
Turbopuffer namespace must match [A-Za-z0-9-_.]
Error message
Turbopuffer namespace must match [A-Za-z0-9-_.]{{1,128}} What it means
After the length check, validate_namespace enforces that every byte of the namespace is an ASCII alphanumeric, '-', '_', or '.'. Namespaces containing spaces, slashes, unicode, or other symbols are rejected with this message. It is raised both at sink creation (try_from) and per-row via url_for_row.
Solutions
- Restrict the namespace to [A-Za-z0-9-_.] characters.
- Sanitize/replace invalid characters in the namespace_column via a materialized view (e.g. regexp_replace).
- Hash or encode arbitrary strings (e.g. md5 hex) before using them as namespaces.
Example fix
// before: namespace from raw URL path WITH (turbopuffer.namespace_column='path') // after CREATE MVIEW mv AS SELECT regexp_replace(path, '[^A-Za-z0-9_.-]', '_', 'g') AS path, * FROM src; WITH (turbopuffer.namespace_column='path')
Defensive patterns
Strategy: validation
Validate before calling
const NS_RE: once_cell::sync::Lazy<regex::Regex> =
once_cell::sync::Lazy::new(|| regex::Regex::new("^[A-Za-z0-9-_.]{1,128}$").unwrap());
fn namespace_ok(ns: &str) -> bool { NS_RE.is_match(ns) } Prevention
- Sanitize data-driven namespaces with regexp_replace before sinking.
- Use only [A-Za-z0-9-_.] in namespace options.
- Hash arbitrary strings (md5/sha hex) when namespaces derive from user data.
When it happens
Trigger: Setting turbopuffer.namespace to a value with illegal characters (e.g. 'my index', 'ns/eu', unicode names); a namespace_column row value containing characters outside [A-Za-z0-9-_.].
Common situations: Using user-supplied or auto-generated strings (emails, URLs, UUIDs with dashes are fine, but slashes/spaces are not) as namespaces; forgetting to sanitize data-driven namespace values.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- Turbopuffer namespace must be 1 to 128 bytes
- `max_linger_second` must be greater than 0
- `num_shards` must be greater than 0
- `write_batch_size` must be greater than 0
- actor count ( ) exceeds vnode count ( )
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/21c96658b702fc5c.
Report an issue: GitHub.
Appendix: source
Thrown at src/connector/src/sink/turbopuffer.rs:734
tracing::warn!(
value,
"cast negative turbopuffer integer document id to unsigned integer"
);
}
DocumentId::U64(value as u64)
}
fn validate_namespace(namespace: &str) -> Result<()> {
if namespace.is_empty() || namespace.len() > 128 {
return Err(SinkError::Config(anyhow!(
"Turbopuffer namespace must be 1 to 128 bytes"
)));
}
if !namespace
.bytes()
.all(|b| b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b'.'))
{
return Err(SinkError::Config(anyhow!(
"Turbopuffer namespace must match [A-Za-z0-9-_.]{{1,128}}"
)));
}
Ok(())
}
fn parse_column_selection(
value: Option<&str>,
schema: &Schema,
attribute_indices: &[usize],
) -> Result<HashSet<String>> {
let attribute_names = attribute_indices
.iter()
.map(|index| schema[*index].name.as_str())
.collect::<HashSet<_>>();
let columns: HashSet<String> = match value {
Some(value) if value.trim() == "*" => {
return Ok(attribute_namesView on GitHub (pinned to 6469eb736d)