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

  1. Format the string as `struct<field1 type1,field2 type2>` or exactly `record` for empty structs
  2. Convert ROW<...>/STRUCT(...)-style names to the `struct<...>` form before parsing
  3. 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

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)