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