1use api::v1::CreateTableExpr;
16use common_recordbatch::map_dictionary_to_values_data_type;
17use datafusion_common::tree_node::TreeNode;
18use datafusion_expr::LogicalPlan;
19use datatypes::prelude::ConcreteDataType;
20use datatypes::schema::ColumnSchema;
21use operator::expr_helper::column_schemas_to_defs;
22use snafu::ResultExt;
23
24use crate::Error;
25use crate::adapter::{AUTO_CREATED_PLACEHOLDER_TS_COL, AUTO_CREATED_UPDATE_AT_TS_COL};
26use crate::batching_mode::utils::FindGroupByFinalName;
27use crate::error::{ConvertColumnSchemaSnafu, DatafusionSnafu};
28
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub enum QueryType {
31 Tql,
33 Sql,
35}
36
37pub(super) fn create_table_with_expr(
40 plan: &LogicalPlan,
41 sink_table_name: &[String; 3],
42 query_type: &QueryType,
43) -> Result<CreateTableExpr, Error> {
44 let table_def = match query_type {
45 &QueryType::Sql => {
46 if let Some(def) = build_pk_from_aggr(plan)? {
47 def
48 } else {
49 build_by_sql_schema(plan)?
50 }
51 }
52 QueryType::Tql => {
53 if let Some(table_def) = build_pk_from_aggr(plan)? {
55 table_def
56 } else {
57 build_by_tql_schema(plan)?
58 }
59 }
60 };
61 let first_time_stamp = table_def.ts_col;
62 let primary_keys = table_def.pks;
63
64 let mut column_schemas = Vec::new();
65 for field in plan.schema().fields() {
66 let name = field.name();
67 let ty = map_dictionary_to_values_data_type(&ConcreteDataType::from_arrow_type(
68 field.data_type(),
69 ));
70 let col_schema = if first_time_stamp == Some(name.clone()) {
71 ColumnSchema::new(name, ty, false).with_time_index(true)
72 } else {
73 ColumnSchema::new(name, ty, true)
74 };
75
76 match query_type {
77 QueryType::Sql => {
78 column_schemas.push(col_schema);
79 }
80 QueryType::Tql => {
81 let is_tag_column = primary_keys.contains(name);
84 let is_val_column = !is_tag_column && first_time_stamp.as_ref() != Some(name);
85 if is_val_column {
86 let col_schema =
87 ColumnSchema::new(name, ConcreteDataType::float64_datatype(), true);
88 column_schemas.push(col_schema);
89 } else if is_tag_column {
90 let col_schema =
91 ColumnSchema::new(name, ConcreteDataType::string_datatype(), true);
92 column_schemas.push(col_schema);
93 } else {
94 column_schemas.push(col_schema);
96 }
97 }
98 }
99 }
100
101 if query_type == &QueryType::Sql {
102 let update_at_schema = ColumnSchema::new(
103 AUTO_CREATED_UPDATE_AT_TS_COL,
104 ConcreteDataType::timestamp_millisecond_datatype(),
105 true,
106 );
107 column_schemas.push(update_at_schema);
108 }
109
110 let time_index = if let Some(time_index) = first_time_stamp {
111 time_index
112 } else {
113 column_schemas.push(
114 ColumnSchema::new(
115 AUTO_CREATED_PLACEHOLDER_TS_COL,
116 ConcreteDataType::timestamp_millisecond_datatype(),
117 false,
118 )
119 .with_time_index(true),
120 );
121 AUTO_CREATED_PLACEHOLDER_TS_COL.to_string()
122 };
123
124 let column_defs =
125 column_schemas_to_defs(column_schemas, &primary_keys).context(ConvertColumnSchemaSnafu)?;
126 Ok(CreateTableExpr {
127 catalog_name: sink_table_name[0].clone(),
128 schema_name: sink_table_name[1].clone(),
129 table_name: sink_table_name[2].clone(),
130 desc: "Auto created table by flow engine".to_string(),
131 column_defs,
132 time_index,
133 primary_keys,
134 create_if_not_exists: true,
135 table_options: Default::default(),
136 table_id: None,
137 engine: "mito".to_string(),
138 })
139}
140
141fn build_by_sql_schema(plan: &LogicalPlan) -> Result<TableDef, Error> {
143 let first_time_stamp = plan.schema().fields().iter().find_map(|f| {
144 if ConcreteDataType::from_arrow_type(f.data_type()).is_timestamp() {
145 Some(f.name().clone())
146 } else {
147 None
148 }
149 });
150 Ok(TableDef {
151 ts_col: first_time_stamp,
152 pks: vec![],
153 })
154}
155
156fn build_by_tql_schema(plan: &LogicalPlan) -> Result<TableDef, Error> {
158 let first_time_stamp = plan.schema().fields().iter().find_map(|f| {
159 if ConcreteDataType::from_arrow_type(f.data_type()).is_timestamp() {
160 Some(f.name().clone())
161 } else {
162 None
163 }
164 });
165 let string_columns = plan
166 .schema()
167 .fields()
168 .iter()
169 .filter_map(|f| {
170 if map_dictionary_to_values_data_type(&ConcreteDataType::from_arrow_type(f.data_type()))
171 .is_string()
172 {
173 Some(f.name().clone())
174 } else {
175 None
176 }
177 })
178 .collect::<Vec<_>>();
179
180 Ok(TableDef {
181 ts_col: first_time_stamp,
182 pks: string_columns,
183 })
184}
185
186struct TableDef {
187 ts_col: Option<String>,
188 pks: Vec<String>,
189}
190
191fn build_pk_from_aggr(plan: &LogicalPlan) -> Result<Option<TableDef>, Error> {
200 let fields = plan.schema().fields();
201 let mut pk_names = FindGroupByFinalName::default();
202
203 plan.visit(&mut pk_names)
204 .with_context(|_| DatafusionSnafu {
205 context: format!("Can't find aggr expr in plan {plan:?}"),
206 })?;
207
208 let Some(pk_final_names) = pk_names.get_group_expr_names() else {
210 return Ok(None);
211 };
212 if pk_final_names.is_empty() {
213 let first_ts_col = fields
214 .iter()
215 .find(|f| ConcreteDataType::from_arrow_type(f.data_type()).is_timestamp())
216 .map(|f| f.name().clone());
217 return Ok(Some(TableDef {
218 ts_col: first_ts_col,
219 pks: vec![],
220 }));
221 }
222
223 let all_pk_cols: Vec<_> = fields
224 .iter()
225 .filter(|f| pk_final_names.contains(f.name()))
226 .map(|f| f.name().clone())
227 .collect();
228 let first_time_stamp = fields
234 .iter()
235 .find(|f| {
236 all_pk_cols.contains(&f.name().clone())
237 && ConcreteDataType::from_arrow_type(f.data_type()).is_timestamp()
238 })
239 .map(|f| f.name().clone());
240
241 let all_pk_cols: Vec<_> = all_pk_cols
242 .into_iter()
243 .filter(|col| first_time_stamp.as_ref() != Some(col))
244 .collect();
245
246 Ok(Some(TableDef {
247 ts_col: first_time_stamp,
248 pks: all_pk_cols,
249 }))
250}
251
252#[cfg(test)]
253mod test {
254 use std::sync::Arc;
255
256 use api::v1::column_def::try_as_column_schema;
257 use datafusion::arrow::datatypes::{
258 DataType as ArrowDataType, Field, Schema as ArrowSchema, TimeUnit,
259 };
260 use datafusion_common::DFSchema;
261 use datafusion_expr::logical_plan::EmptyRelation;
262 use datatypes::prelude::ConcreteDataType;
263 use datatypes::schema::ColumnSchema;
264 use pretty_assertions::assert_eq;
265 use session::context::QueryContext;
266
267 use super::*;
268 use crate::adapter::{AUTO_CREATED_PLACEHOLDER_TS_COL, AUTO_CREATED_UPDATE_AT_TS_COL};
269 use crate::batching_mode::utils::sql_to_df_plan;
270 use crate::test_utils::create_test_query_engine;
271
272 #[test]
273 fn test_tql_dictionary_string_is_label() {
274 let arrow_schema = Arc::new(ArrowSchema::new(vec![
275 Field::new_dictionary("host", ArrowDataType::UInt32, ArrowDataType::Utf8, true),
276 Field::new("value", ArrowDataType::Float64, true),
277 Field::new(
278 "ts",
279 ArrowDataType::Timestamp(TimeUnit::Millisecond, None),
280 false,
281 ),
282 ]));
283 let plan = LogicalPlan::EmptyRelation(EmptyRelation {
284 produce_one_row: false,
285 schema: Arc::new(DFSchema::try_from(arrow_schema).unwrap()),
286 });
287
288 let expr = create_table_with_expr(
289 &plan,
290 &[
291 "greptime".to_string(),
292 "public".to_string(),
293 "sink".to_string(),
294 ],
295 &QueryType::Tql,
296 )
297 .unwrap();
298 let columns = expr
299 .column_defs
300 .iter()
301 .map(|column| try_as_column_schema(column).unwrap())
302 .collect::<Vec<_>>();
303
304 assert_eq!(vec!["host".to_string()], expr.primary_keys);
305 assert_eq!("ts", expr.time_index);
306 assert_eq!(ConcreteDataType::string_datatype(), columns[0].data_type);
307 assert_eq!(ConcreteDataType::float64_datatype(), columns[1].data_type);
308 assert!(columns[2].is_time_index());
309 }
310
311 #[tokio::test]
312 async fn test_gen_create_table_sql() {
313 let query_engine = create_test_query_engine();
314 let ctx = QueryContext::arc();
315 struct TestCase {
316 sql: String,
317 sink_table_name: String,
318 column_schemas: Vec<ColumnSchema>,
319 primary_keys: Vec<String>,
320 time_index: String,
321 }
322
323 let update_at_schema = ColumnSchema::new(
324 AUTO_CREATED_UPDATE_AT_TS_COL,
325 ConcreteDataType::timestamp_millisecond_datatype(),
326 true,
327 );
328
329 let ts_placeholder_schema = ColumnSchema::new(
330 AUTO_CREATED_PLACEHOLDER_TS_COL,
331 ConcreteDataType::timestamp_millisecond_datatype(),
332 false,
333 )
334 .with_time_index(true);
335
336 let testcases = vec![
337 TestCase {
338 sql: "SELECT number, ts FROM numbers_with_ts".to_string(),
339 sink_table_name: "new_table".to_string(),
340 column_schemas: vec![
341 ColumnSchema::new("number", ConcreteDataType::uint32_datatype(), true),
342 ColumnSchema::new(
343 "ts",
344 ConcreteDataType::timestamp_millisecond_datatype(),
345 false,
346 )
347 .with_time_index(true),
348 update_at_schema.clone(),
349 ],
350 primary_keys: vec![],
351 time_index: "ts".to_string(),
352 },
353 TestCase {
354 sql: "SELECT number, max(ts) FROM numbers_with_ts GROUP BY number".to_string(),
355 sink_table_name: "new_table".to_string(),
356 column_schemas: vec![
357 ColumnSchema::new("number", ConcreteDataType::uint32_datatype(), true),
358 ColumnSchema::new(
359 "max(numbers_with_ts.ts)",
360 ConcreteDataType::timestamp_millisecond_datatype(),
361 true,
362 ),
363 update_at_schema.clone(),
364 ts_placeholder_schema.clone(),
365 ],
366 primary_keys: vec!["number".to_string()],
367 time_index: AUTO_CREATED_PLACEHOLDER_TS_COL.to_string(),
368 },
369 TestCase {
370 sql: "SELECT max(number), ts FROM numbers_with_ts GROUP BY ts".to_string(),
371 sink_table_name: "new_table".to_string(),
372 column_schemas: vec![
373 ColumnSchema::new(
374 "max(numbers_with_ts.number)",
375 ConcreteDataType::uint32_datatype(),
376 true,
377 ),
378 ColumnSchema::new(
379 "ts",
380 ConcreteDataType::timestamp_millisecond_datatype(),
381 false,
382 )
383 .with_time_index(true),
384 update_at_schema.clone(),
385 ],
386 primary_keys: vec![],
387 time_index: "ts".to_string(),
388 },
389 TestCase {
390 sql: "SELECT number, ts FROM numbers_with_ts GROUP BY ts, number".to_string(),
391 sink_table_name: "new_table".to_string(),
392 column_schemas: vec![
393 ColumnSchema::new("number", ConcreteDataType::uint32_datatype(), true),
394 ColumnSchema::new(
395 "ts",
396 ConcreteDataType::timestamp_millisecond_datatype(),
397 false,
398 )
399 .with_time_index(true),
400 update_at_schema.clone(),
401 ],
402 primary_keys: vec!["number".to_string()],
403 time_index: "ts".to_string(),
404 },
405 ];
406
407 for tc in testcases {
408 let plan = sql_to_df_plan(ctx.clone(), query_engine.clone(), &tc.sql, true)
409 .await
410 .unwrap();
411 let expr = create_table_with_expr(
412 &plan,
413 &[
414 "greptime".to_string(),
415 "public".to_string(),
416 tc.sink_table_name.clone(),
417 ],
418 &QueryType::Sql,
419 )
420 .unwrap();
421 let column_schemas = expr
423 .column_defs
424 .iter()
425 .map(|c| try_as_column_schema(c).unwrap())
426 .collect::<Vec<_>>();
427 assert_eq!(tc.column_schemas, column_schemas, "{:?}", tc.sql);
428 assert_eq!(tc.primary_keys, expr.primary_keys, "{:?}", tc.sql);
429 assert_eq!(tc.time_index, expr.time_index, "{:?}", tc.sql);
430 }
431 }
432}