Skip to main content

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