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