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