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