risingwave_expr_impl/scalar/external/
iceberg.rs1use std::fmt::Formatter;
18use std::str::FromStr;
19use std::sync::Arc;
20
21use anyhow::anyhow;
22use iceberg::spec::{PrimitiveType, Transform, Type as IcebergType};
23use iceberg::transform::{BoxedTransformFunction, create_transform_function};
24use risingwave_common::array::arrow::{IcebergArrowConvert, arrow_schema_iceberg};
25use risingwave_common::array::{ArrayRef, DataChunk};
26use risingwave_common::ensure;
27use risingwave_common::row::OwnedRow;
28use risingwave_common::types::{DataType, Datum};
29use risingwave_expr::expr::{
30 BoxedExpression, ExpressionInfo, SyncExpression, SyncExpressionBoxExt,
31};
32use risingwave_expr::{ExprError, Result, build_function};
33use thiserror_ext::AsReport;
34
35pub struct IcebergTransform {
36 child: Arc<dyn SyncExpression>,
37 transform: BoxedTransformFunction,
38 input_arrow_type: arrow_schema_iceberg::DataType,
39 output_arrow_field: arrow_schema_iceberg::Field,
40 return_type: DataType,
41}
42
43impl std::fmt::Debug for IcebergTransform {
44 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
45 f.debug_struct("IcebergTransform")
46 .field("child", &self.child)
47 .field("return_type", &self.return_type)
48 .finish()
49 }
50}
51
52impl ExpressionInfo for IcebergTransform {
53 fn return_type(&self) -> DataType {
54 self.return_type.clone()
55 }
56}
57
58impl SyncExpression for IcebergTransform {
59 fn eval(&self, data_chunk: &DataChunk) -> Result<ArrayRef> {
60 let array = self.child.eval(data_chunk)?;
61 let arrow_array = IcebergArrowConvert.to_arrow_array(&self.input_arrow_type, &array)?;
63 let res_array = self.transform.transform(arrow_array).unwrap();
65 Ok(Arc::new(IcebergArrowConvert.array_from_arrow_array(
67 &self.output_arrow_field,
68 &res_array,
69 )?))
70 }
71
72 fn eval_row(&self, _row: &OwnedRow) -> Result<Datum> {
73 Err(ExprError::Internal(anyhow!(
74 "eval_row in iceberg_transform is not supported yet"
75 )))
76 }
77}
78
79#[build_function("iceberg_transform(varchar, any) -> any", type_infer = "unreachable")]
80fn build(return_type: DataType, mut children: Vec<BoxedExpression>) -> Result<BoxedExpression> {
81 let transform_type = {
82 let datum = children[0].eval_const()?.unwrap();
83 let str = datum.as_utf8();
84 Transform::from_str(str).map_err(|_| ExprError::InvalidParam {
85 name: "transform type in iceberg_transform",
86 reason: format!("Fail to parse {str} as iceberg transform type").into(),
87 })?
88 };
89
90 assert!(!matches!(
93 transform_type,
94 Transform::Identity | Transform::Void
95 ));
96
97 let input_arrow_type = IcebergArrowConvert
101 .to_arrow_field("", &children[1].return_type())?
102 .data_type()
103 .clone();
104 let output_arrow_field = IcebergArrowConvert.to_arrow_field("", &return_type)?;
105 let input_type = iceberg::arrow::arrow_type_to_type(&input_arrow_type).map_err(|err| {
106 ExprError::InvalidParam {
107 name: "input type in iceberg_transform",
108 reason: format!(
109 "Failed to convert input type to iceberg type, got error: {}",
110 err.as_report()
111 )
112 .into(),
113 }
114 })?;
115 let expect_res_type = transform_type.result_type(&input_type).map_err(
116 |err| ExprError::InvalidParam {
117 name: "input type in iceberg_transform",
118 reason: format!(
119 "Failed to get result type for transform type {:?} and input type {:?}, got error: {}",
120 transform_type, input_type, err.as_report()
121 )
122 .into()
123 })?;
124 let actual_res_type = iceberg::arrow::arrow_type_to_type(
125 &IcebergArrowConvert
126 .to_arrow_field("", &return_type)?
127 .data_type()
128 .clone(),
129 )
130 .map_err(|err| ExprError::InvalidParam {
131 name: "return type in iceberg_transform",
132 reason: format!(
133 "Failed to convert return type to iceberg type, got error: {}",
134 err.as_report()
135 )
136 .into(),
137 })?;
138
139 ensure!(
140 (expect_res_type == actual_res_type)
141 ||
142 (matches!(transform_type, Transform::Day) && matches!(actual_res_type, IcebergType::Primitive(PrimitiveType::Int))),
144 ExprError::InvalidParam {
145 name: "return type in iceberg_transform",
146 reason: format!(
147 "Expect return type {:?} but got {:?}, RisingWave return type is {:?}, input type is {:?}, transform type is {:?}",
148 expect_res_type,
149 actual_res_type,
150 return_type,
151 (input_type, input_arrow_type),
152 transform_type
153 )
154 .into()
155 }
156 );
157
158 let child = match children.remove(1) {
159 BoxedExpression::Sync(child) => child,
160 BoxedExpression::Async(_) => {
161 return Err(ExprError::Internal(anyhow!(
162 "async child in iceberg_transform is not supported"
163 )));
164 }
165 };
166 let transform = create_transform_function(&transform_type)
167 .map_err(|err| ExprError::Internal(err.into()))?;
168 Ok(IcebergTransform {
169 child,
170 transform,
171 input_arrow_type,
172 output_arrow_field,
173 return_type,
174 }
175 .boxed())
176}
177
178#[cfg(test)]
179mod test {
180 use risingwave_common::array::{DataChunk, DataChunkTestExt};
181 use risingwave_expr::expr::build_from_pretty;
182
183 #[tokio::test]
184 async fn test_bucket() {
185 let (input, expected) = DataChunk::from_pretty(
186 "i i
187 34 1373",
188 )
189 .split_column_at(1);
190 let expr = build_from_pretty("(iceberg_transform:int4 bucket[2017]:varchar $0:int)");
191 let res = expr.eval(&input).await.unwrap();
192 assert_eq!(res, *expected.column_at(0));
193 }
194
195 #[tokio::test]
196 async fn test_truncate() {
197 let (input, expected) = DataChunk::from_pretty(
198 "T T
199 iceberg ice
200 risingwave ris
201 delta del",
202 )
203 .split_column_at(1);
204 let expr = build_from_pretty("(iceberg_transform:varchar truncate[3]:varchar $0:varchar)");
205 let res = expr.eval(&input).await.unwrap();
206 assert_eq!(res, *expected.column_at(0));
207 }
208
209 #[tokio::test]
210 async fn test_year_month_day_hour() {
211 let (input, expected) = DataChunk::from_pretty(
212 "TZ i i D i
213 1970-01-01T00:00:00.000000000+00:00 0 0 1970-01-01 0
214 1971-02-01T01:00:00.000000000+00:00 1 13 1971-02-01 9505
215 1972-03-01T02:00:00.000000000+00:00 2 26 1972-03-01 18962
216 1970-05-01T06:00:00.000000000+00:00 0 4 1970-05-01 2886
217 1970-06-01T07:00:00.000000000+00:00 0 5 1970-06-01 3631",
218 )
219 .split_column_at(1);
220
221 let expr = build_from_pretty("(iceberg_transform:int4 year:varchar $0:timestamptz)");
223 let res = expr.eval(&input).await.unwrap();
224 assert_eq!(res, *expected.column_at(0));
225
226 let expr = build_from_pretty("(iceberg_transform:int4 month:varchar $0:timestamptz)");
228 let res = expr.eval(&input).await.unwrap();
229 assert_eq!(res, *expected.column_at(1));
230
231 let expr = build_from_pretty("(iceberg_transform:int4 day:varchar $0:timestamptz)");
233 let res = expr.eval(&input).await.unwrap();
234 assert_eq!(res, *expected.column_at(2));
235
236 let expr = build_from_pretty("(iceberg_transform:int4 hour:varchar $0:timestamptz)");
238 let res = expr.eval(&input).await.unwrap();
239 assert_eq!(res, *expected.column_at(3));
240 }
241}