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