risingwave_frontend/binder/
struct_field.rs1use itertools::Itertools;
16use risingwave_common::types::{DataType, Scalar};
17use risingwave_sqlparser::ast::{Expr, Ident};
18
19use crate::binder::Binder;
20use crate::error::{ErrorCode, Result};
21use crate::expr::{Expr as RwExpr, ExprImpl, ExprType, FunctionCall, Literal};
22
23impl Binder {
24 fn extract_struct_column(
31 &mut self,
32 expr: &Expr,
33 field_idents: &[Ident],
34 ) -> Result<(ExprImpl, Vec<Ident>)> {
35 let d = self.bind_expr(expr)?;
36 Ok((d, field_idents.to_vec()))
37 }
38
39 pub fn bind_wildcard_field_column(
43 &mut self,
44 expr: &Expr,
45 prefix: &[Ident],
46 ) -> Result<(Vec<ExprImpl>, Vec<Option<String>>)> {
47 let (expr, idents) = self.extract_struct_column(expr, prefix)?;
48 let fields = Self::bind_field("".to_owned(), expr, &idents, true)?;
49 let (exprs, names) = fields.into_iter().map(|(e, s)| (e, Some(s))).unzip();
50 Ok((exprs, names))
51 }
52
53 pub fn bind_single_field_column(&mut self, expr: &Expr, idents: &[Ident]) -> Result<ExprImpl> {
57 let (expr, idents) = self.extract_struct_column(expr, idents)?;
58 let exprs = Self::bind_field("".to_owned(), expr, &idents, false)?;
59 Ok(exprs[0].clone().0)
60 }
61
62 fn bind_field(
65 field_name: String,
66 expr: ExprImpl,
67 idents: &[Ident],
68 wildcard: bool,
69 ) -> Result<Vec<(ExprImpl, String)>> {
70 match idents.first() {
71 Some(ident) => {
72 let field_name = ident.real_value();
73 let (field_type, field_index) = find_field(expr.return_type(), ident.real_value())?;
74 let expr = FunctionCall::new_unchecked(
75 ExprType::Field,
76 vec![
77 expr,
78 Literal::new(
79 Some((field_index as i32).to_scalar_value()),
80 DataType::Int32,
81 )
82 .into(),
83 ],
84 field_type,
85 )
86 .into();
87 Self::bind_field(field_name, expr, &idents[1..], wildcard)
88 }
89 None => {
90 if wildcard {
91 Self::bind_wildcard_field(expr)
92 } else {
93 Ok(vec![(expr, field_name)])
94 }
95 }
96 }
97 }
98
99 fn bind_wildcard_field(expr: ExprImpl) -> Result<Vec<(ExprImpl, String)>> {
102 let input = expr.return_type();
103 if let DataType::Struct(t) = input {
104 Ok(t.iter()
105 .enumerate()
106 .map(|(i, (name, ty))| {
107 (
108 FunctionCall::new_unchecked(
109 ExprType::Field,
110 vec![
111 expr.clone(),
112 Literal::new(Some((i as i32).to_scalar_value()), DataType::Int32)
113 .into(),
114 ],
115 ty.clone(),
116 )
117 .into(),
118 name.to_owned(),
119 )
120 })
121 .collect_vec())
122 } else {
123 Err(ErrorCode::BindError(format!("type \"{}\" is not composite", input)).into())
124 }
125 }
126}
127
128fn find_field(input: DataType, field_name: String) -> Result<(DataType, usize)> {
129 if let DataType::Struct(t) = input {
130 let mut matches = t
131 .iter()
132 .enumerate()
133 .filter(|(_, (name, _))| name == &field_name);
134 let Some((pos, (_, ty))) = matches.next() else {
135 return Err(ErrorCode::BindError(format!(
136 "field \"{field_name}\" not found in struct type"
137 ))
138 .into());
139 };
140 if matches.next().is_some() {
141 return Err(ErrorCode::BindError(format!(
142 "struct field \"{field_name}\" is ambiguous"
143 ))
144 .into());
145 }
146 Ok((ty.clone(), pos))
147 } else {
148 Err(ErrorCode::BindError(format!(
149 "column notation .{} applied to type {}, which is not a composite type",
150 field_name, input
151 ))
152 .into())
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use risingwave_common::types::StructType;
159
160 use super::*;
161
162 #[test]
163 fn find_field_rejects_ambiguous_struct_field() {
164 let data_type = DataType::Struct(StructType::new([
165 ("a", DataType::Int32),
166 ("a", DataType::Varchar),
167 ("b", DataType::Boolean),
168 ]));
169
170 assert!(find_field(data_type.clone(), "a".to_owned()).is_err());
171
172 assert_eq!(
175 find_field(data_type, "b".to_owned()).unwrap(),
176 (DataType::Boolean, 2)
177 );
178 }
179
180 #[test]
181 fn find_field_distinguishes_missing_and_non_struct_inputs() {
182 let data_type = DataType::Struct(StructType::new([("a", DataType::Int32)]));
183 assert!(find_field(data_type, "b".to_owned()).is_err());
184 assert!(find_field(DataType::Int32, "a".to_owned()).is_err());
185 }
186}