risingwavelabs/risingwave · error
expect struct<...>
Error message
expect struct<...>
What it means
StructType::from_str parses textual struct types like `struct<a int,b int>` (and `record` for the empty unnamed struct). If the string does not start with `struct<` and end with `>`, the parser rejects it with this error.
Solutions
- Format the string as `struct<field1 type1,field2 type2>` or exactly `record` for empty structs
- Convert ROW<...>/STRUCT(...)-style names to the `struct<...>` form before parsing
- Trim whitespace around the whole string
Example fix
// before let ty: StructType = "struct(a int)".parse()?; // after let ty: StructType = "struct<a int>".parse()?;
Defensive patterns
Strategy: validation
Validate before calling
fn is_struct_type_string(s: &str) -> bool {
let s = s.trim();
s == "record" || (s.starts_with("struct<") && s.ends_with('>'))
} Type guard
fn looks_like_struct_type(s: &str) -> bool {
let s = s.trim();
s == "record" || (s.starts_with("struct<") && s.ends_with('>'))
} Try / catch
let ty = s.parse::<StructType>()
.with_context(|| format!("invalid struct type string: {s}"))?; Prevention
- Generate struct type strings via StructType's Display
- Convert ROW<...>/STRUCT(...) dialects to struct<...> first
- Handle the special-case string "record" for empty structs
When it happens
Trigger: Calling `s.parse::<StructType>()` with strings like `struct(a int)`, `row(...)`, a plain field list, or `record<...>` instead of `struct<...>`.
Common situations: Migrating type names from other systems (Spark `struct<...>` works; Flink `ROW<...>` does not), hand-edited catalog entries, or strings that lost the wrapper during serialization.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/35441a111e7c39de.
Report an issue: GitHub.
Appendix: source
Thrown at src/common/src/types/struct_type.rs:271
f,
"struct<{}>",
self.iter()
.map(|(name, ty)| format!("{} {}", QuoteIdent(name), ty))
.join(", ")
)
}
}
}
impl FromStr for StructType {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "record" {
return Ok(StructType::unnamed(Vec::new()));
}
if !(s.starts_with("struct<") && s.ends_with('>')) {
return Err(anyhow!("expect struct<...>"));
};
let mut fields = Vec::new();
for field in s[7..s.len() - 1].split(',') {
let field = field.trim();
let mut iter = field.split_whitespace();
let field_name = iter.next().unwrap().to_owned();
let field_type = DataType::from_str(iter.next().unwrap())?;
fields.push((field_name, field_type));
}
Ok(Self::new(fields))
}
}
View on GitHub (pinned to 6469eb736d)