{"record":{"id":"be26efa6cbdd7de9","repo":"risingwavelabs/risingwave","slug":"turbopuffer-namespace-column-cannot-be-null","errorCode":null,"errorMessage":"Turbopuffer namespace_column cannot be null","messagePattern":"Turbopuffer namespace_column cannot be null","errorType":"http","errorClass":"SinkError::Http","httpStatus":null,"severity":"error","filePath":"src/connector/src/sink/turbopuffer.rs","lineNumber":499,"sourceCode":"            row_encoder,\n            write_batch_size,\n            max_linger,\n            pending_batches: BTreeMap::new(),\n        })\n    }\n\n    fn url_for_row(&self, row: &impl Row) -> Result<String> {\n        match &self.namespace {\n            TurbopufferNamespace::Static(namespace) => Ok(format!(\n                \"{}/v2/namespaces/{}\",\n                self.base_url,\n                namespace.as_str()\n            )),\n            TurbopufferNamespace::Dynamic { index } => {\n                let namespace = match row.datum_at(*index) {\n                    Some(ScalarRefImpl::Utf8(namespace)) => namespace,\n                    None => {\n                        return Err(SinkError::Http(anyhow!(\n                            \"Turbopuffer namespace_column cannot be null\"\n                        )));\n                    }\n                    Some(_) => {\n                        return Err(SinkError::Http(anyhow!(\n                            \"unexpected namespace_column type, expected varchar\"\n                        )));\n                    }\n                };\n                validate_namespace(namespace)?;\n                Ok(format!(\"{}/v2/namespaces/{}\", self.base_url, namespace))\n            }\n        }\n    }\n\n    // Turbopuffer document IDs are unsigned 64-bit integers, UUIDs, or strings up to 64 bytes.\n    // RisingWave UUID IDs can be represented with varchar.\n    fn id_for_row(&self, row: &impl Row) -> Result<DocumentId> {","sourceCodeStart":481,"sourceCodeEnd":517,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/connector/src/sink/turbopuffer.rs#L481-L517","documentation":"When the namespace is chosen per row via `namespace_column`, the URL for each write is built from that column's value in `url_for_row`. If the datum at the namespace index is NULL at write time, there is no namespace to route the document to, so an Http error is returned for the chunk.","triggerScenarios":"Writing a row where the varchar namespace_column is NULL; rows inserted with an unspecified namespace column after the sink was created.","commonSituations":"Nullable namespace column in the MV feeding the sink; batch backfills that omit the namespace field; LEFT JOIN results producing NULL namespaces for unmatched rows.","solutions":["Make the namespace column NOT NULL in the upstream MV/table (e.g. COALESCE(ns, 'default')).","Backfill existing NULL rows with a default namespace before enabling the sink.","Add a WHERE filter upstream to exclude rows with NULL namespace.","If a single namespace works, switch to the static `namespace` option."],"exampleFix":"// before\nCREATE MATERIALIZED VIEW mv AS SELECT tenant_id::varchar AS ns, * FROM t;\n\n// after\nCREATE MATERIALIZED VIEW mv AS\nSELECT COALESCE(tenant_id::varchar, 'default') AS ns, * FROM t;","handlingStrategy":"validation","validationCode":"// SQL: detect NULL namespaces before the sink processes them\nSELECT count(*) FROM mv_for_sink WHERE ns IS NULL; -- should be 0","typeGuard":"// in a row-processing pipeline, before building the sink payload\nfunction getNamespace(row) {\n  const ns = row.ns;\n  if (typeof ns !== 'string') throw new TypeError('namespace_column must be a non-null string');\n  return ns;\n}","tryCatchPattern":"// route failing rows to a dead-letter path\ntry {\n  await sink.write(row);\n} catch (e) {\n  if (String(e).includes('namespace_column cannot be null')) {\n    deadLetter.push({ row, reason: 'null-namespace' });\n  } else { throw e; }\n}","preventionTips":["Declare namespace columns NOT NULL with COALESCE defaults","Filter NULL-namespace rows out of the MV feeding the sink"],"tags":["sink","turbopuffer","null-value","runtime"],"backgroundTag":"null-argument","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}