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