Skip to main content

sql/statements/transform/
type_alias.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::ops::ControlFlow;
16
17use datatypes::data_type::DataType as GreptimeDataType;
18use sqlparser::ast::{
19    DataType, ExactNumberInfo, Expr, Function, FunctionArg, FunctionArgExpr, FunctionArgumentList,
20    Ident, ObjectName, Value,
21};
22
23use crate::ast::ObjectNamePartExt;
24use crate::error::Result;
25use crate::statements::alter::AlterTableOperation;
26use crate::statements::create::{CreateExternalTable, CreateTable};
27use crate::statements::statement::Statement;
28use crate::statements::transform::TransformRule;
29use crate::statements::{TimezoneInfo, sql_data_type_to_concrete_data_type};
30
31/// SQL data type alias transformer:
32///  - `TimestampSecond`, `Timestamp_s`, `Timestamp_sec` for `Timestamp(0)`.
33///  - `TimestampMillisecond`, `Timestamp_ms` for `Timestamp(3)`.
34///  - `TimestampMicrosecond`, `Timestamp_us` for `Timestamp(6)`.
35///  - `TimestampNanosecond`, `Timestamp_ns` for `Timestamp(9)`.
36///  -  TinyText, MediumText, LongText for `Text`.
37///
38/// SQL dialect integer type aliases (MySQL & PostgreSQL):
39///  - `INT2` for `smallint`
40///  - `INT4` for `int`
41///  - `INT8` for `bigint`
42///  - `FLOAT4` for `float`
43///  - `FLOAT8` for `double`
44///
45/// Extended type aliases for Arrow types:
46///  - `INT16` for `smallint`
47///  - `INT32` for `int`
48///  - `INT64` for `bigint`
49///  -  And `UINT8`, `UINT16` etc. for `TinyIntUnsigned` etc.
50///
51pub(crate) struct TypeAliasTransformRule;
52
53impl TransformRule for TypeAliasTransformRule {
54    fn visit_statement(&self, stmt: &mut Statement) -> Result<()> {
55        match stmt {
56            Statement::CreateTable(CreateTable { columns, .. }) => {
57                columns
58                    .iter_mut()
59                    .for_each(|column| replace_type_alias(column.mut_data_type()));
60            }
61            Statement::CreateExternalTable(CreateExternalTable { columns, .. }) => {
62                columns
63                    .iter_mut()
64                    .for_each(|column| replace_type_alias(column.mut_data_type()));
65            }
66            Statement::AlterTable(alter_table) => {
67                if let AlterTableOperation::ModifyColumnType { target_type, .. } =
68                    alter_table.alter_operation_mut()
69                {
70                    replace_type_alias(target_type)
71                } else if let AlterTableOperation::AddColumns { add_columns, .. } =
72                    alter_table.alter_operation_mut()
73                {
74                    for add_column in add_columns {
75                        replace_type_alias(&mut add_column.column_def.data_type);
76                    }
77                }
78            }
79            _ => {}
80        }
81
82        Ok(())
83    }
84
85    fn visit_expr(&self, expr: &mut Expr) -> ControlFlow<()> {
86        fn cast_expr_to_arrow_cast_func(expr: Expr, cast_type: String) -> Function {
87            Function {
88                name: ObjectName::from(vec![Ident::new("arrow_cast")]),
89                args: sqlparser::ast::FunctionArguments::List(FunctionArgumentList {
90                    args: vec![
91                        FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)),
92                        FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
93                            Value::SingleQuotedString(cast_type).into(),
94                        ))),
95                    ],
96                    duplicate_treatment: None,
97                    clauses: vec![],
98                }),
99                filter: None,
100                null_treatment: None,
101                over: None,
102                parameters: sqlparser::ast::FunctionArguments::None,
103                within_group: vec![],
104                uses_odbc_syntax: false,
105            }
106        }
107
108        match expr {
109            // In new sqlparser, the "INT64" is no longer parsed to custom datatype.
110            // The new "Int64" is not recognizable by Datafusion, cannot directly "CAST" to it.
111            // We have to replace the expr to "arrow_cast" function call here.
112            // Same for "FLOAT64".
113            Expr::Cast {
114                expr: cast_expr,
115                data_type,
116                ..
117            } if get_type_by_alias(data_type).is_some() => {
118                // Safety: checked in the match arm.
119                let new_type = get_type_by_alias(data_type).unwrap();
120                if let Ok(new_type) = sql_data_type_to_concrete_data_type(&new_type) {
121                    *expr = Expr::Function(cast_expr_to_arrow_cast_func(
122                        (**cast_expr).clone(),
123                        new_type.as_arrow_type().to_string(),
124                    ));
125                }
126            }
127
128            // Timestamp(precision) in cast, datafusion doesn't support Timestamp(9) etc.
129            // We have to transform it into arrow_cast(expr, type).
130            Expr::Cast {
131                data_type: DataType::Timestamp(precision, zone),
132                expr: cast_expr,
133                ..
134            } => {
135                if let Ok(concrete_type) =
136                    sql_data_type_to_concrete_data_type(&DataType::Timestamp(*precision, *zone))
137                {
138                    let new_type = concrete_type.as_arrow_type();
139                    *expr = Expr::Function(cast_expr_to_arrow_cast_func(
140                        (**cast_expr).clone(),
141                        new_type.to_string(),
142                    ));
143                }
144            }
145
146            // TODO(dennis): supports try_cast
147            _ => {}
148        }
149
150        ControlFlow::<()>::Continue(())
151    }
152}
153
154fn replace_type_alias(data_type: &mut DataType) {
155    if let Some(new_type) = get_type_by_alias(data_type) {
156        *data_type = new_type;
157    }
158}
159
160/// Get data type from alias type.
161/// Returns the mapped data type if the input data type is an alias that we need to replace.
162// Remember to update `get_data_type_by_alias_name()` if you modify this method.
163pub(crate) fn get_type_by_alias(data_type: &DataType) -> Option<DataType> {
164    match data_type {
165        DataType::Custom(name, tokens) if name.0.len() == 1 && tokens.is_empty() => {
166            get_data_type_by_alias_name(name.0[0].to_string_unquoted().as_str())
167        }
168        DataType::Int2(None) => Some(DataType::SmallInt(None)),
169        DataType::Int4(None) => Some(DataType::Int(None)),
170        DataType::Int8(None) => Some(DataType::BigInt(None)),
171        DataType::Int16 => Some(DataType::SmallInt(None)),
172        DataType::Int32 => Some(DataType::Int(None)),
173        DataType::Int64 => Some(DataType::BigInt(None)),
174        DataType::UInt8 => Some(DataType::TinyIntUnsigned(None)),
175        DataType::UInt16 => Some(DataType::SmallIntUnsigned(None)),
176        DataType::UInt32 => Some(DataType::IntUnsigned(None)),
177        DataType::UInt64 => Some(DataType::BigIntUnsigned(None)),
178        DataType::Float4 => Some(DataType::Float(ExactNumberInfo::None)),
179        DataType::Float8 => Some(DataType::Double(ExactNumberInfo::None)),
180        DataType::Float32 => Some(DataType::Float(ExactNumberInfo::None)),
181        DataType::Float64 => Some(DataType::Double(ExactNumberInfo::None)),
182        DataType::Bool => Some(DataType::Boolean),
183        DataType::Datetime(_) => Some(DataType::Timestamp(Some(6), TimezoneInfo::None)),
184        _ => None,
185    }
186}
187
188/// Get the mapped data type from alias name.
189/// It only supports the following types of alias:
190/// - timestamps
191/// - ints
192/// - floats
193/// - texts
194// Remember to update `get_type_alias()` if you modify this method.
195pub(crate) fn get_data_type_by_alias_name(name: &str) -> Option<DataType> {
196    match name.to_uppercase().as_ref() {
197        // Timestamp type alias
198        "TIMESTAMP_S" | "TIMESTAMP_SEC" | "TIMESTAMPSECOND" => {
199            Some(DataType::Timestamp(Some(0), TimezoneInfo::None))
200        }
201
202        "TIMESTAMP_MS" | "TIMESTAMPMILLISECOND" => {
203            Some(DataType::Timestamp(Some(3), TimezoneInfo::None))
204        }
205        "TIMESTAMP_US" | "TIMESTAMPMICROSECOND" | "DATETIME" => {
206            Some(DataType::Timestamp(Some(6), TimezoneInfo::None))
207        }
208        "TIMESTAMP_NS" | "TIMESTAMPNANOSECOND" => {
209            Some(DataType::Timestamp(Some(9), TimezoneInfo::None))
210        }
211        // Number type alias
212        "INT2" => Some(DataType::SmallInt(None)),
213        "INT4" => Some(DataType::Int(None)),
214        "INT8" => Some(DataType::BigInt(None)),
215        "INT16" => Some(DataType::SmallInt(None)),
216        "INT32" => Some(DataType::Int(None)),
217        "INT64" => Some(DataType::BigInt(None)),
218        "UINT8" => Some(DataType::TinyIntUnsigned(None)),
219        "UINT16" => Some(DataType::SmallIntUnsigned(None)),
220        "UINT32" => Some(DataType::IntUnsigned(None)),
221        "UINT64" => Some(DataType::BigIntUnsigned(None)),
222        "FLOAT4" => Some(DataType::Float(ExactNumberInfo::None)),
223        "FLOAT8" => Some(DataType::Double(ExactNumberInfo::None)),
224        "FLOAT32" => Some(DataType::Float(ExactNumberInfo::None)),
225        "FLOAT64" => Some(DataType::Double(ExactNumberInfo::None)),
226        // String type alias
227        "TINYTEXT" | "MEDIUMTEXT" | "LONGTEXT" => Some(DataType::Text),
228        _ => None,
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use sqlparser::dialect::GenericDialect;
235
236    use super::*;
237    use crate::parser::{ParseOptions, ParserContext};
238    use crate::statements::transform_statements;
239
240    #[test]
241    fn test_get_data_type_by_alias_name() {
242        assert_eq!(
243            get_data_type_by_alias_name("float64"),
244            Some(DataType::Double(ExactNumberInfo::None))
245        );
246        assert_eq!(
247            get_data_type_by_alias_name("Float64"),
248            Some(DataType::Double(ExactNumberInfo::None))
249        );
250        assert_eq!(
251            get_data_type_by_alias_name("FLOAT64"),
252            Some(DataType::Double(ExactNumberInfo::None))
253        );
254        assert_eq!(
255            get_data_type_by_alias_name("float32"),
256            Some(DataType::Float(ExactNumberInfo::None))
257        );
258        assert_eq!(
259            get_data_type_by_alias_name("float8"),
260            Some(DataType::Double(ExactNumberInfo::None))
261        );
262        assert_eq!(
263            get_data_type_by_alias_name("float4"),
264            Some(DataType::Float(ExactNumberInfo::None))
265        );
266        assert_eq!(
267            get_data_type_by_alias_name("int8"),
268            Some(DataType::BigInt(None))
269        );
270        assert_eq!(
271            get_data_type_by_alias_name("int4"),
272            Some(DataType::Int(None))
273        );
274        assert_eq!(
275            get_data_type_by_alias_name("int2"),
276            Some(DataType::SmallInt(None))
277        );
278        assert_eq!(
279            get_data_type_by_alias_name("INT16"),
280            Some(DataType::SmallInt(None))
281        );
282        assert_eq!(
283            get_data_type_by_alias_name("INT32"),
284            Some(DataType::Int(None))
285        );
286        assert_eq!(
287            get_data_type_by_alias_name("INT64"),
288            Some(DataType::BigInt(None))
289        );
290        assert_eq!(
291            get_data_type_by_alias_name("Uint8"),
292            Some(DataType::TinyIntUnsigned(None))
293        );
294        assert_eq!(
295            get_data_type_by_alias_name("UINT16"),
296            Some(DataType::SmallIntUnsigned(None))
297        );
298        assert_eq!(
299            get_data_type_by_alias_name("UINT32"),
300            Some(DataType::IntUnsigned(None))
301        );
302        assert_eq!(
303            get_data_type_by_alias_name("uint64"),
304            Some(DataType::BigIntUnsigned(None))
305        );
306
307        assert_eq!(
308            get_data_type_by_alias_name("TimestampSecond"),
309            Some(DataType::Timestamp(Some(0), TimezoneInfo::None))
310        );
311        assert_eq!(
312            get_data_type_by_alias_name("Timestamp_s"),
313            Some(DataType::Timestamp(Some(0), TimezoneInfo::None))
314        );
315        assert_eq!(
316            get_data_type_by_alias_name("Timestamp_sec"),
317            Some(DataType::Timestamp(Some(0), TimezoneInfo::None))
318        );
319
320        assert_eq!(
321            get_data_type_by_alias_name("TimestampMilliSecond"),
322            Some(DataType::Timestamp(Some(3), TimezoneInfo::None))
323        );
324        assert_eq!(
325            get_data_type_by_alias_name("Timestamp_ms"),
326            Some(DataType::Timestamp(Some(3), TimezoneInfo::None))
327        );
328
329        assert_eq!(
330            get_data_type_by_alias_name("TimestampMicroSecond"),
331            Some(DataType::Timestamp(Some(6), TimezoneInfo::None))
332        );
333        assert_eq!(
334            get_data_type_by_alias_name("Timestamp_us"),
335            Some(DataType::Timestamp(Some(6), TimezoneInfo::None))
336        );
337
338        assert_eq!(
339            get_data_type_by_alias_name("TimestampNanoSecond"),
340            Some(DataType::Timestamp(Some(9), TimezoneInfo::None))
341        );
342        assert_eq!(
343            get_data_type_by_alias_name("Timestamp_ns"),
344            Some(DataType::Timestamp(Some(9), TimezoneInfo::None))
345        );
346        assert_eq!(
347            get_data_type_by_alias_name("TinyText"),
348            Some(DataType::Text)
349        );
350        assert_eq!(
351            get_data_type_by_alias_name("MediumText"),
352            Some(DataType::Text)
353        );
354        assert_eq!(
355            get_data_type_by_alias_name("LongText"),
356            Some(DataType::Text)
357        );
358    }
359
360    fn test_timestamp_alias(alias: &str, expected: &str) {
361        let sql = format!("SELECT TIMESTAMP '2020-01-01 01:23:45.12345678'::{alias}");
362        let mut stmts =
363            ParserContext::create_with_dialect(&sql, &GenericDialect {}, ParseOptions::default())
364                .unwrap();
365        transform_statements(&mut stmts).unwrap();
366
367        match &stmts[0] {
368            Statement::Query(q) => assert_eq!(
369                format!(
370                    "SELECT arrow_cast(TIMESTAMP '2020-01-01 01:23:45.12345678', 'Timestamp({expected})')"
371                ),
372                q.to_string()
373            ),
374            _ => unreachable!(),
375        }
376    }
377
378    fn test_timestamp_precision_type(precision: i32, expected: &str) {
379        test_timestamp_alias(&format!("Timestamp({precision})"), expected);
380    }
381
382    #[test]
383    fn test_boolean_alias() {
384        let sql = "CREATE TABLE test(b bool, ts TIMESTAMP TIME INDEX)";
385        let mut stmts =
386            ParserContext::create_with_dialect(sql, &GenericDialect {}, ParseOptions::default())
387                .unwrap();
388        transform_statements(&mut stmts).unwrap();
389
390        match &stmts[0] {
391            Statement::CreateTable(c) => assert_eq!(
392                "CREATE TABLE test (\n  b BOOLEAN,\n  ts TIMESTAMP NOT NULL,\n  TIME INDEX (ts)\n)\nENGINE=mito\n",
393                c.to_string()
394            ),
395            _ => unreachable!(),
396        }
397    }
398
399    #[test]
400    fn test_transform_timestamp_alias() {
401        // Timestamp[Second | Millisecond | Microsecond | Nanosecond]
402        test_timestamp_alias("TimestampSecond", "s");
403        test_timestamp_alias("Timestamp_s", "s");
404        test_timestamp_alias("TimestampMillisecond", "ms");
405        test_timestamp_alias("Timestamp_ms", "ms");
406        test_timestamp_alias("TimestampMicrosecond", "µs");
407        test_timestamp_alias("Timestamp_us", "µs");
408        test_timestamp_alias("TimestampNanosecond", "ns");
409        test_timestamp_alias("Timestamp_ns", "ns");
410        // Timestamp(precision)
411        test_timestamp_precision_type(0, "s");
412        test_timestamp_precision_type(3, "ms");
413        test_timestamp_precision_type(6, "µs");
414        test_timestamp_precision_type(9, "ns");
415    }
416
417    #[test]
418    fn test_create_sql_with_type_alias() {
419        let sql = r#"
420CREATE TABLE data_types (
421  s string,
422  tt tinytext,
423  mt mediumtext,
424  lt longtext,
425  i2 int2,
426  i4 int4,
427  i8 int8,
428  sint int16,
429  i int32,
430  bint int64,
431  v varchar,
432  f4 float4,
433  f8 float8,
434  f float32,
435  d float64,
436  b boolean,
437  vb varbinary,
438  dt date,
439  dtt datetime,
440  ts0 TimestampSecond,
441  ts3 TimestampMillisecond,
442  ts6 TimestampMicrosecond,
443  ts9 TimestampNanosecond DEFAULT CURRENT_TIMESTAMP TIME INDEX,
444  PRIMARY KEY(s));"#;
445
446        let mut stmts =
447            ParserContext::create_with_dialect(sql, &GenericDialect {}, ParseOptions::default())
448                .unwrap();
449        transform_statements(&mut stmts).unwrap();
450
451        match &stmts[0] {
452            Statement::CreateTable(c) => {
453                let expected = r#"CREATE TABLE data_types (
454  s STRING,
455  tt TINYTEXT,
456  mt MEDIUMTEXT,
457  lt LONGTEXT,
458  i2 SMALLINT,
459  i4 INT,
460  i8 BIGINT,
461  sint SMALLINT,
462  i INT,
463  bint BIGINT,
464  v VARCHAR,
465  f4 FLOAT,
466  f8 DOUBLE,
467  f FLOAT,
468  d DOUBLE,
469  b BOOLEAN,
470  vb VARBINARY,
471  dt DATE,
472  dtt TIMESTAMP(6),
473  ts0 TIMESTAMP(0),
474  ts3 TIMESTAMP(3),
475  ts6 TIMESTAMP(6),
476  ts9 TIMESTAMP(9) DEFAULT CURRENT_TIMESTAMP NOT NULL,
477  TIME INDEX (ts9),
478  PRIMARY KEY (s)
479)
480ENGINE=mito
481"#;
482
483                assert_eq!(expected, c.to_string());
484            }
485            _ => unreachable!(),
486        }
487    }
488}