sql/
statements.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
15pub mod admin;
16pub mod alter;
17pub mod copy;
18pub mod create;
19pub mod cursor;
20pub mod delete;
21pub mod describe;
22pub mod drop;
23pub mod explain;
24pub mod insert;
25pub mod kill;
26mod option_map;
27pub mod query;
28pub mod set_variables;
29pub mod show;
30pub mod statement;
31pub mod tql;
32pub(crate) mod transform;
33pub mod truncate;
34
35use api::helper::ColumnDataTypeWrapper;
36use api::v1::SemanticType;
37use common_sql::default_constraint::parse_column_default_constraint;
38use common_time::timezone::Timezone;
39use datatypes::prelude::ConcreteDataType;
40use datatypes::schema::{ColumnDefaultConstraint, ColumnSchema, COMMENT_KEY};
41use datatypes::types::TimestampType;
42use datatypes::value::Value;
43use snafu::ResultExt;
44use sqlparser::ast::{ExactNumberInfo, Ident, ObjectName};
45
46use crate::ast::{
47    ColumnDef, ColumnOption, DataType as SqlDataType, TimezoneInfo, Value as SqlValue,
48};
49use crate::error::{
50    self, ConvertToGrpcDataTypeSnafu, ConvertValueSnafu, Result,
51    SerializeColumnDefaultConstraintSnafu, SetFulltextOptionSnafu, SetSkippingIndexOptionSnafu,
52    SqlCommonSnafu,
53};
54use crate::statements::create::Column;
55pub use crate::statements::option_map::OptionMap;
56pub(crate) use crate::statements::transform::transform_statements;
57
58const VECTOR_TYPE_NAME: &str = "VECTOR";
59
60pub fn value_to_sql_value(val: &Value) -> Result<SqlValue> {
61    Ok(match val {
62        Value::Int8(v) => SqlValue::Number(v.to_string(), false),
63        Value::UInt8(v) => SqlValue::Number(v.to_string(), false),
64        Value::Int16(v) => SqlValue::Number(v.to_string(), false),
65        Value::UInt16(v) => SqlValue::Number(v.to_string(), false),
66        Value::Int32(v) => SqlValue::Number(v.to_string(), false),
67        Value::UInt32(v) => SqlValue::Number(v.to_string(), false),
68        Value::Int64(v) => SqlValue::Number(v.to_string(), false),
69        Value::UInt64(v) => SqlValue::Number(v.to_string(), false),
70        Value::Float32(v) => SqlValue::Number(v.to_string(), false),
71        Value::Float64(v) => SqlValue::Number(v.to_string(), false),
72        Value::Boolean(b) => SqlValue::Boolean(*b),
73        Value::Date(d) => SqlValue::SingleQuotedString(d.to_string()),
74        Value::Timestamp(ts) => SqlValue::SingleQuotedString(ts.to_iso8601_string()),
75        Value::String(s) => SqlValue::SingleQuotedString(s.as_utf8().to_string()),
76        Value::Null => SqlValue::Null,
77        // TODO(dennis): supports binary
78        _ => return ConvertValueSnafu { value: val.clone() }.fail(),
79    })
80}
81
82/// Return true when the `ColumnDef` options contain primary key
83pub fn has_primary_key_option(column_def: &ColumnDef) -> bool {
84    column_def
85        .options
86        .iter()
87        .any(|options| match options.option {
88            ColumnOption::Unique { is_primary, .. } => is_primary,
89            _ => false,
90        })
91}
92
93/// Create a `ColumnSchema` from `Column`.
94pub fn column_to_schema(
95    column: &Column,
96    time_index: &str,
97    timezone: Option<&Timezone>,
98) -> Result<ColumnSchema> {
99    let is_time_index = column.name().value == time_index;
100
101    let is_nullable = column
102        .options()
103        .iter()
104        .all(|o| !matches!(o.option, ColumnOption::NotNull))
105        && !is_time_index;
106
107    let name = column.name().value.clone();
108    let data_type = sql_data_type_to_concrete_data_type(column.data_type())?;
109    let default_constraint =
110        parse_column_default_constraint(&name, &data_type, column.options(), timezone)
111            .context(SqlCommonSnafu)?;
112
113    let mut column_schema = ColumnSchema::new(name, data_type, is_nullable)
114        .with_time_index(is_time_index)
115        .with_default_constraint(default_constraint)
116        .context(error::InvalidDefaultSnafu {
117            column: &column.name().value,
118        })?;
119
120    if let Some(ColumnOption::Comment(c)) = column.options().iter().find_map(|o| {
121        if matches!(o.option, ColumnOption::Comment(_)) {
122            Some(&o.option)
123        } else {
124            None
125        }
126    }) {
127        let _ = column_schema
128            .mut_metadata()
129            .insert(COMMENT_KEY.to_string(), c.to_string());
130    }
131
132    if let Some(options) = column.extensions.build_fulltext_options()? {
133        column_schema = column_schema
134            .with_fulltext_options(options)
135            .context(SetFulltextOptionSnafu)?;
136    }
137
138    if let Some(options) = column.extensions.build_skipping_index_options()? {
139        column_schema = column_schema
140            .with_skipping_options(options)
141            .context(SetSkippingIndexOptionSnafu)?;
142    }
143
144    column_schema.set_inverted_index(column.extensions.inverted_index_options.is_some());
145
146    Ok(column_schema)
147}
148
149/// Convert `ColumnDef` in sqlparser to `ColumnDef` in gRPC proto.
150pub fn sql_column_def_to_grpc_column_def(
151    col: &ColumnDef,
152    timezone: Option<&Timezone>,
153) -> Result<api::v1::ColumnDef> {
154    let name = col.name.value.clone();
155    let data_type = sql_data_type_to_concrete_data_type(&col.data_type)?;
156
157    let is_nullable = col
158        .options
159        .iter()
160        .all(|o| !matches!(o.option, ColumnOption::NotNull));
161
162    let default_constraint =
163        parse_column_default_constraint(&name, &data_type, &col.options, timezone)
164            .context(SqlCommonSnafu)?
165            .map(ColumnDefaultConstraint::try_into) // serialize default constraint to bytes
166            .transpose()
167            .context(SerializeColumnDefaultConstraintSnafu)?;
168    // convert ConcreteDataType to grpc ColumnDataTypeWrapper
169    let (datatype, datatype_ext) = ColumnDataTypeWrapper::try_from(data_type.clone())
170        .context(ConvertToGrpcDataTypeSnafu)?
171        .to_parts();
172
173    let is_primary_key = col.options.iter().any(|o| {
174        matches!(
175            o.option,
176            ColumnOption::Unique {
177                is_primary: true,
178                ..
179            }
180        )
181    });
182
183    let semantic_type = if is_primary_key {
184        SemanticType::Tag
185    } else {
186        SemanticType::Field
187    };
188
189    Ok(api::v1::ColumnDef {
190        name,
191        data_type: datatype as i32,
192        is_nullable,
193        default_constraint: default_constraint.unwrap_or_default(),
194        semantic_type: semantic_type as _,
195        comment: String::new(),
196        datatype_extension: datatype_ext,
197        options: None,
198    })
199}
200
201pub fn sql_data_type_to_concrete_data_type(data_type: &SqlDataType) -> Result<ConcreteDataType> {
202    match data_type {
203        SqlDataType::BigInt(_) | SqlDataType::Int64 => Ok(ConcreteDataType::int64_datatype()),
204        SqlDataType::UnsignedBigInt(_) => Ok(ConcreteDataType::uint64_datatype()),
205        SqlDataType::Int(_) | SqlDataType::Integer(_) => Ok(ConcreteDataType::int32_datatype()),
206        SqlDataType::UnsignedInt(_) | SqlDataType::UnsignedInteger(_) => {
207            Ok(ConcreteDataType::uint32_datatype())
208        }
209        SqlDataType::SmallInt(_) => Ok(ConcreteDataType::int16_datatype()),
210        SqlDataType::UnsignedSmallInt(_) => Ok(ConcreteDataType::uint16_datatype()),
211        SqlDataType::TinyInt(_) | SqlDataType::Int8(_) => Ok(ConcreteDataType::int8_datatype()),
212        SqlDataType::UnsignedTinyInt(_) | SqlDataType::UnsignedInt8(_) => {
213            Ok(ConcreteDataType::uint8_datatype())
214        }
215        SqlDataType::Char(_)
216        | SqlDataType::Varchar(_)
217        | SqlDataType::Text
218        | SqlDataType::TinyText
219        | SqlDataType::MediumText
220        | SqlDataType::LongText
221        | SqlDataType::String(_) => Ok(ConcreteDataType::string_datatype()),
222        SqlDataType::Float(_) => Ok(ConcreteDataType::float32_datatype()),
223        SqlDataType::Double(_) | SqlDataType::Float64 => Ok(ConcreteDataType::float64_datatype()),
224        SqlDataType::Boolean => Ok(ConcreteDataType::boolean_datatype()),
225        SqlDataType::Date => Ok(ConcreteDataType::date_datatype()),
226        SqlDataType::Binary(_)
227        | SqlDataType::Blob(_)
228        | SqlDataType::Bytea
229        | SqlDataType::Varbinary(_) => Ok(ConcreteDataType::binary_datatype()),
230        SqlDataType::Datetime(_) => Ok(ConcreteDataType::timestamp_microsecond_datatype()),
231        SqlDataType::Timestamp(precision, _) => Ok(precision
232            .as_ref()
233            .map(|v| TimestampType::try_from(*v))
234            .transpose()
235            .map_err(|_| {
236                error::SqlTypeNotSupportedSnafu {
237                    t: data_type.clone(),
238                }
239                .build()
240            })?
241            .map(|t| ConcreteDataType::timestamp_datatype(t.unit()))
242            .unwrap_or(ConcreteDataType::timestamp_millisecond_datatype())),
243        SqlDataType::Interval => Ok(ConcreteDataType::interval_month_day_nano_datatype()),
244        SqlDataType::Decimal(exact_info) => match exact_info {
245            ExactNumberInfo::None => Ok(ConcreteDataType::decimal128_default_datatype()),
246            // refer to https://dev.mysql.com/doc/refman/8.0/en/fixed-point-types.html
247            // In standard SQL, the syntax DECIMAL(M) is equivalent to DECIMAL(M,0).
248            ExactNumberInfo::Precision(p) => Ok(ConcreteDataType::decimal128_datatype(*p as u8, 0)),
249            ExactNumberInfo::PrecisionAndScale(p, s) => {
250                Ok(ConcreteDataType::decimal128_datatype(*p as u8, *s as i8))
251            }
252        },
253        SqlDataType::JSON => Ok(ConcreteDataType::json_datatype()),
254        // Vector type
255        SqlDataType::Custom(name, d)
256            if name.0.as_slice().len() == 1
257                && name.0.as_slice()[0].value.to_ascii_uppercase() == VECTOR_TYPE_NAME
258                && d.len() == 1 =>
259        {
260            let dim = d[0].parse().map_err(|e| {
261                error::ParseSqlValueSnafu {
262                    msg: format!("Failed to parse vector dimension: {}", e),
263                }
264                .build()
265            })?;
266            Ok(ConcreteDataType::vector_datatype(dim))
267        }
268        _ => error::SqlTypeNotSupportedSnafu {
269            t: data_type.clone(),
270        }
271        .fail(),
272    }
273}
274
275pub fn concrete_data_type_to_sql_data_type(data_type: &ConcreteDataType) -> Result<SqlDataType> {
276    match data_type {
277        ConcreteDataType::Int64(_) => Ok(SqlDataType::BigInt(None)),
278        ConcreteDataType::UInt64(_) => Ok(SqlDataType::UnsignedBigInt(None)),
279        ConcreteDataType::Int32(_) => Ok(SqlDataType::Int(None)),
280        ConcreteDataType::UInt32(_) => Ok(SqlDataType::UnsignedInt(None)),
281        ConcreteDataType::Int16(_) => Ok(SqlDataType::SmallInt(None)),
282        ConcreteDataType::UInt16(_) => Ok(SqlDataType::UnsignedSmallInt(None)),
283        ConcreteDataType::Int8(_) => Ok(SqlDataType::TinyInt(None)),
284        ConcreteDataType::UInt8(_) => Ok(SqlDataType::UnsignedTinyInt(None)),
285        ConcreteDataType::String(_) => Ok(SqlDataType::String(None)),
286        ConcreteDataType::Float32(_) => Ok(SqlDataType::Float(None)),
287        ConcreteDataType::Float64(_) => Ok(SqlDataType::Double(ExactNumberInfo::None)),
288        ConcreteDataType::Boolean(_) => Ok(SqlDataType::Boolean),
289        ConcreteDataType::Date(_) => Ok(SqlDataType::Date),
290        ConcreteDataType::Timestamp(ts_type) => Ok(SqlDataType::Timestamp(
291            Some(ts_type.precision()),
292            TimezoneInfo::None,
293        )),
294        ConcreteDataType::Time(time_type) => Ok(SqlDataType::Time(
295            Some(time_type.precision()),
296            TimezoneInfo::None,
297        )),
298        ConcreteDataType::Interval(_) => Ok(SqlDataType::Interval),
299        ConcreteDataType::Binary(_) => Ok(SqlDataType::Varbinary(None)),
300        ConcreteDataType::Decimal128(d) => Ok(SqlDataType::Decimal(
301            ExactNumberInfo::PrecisionAndScale(d.precision() as u64, d.scale() as u64),
302        )),
303        ConcreteDataType::Json(_) => Ok(SqlDataType::JSON),
304        ConcreteDataType::Vector(v) => Ok(SqlDataType::Custom(
305            ObjectName(vec![Ident::new(VECTOR_TYPE_NAME)]),
306            vec![v.dim.to_string()],
307        )),
308        ConcreteDataType::Duration(_)
309        | ConcreteDataType::Null(_)
310        | ConcreteDataType::List(_)
311        | ConcreteDataType::Dictionary(_) => error::ConcreteTypeNotSupportedSnafu {
312            t: data_type.clone(),
313        }
314        .fail(),
315    }
316}
317
318#[cfg(test)]
319mod tests {
320    use std::collections::HashMap;
321
322    use api::v1::ColumnDataType;
323    use datatypes::schema::{
324        FulltextAnalyzer, COLUMN_FULLTEXT_OPT_KEY_ANALYZER, COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE,
325    };
326    use sqlparser::ast::{ColumnOptionDef, Expr};
327
328    use super::*;
329    use crate::ast::TimezoneInfo;
330    use crate::statements::create::ColumnExtensions;
331    use crate::statements::ColumnOption;
332
333    fn check_type(sql_type: SqlDataType, data_type: ConcreteDataType) {
334        assert_eq!(
335            data_type,
336            sql_data_type_to_concrete_data_type(&sql_type).unwrap()
337        );
338    }
339
340    #[test]
341    pub fn test_sql_data_type_to_concrete_data_type() {
342        check_type(
343            SqlDataType::BigInt(None),
344            ConcreteDataType::int64_datatype(),
345        );
346        check_type(SqlDataType::Int(None), ConcreteDataType::int32_datatype());
347        check_type(
348            SqlDataType::Integer(None),
349            ConcreteDataType::int32_datatype(),
350        );
351        check_type(
352            SqlDataType::SmallInt(None),
353            ConcreteDataType::int16_datatype(),
354        );
355        check_type(SqlDataType::Char(None), ConcreteDataType::string_datatype());
356        check_type(
357            SqlDataType::Varchar(None),
358            ConcreteDataType::string_datatype(),
359        );
360        check_type(SqlDataType::Text, ConcreteDataType::string_datatype());
361        check_type(
362            SqlDataType::String(None),
363            ConcreteDataType::string_datatype(),
364        );
365        check_type(
366            SqlDataType::Float(None),
367            ConcreteDataType::float32_datatype(),
368        );
369        check_type(
370            SqlDataType::Double(ExactNumberInfo::None),
371            ConcreteDataType::float64_datatype(),
372        );
373        check_type(SqlDataType::Boolean, ConcreteDataType::boolean_datatype());
374        check_type(SqlDataType::Date, ConcreteDataType::date_datatype());
375        check_type(
376            SqlDataType::Timestamp(None, TimezoneInfo::None),
377            ConcreteDataType::timestamp_millisecond_datatype(),
378        );
379        check_type(
380            SqlDataType::Varbinary(None),
381            ConcreteDataType::binary_datatype(),
382        );
383        check_type(
384            SqlDataType::UnsignedBigInt(None),
385            ConcreteDataType::uint64_datatype(),
386        );
387        check_type(
388            SqlDataType::UnsignedInt(None),
389            ConcreteDataType::uint32_datatype(),
390        );
391        check_type(
392            SqlDataType::UnsignedSmallInt(None),
393            ConcreteDataType::uint16_datatype(),
394        );
395        check_type(
396            SqlDataType::UnsignedTinyInt(None),
397            ConcreteDataType::uint8_datatype(),
398        );
399        check_type(
400            SqlDataType::Datetime(None),
401            ConcreteDataType::timestamp_microsecond_datatype(),
402        );
403        check_type(
404            SqlDataType::Interval,
405            ConcreteDataType::interval_month_day_nano_datatype(),
406        );
407        check_type(SqlDataType::JSON, ConcreteDataType::json_datatype());
408        check_type(
409            SqlDataType::Custom(
410                ObjectName(vec![Ident::new(VECTOR_TYPE_NAME)]),
411                vec!["3".to_string()],
412            ),
413            ConcreteDataType::vector_datatype(3),
414        );
415    }
416
417    #[test]
418    pub fn test_sql_column_def_to_grpc_column_def() {
419        // test basic
420        let column_def = ColumnDef {
421            name: "col".into(),
422            data_type: SqlDataType::Double(ExactNumberInfo::None),
423            collation: None,
424            options: vec![],
425        };
426
427        let grpc_column_def = sql_column_def_to_grpc_column_def(&column_def, None).unwrap();
428
429        assert_eq!("col", grpc_column_def.name);
430        assert!(grpc_column_def.is_nullable); // nullable when options are empty
431        assert_eq!(ColumnDataType::Float64 as i32, grpc_column_def.data_type);
432        assert!(grpc_column_def.default_constraint.is_empty());
433        assert_eq!(grpc_column_def.semantic_type, SemanticType::Field as i32);
434
435        // test not null
436        let column_def = ColumnDef {
437            name: "col".into(),
438            data_type: SqlDataType::Double(ExactNumberInfo::None),
439            collation: None,
440            options: vec![ColumnOptionDef {
441                name: None,
442                option: ColumnOption::NotNull,
443            }],
444        };
445
446        let grpc_column_def = sql_column_def_to_grpc_column_def(&column_def, None).unwrap();
447        assert!(!grpc_column_def.is_nullable);
448
449        // test primary key
450        let column_def = ColumnDef {
451            name: "col".into(),
452            data_type: SqlDataType::Double(ExactNumberInfo::None),
453            collation: None,
454            options: vec![ColumnOptionDef {
455                name: None,
456                option: ColumnOption::Unique {
457                    is_primary: true,
458                    characteristics: None,
459                },
460            }],
461        };
462
463        let grpc_column_def = sql_column_def_to_grpc_column_def(&column_def, None).unwrap();
464        assert_eq!(grpc_column_def.semantic_type, SemanticType::Tag as i32);
465    }
466
467    #[test]
468    pub fn test_sql_column_def_to_grpc_column_def_with_timezone() {
469        let column_def = ColumnDef {
470            name: "col".into(),
471            // MILLISECOND
472            data_type: SqlDataType::Timestamp(Some(3), TimezoneInfo::None),
473            collation: None,
474            options: vec![ColumnOptionDef {
475                name: None,
476                option: ColumnOption::Default(Expr::Value(SqlValue::SingleQuotedString(
477                    "2024-01-30T00:01:01".to_string(),
478                ))),
479            }],
480        };
481
482        // with timezone "Asia/Shanghai"
483        let grpc_column_def = sql_column_def_to_grpc_column_def(
484            &column_def,
485            Some(&Timezone::from_tz_string("Asia/Shanghai").unwrap()),
486        )
487        .unwrap();
488        assert_eq!("col", grpc_column_def.name);
489        assert!(grpc_column_def.is_nullable); // nullable when options are empty
490        assert_eq!(
491            ColumnDataType::TimestampMillisecond as i32,
492            grpc_column_def.data_type
493        );
494        assert!(!grpc_column_def.default_constraint.is_empty());
495
496        let constraint =
497            ColumnDefaultConstraint::try_from(&grpc_column_def.default_constraint[..]).unwrap();
498        assert!(
499            matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
500                         if ts.to_iso8601_string() == "2024-01-29 16:01:01+0000")
501        );
502
503        // without timezone
504        let grpc_column_def = sql_column_def_to_grpc_column_def(&column_def, None).unwrap();
505        assert_eq!("col", grpc_column_def.name);
506        assert!(grpc_column_def.is_nullable); // nullable when options are empty
507        assert_eq!(
508            ColumnDataType::TimestampMillisecond as i32,
509            grpc_column_def.data_type
510        );
511        assert!(!grpc_column_def.default_constraint.is_empty());
512
513        let constraint =
514            ColumnDefaultConstraint::try_from(&grpc_column_def.default_constraint[..]).unwrap();
515        assert!(
516            matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
517                         if ts.to_iso8601_string() == "2024-01-30 00:01:01+0000")
518        );
519    }
520
521    #[test]
522    pub fn test_has_primary_key_option() {
523        let column_def = ColumnDef {
524            name: "col".into(),
525            data_type: SqlDataType::Double(ExactNumberInfo::None),
526            collation: None,
527            options: vec![],
528        };
529        assert!(!has_primary_key_option(&column_def));
530
531        let column_def = ColumnDef {
532            name: "col".into(),
533            data_type: SqlDataType::Double(ExactNumberInfo::None),
534            collation: None,
535            options: vec![ColumnOptionDef {
536                name: None,
537                option: ColumnOption::Unique {
538                    is_primary: true,
539                    characteristics: None,
540                },
541            }],
542        };
543        assert!(has_primary_key_option(&column_def));
544    }
545
546    #[test]
547    pub fn test_column_to_schema() {
548        let column_def = Column {
549            column_def: ColumnDef {
550                name: "col".into(),
551                data_type: SqlDataType::Double(ExactNumberInfo::None),
552                collation: None,
553                options: vec![],
554            },
555            extensions: ColumnExtensions::default(),
556        };
557
558        let column_schema = column_to_schema(&column_def, "ts", None).unwrap();
559
560        assert_eq!("col", column_schema.name);
561        assert_eq!(
562            ConcreteDataType::float64_datatype(),
563            column_schema.data_type
564        );
565        assert!(column_schema.is_nullable());
566        assert!(!column_schema.is_time_index());
567
568        let column_schema = column_to_schema(&column_def, "col", None).unwrap();
569
570        assert_eq!("col", column_schema.name);
571        assert_eq!(
572            ConcreteDataType::float64_datatype(),
573            column_schema.data_type
574        );
575        assert!(!column_schema.is_nullable());
576        assert!(column_schema.is_time_index());
577
578        let column_def = Column {
579            column_def: ColumnDef {
580                name: "col2".into(),
581                data_type: SqlDataType::String(None),
582                collation: None,
583                options: vec![
584                    ColumnOptionDef {
585                        name: None,
586                        option: ColumnOption::NotNull,
587                    },
588                    ColumnOptionDef {
589                        name: None,
590                        option: ColumnOption::Comment("test comment".to_string()),
591                    },
592                ],
593            },
594            extensions: ColumnExtensions::default(),
595        };
596
597        let column_schema = column_to_schema(&column_def, "ts", None).unwrap();
598
599        assert_eq!("col2", column_schema.name);
600        assert_eq!(ConcreteDataType::string_datatype(), column_schema.data_type);
601        assert!(!column_schema.is_nullable());
602        assert!(!column_schema.is_time_index());
603        assert_eq!(
604            column_schema.metadata().get(COMMENT_KEY),
605            Some(&"test comment".to_string())
606        );
607    }
608
609    #[test]
610    pub fn test_column_to_schema_timestamp_with_timezone() {
611        let column = Column {
612            column_def: ColumnDef {
613                name: "col".into(),
614                // MILLISECOND
615                data_type: SqlDataType::Timestamp(Some(3), TimezoneInfo::None),
616                collation: None,
617                options: vec![ColumnOptionDef {
618                    name: None,
619                    option: ColumnOption::Default(Expr::Value(SqlValue::SingleQuotedString(
620                        "2024-01-30T00:01:01".to_string(),
621                    ))),
622                }],
623            },
624            extensions: ColumnExtensions::default(),
625        };
626
627        // with timezone "Asia/Shanghai"
628
629        let column_schema = column_to_schema(
630            &column,
631            "ts",
632            Some(&Timezone::from_tz_string("Asia/Shanghai").unwrap()),
633        )
634        .unwrap();
635
636        assert_eq!("col", column_schema.name);
637        assert_eq!(
638            ConcreteDataType::timestamp_millisecond_datatype(),
639            column_schema.data_type
640        );
641        assert!(column_schema.is_nullable());
642
643        let constraint = column_schema.default_constraint().unwrap();
644        assert!(
645            matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
646                         if ts.to_iso8601_string() == "2024-01-29 16:01:01+0000")
647        );
648
649        // without timezone
650        let column_schema = column_to_schema(&column, "ts", None).unwrap();
651
652        assert_eq!("col", column_schema.name);
653        assert_eq!(
654            ConcreteDataType::timestamp_millisecond_datatype(),
655            column_schema.data_type
656        );
657        assert!(column_schema.is_nullable());
658
659        let constraint = column_schema.default_constraint().unwrap();
660        assert!(
661            matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
662                         if ts.to_iso8601_string() == "2024-01-30 00:01:01+0000")
663        );
664    }
665
666    #[test]
667    fn test_column_to_schema_with_fulltext() {
668        let column = Column {
669            column_def: ColumnDef {
670                name: "col".into(),
671                data_type: SqlDataType::Text,
672                collation: None,
673                options: vec![],
674            },
675            extensions: ColumnExtensions {
676                fulltext_index_options: Some(
677                    HashMap::from_iter([
678                        (
679                            COLUMN_FULLTEXT_OPT_KEY_ANALYZER.to_string(),
680                            "English".to_string(),
681                        ),
682                        (
683                            COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE.to_string(),
684                            "true".to_string(),
685                        ),
686                    ])
687                    .into(),
688                ),
689                vector_options: None,
690                skipping_index_options: None,
691                inverted_index_options: None,
692            },
693        };
694
695        let column_schema = column_to_schema(&column, "ts", None).unwrap();
696        assert_eq!("col", column_schema.name);
697        assert_eq!(ConcreteDataType::string_datatype(), column_schema.data_type);
698        let fulltext_options = column_schema.fulltext_options().unwrap().unwrap();
699        assert_eq!(fulltext_options.analyzer, FulltextAnalyzer::English);
700        assert!(fulltext_options.case_sensitive);
701    }
702}