1use std::collections::HashMap;
16use std::fmt::{Display, Formatter};
17
18use common_catalog::consts::FILE_ENGINE;
19use datatypes::json::{JSON2_DEFAULT_MAX_AUTO_EXPANDED_PATHS, JsonSettings};
20use datatypes::prelude::ConcreteDataType;
21use datatypes::schema::{
22 FulltextOptions, SkippingIndexOptions, VectorDistanceMetric, VectorIndexEngineType,
23 VectorIndexOptions,
24};
25use itertools::Itertools;
26use serde::Serialize;
27use snafu::ResultExt;
28use sqlparser::ast::{ColumnOptionDef, DataType, Expr};
29use sqlparser_derive::{Visit, VisitMut};
30
31use crate::ast::{ColumnDef, Ident, ObjectName, Value as SqlValue};
32use crate::error::{
33 InvalidFlowQuerySnafu, InvalidSqlSnafu, Result, SetFulltextOptionSnafu,
34 SetSkippingIndexOptionSnafu,
35};
36use crate::statements::query::Query as GtQuery;
37use crate::statements::statement::Statement;
38use crate::statements::tql::Tql;
39use crate::statements::{OptionMap, sql_data_type_to_concrete_data_type};
40
41const LINE_SEP: &str = ",\n";
42const COMMA_SEP: &str = ", ";
43const INDENT: usize = 2;
44pub const VECTOR_OPT_DIM: &str = "dim";
45
46macro_rules! format_indent {
47 ($fmt: expr, $arg: expr) => {
48 format!($fmt, format_args!("{: >1$}", "", INDENT), $arg)
49 };
50 ($arg: expr) => {
51 format_indent!("{}{}", $arg)
52 };
53}
54
55macro_rules! format_list_indent {
56 ($list: expr) => {
57 $list.iter().map(|e| format_indent!(e)).join(LINE_SEP)
58 };
59}
60
61macro_rules! format_list_comma {
62 ($list: expr) => {
63 $list.iter().map(|e| format!("{}", e)).join(COMMA_SEP)
64 };
65}
66
67#[cfg(feature = "enterprise")]
68pub mod trigger;
69
70fn format_table_constraint(constraints: &[TableConstraint]) -> String {
71 constraints.iter().map(|c| format_indent!(c)).join(LINE_SEP)
72}
73
74#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
76pub enum TableConstraint {
77 PrimaryKey { columns: Vec<Ident> },
79 TimeIndex { column: Ident },
81}
82
83impl Display for TableConstraint {
84 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
85 match self {
86 TableConstraint::PrimaryKey { columns } => {
87 write!(f, "PRIMARY KEY ({})", format_list_comma!(columns))
88 }
89 TableConstraint::TimeIndex { column } => {
90 write!(f, "TIME INDEX ({})", column)
91 }
92 }
93 }
94}
95
96#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
97pub struct CreateTable {
98 pub if_not_exists: bool,
100 pub table_id: u32,
101 pub name: ObjectName,
103 pub columns: Vec<Column>,
104 pub engine: String,
105 pub constraints: Vec<TableConstraint>,
106 pub options: OptionMap,
108 pub partitions: Option<Partitions>,
109}
110
111#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
113pub struct Column {
114 pub column_def: ColumnDef,
116 pub extensions: ColumnExtensions,
118}
119
120#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Default, Serialize)]
122pub struct ColumnExtensions {
123 pub vector_options: Option<OptionMap>,
125
126 pub fulltext_index_options: Option<OptionMap>,
128 pub skipping_index_options: Option<OptionMap>,
130 pub inverted_index_options: Option<OptionMap>,
134 pub vector_index_options: Option<OptionMap>,
136 pub json2_options: Option<Json2Options>,
138}
139
140#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Default, Serialize)]
142pub struct Json2Options {
143 pub(crate) max_auto_expanded_paths: Option<u32>,
145 pub(crate) type_hints: Vec<JsonTypeHint>,
147}
148
149impl Display for Json2Options {
150 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
151 let mut options = Vec::with_capacity(self.type_hints.len() + 1);
152 if let Some(max) = self.max_auto_expanded_paths {
153 options.push(format!("max_auto_expanded_paths = {max}"));
154 }
155 options.extend(self.type_hints.iter().map(format_json_type_hint));
156 write!(f, "(\n {}\n )", options.iter().join(",\n "))
157 }
158}
159
160impl Json2Options {
161 pub fn build_json_settings(&self) -> Result<JsonSettings> {
162 let type_hints = self
163 .type_hints
164 .iter()
165 .map(|hint| {
166 Ok(datatypes::json::JsonTypeHint {
167 path: hint.path.clone(),
168 data_type: json_type_hint_concrete_data_type(&hint.data_type)?,
169 inverted_index: hint.inverted_index,
170 })
171 })
172 .collect::<Result<Vec<_>>>()?;
173 let max_auto_expanded_paths = self
174 .max_auto_expanded_paths
175 .or(Some(JSON2_DEFAULT_MAX_AUTO_EXPANDED_PATHS));
176 JsonSettings::try_new(type_hints, max_auto_expanded_paths).map_err(Into::into)
177 }
178}
179
180#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
181pub struct JsonTypeHint {
182 pub path: Vec<String>,
183 pub data_type: DataType,
184 pub inverted_index: bool,
185}
186
187impl Column {
188 pub fn name(&self) -> &Ident {
189 &self.column_def.name
190 }
191
192 pub fn data_type(&self) -> &DataType {
193 &self.column_def.data_type
194 }
195
196 pub fn mut_data_type(&mut self) -> &mut DataType {
197 &mut self.column_def.data_type
198 }
199
200 pub fn options(&self) -> &[ColumnOptionDef] {
201 &self.column_def.options
202 }
203
204 pub fn mut_options(&mut self) -> &mut Vec<ColumnOptionDef> {
205 &mut self.column_def.options
206 }
207}
208
209impl Display for Column {
210 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
211 if let Some(vector_options) = &self.extensions.vector_options
212 && let Some(dim) = vector_options.get(VECTOR_OPT_DIM)
213 {
214 write!(f, "{} VECTOR({})", self.column_def.name, dim)?;
215 return Ok(());
216 }
217
218 write!(f, "{} {}", self.column_def.name, self.column_def.data_type)?;
219 if let Some(options) = &self.extensions.json2_options {
220 write!(f, "{options}")?;
221 }
222 for option in &self.column_def.options {
223 write!(f, " {option}")?;
224 }
225
226 if let Some(fulltext_options) = &self.extensions.fulltext_index_options {
227 if !fulltext_options.is_empty() {
228 let options = fulltext_options.kv_pairs();
229 write!(f, " FULLTEXT INDEX WITH({})", format_list_comma!(options))?;
230 } else {
231 write!(f, " FULLTEXT INDEX")?;
232 }
233 }
234
235 if let Some(skipping_index_options) = &self.extensions.skipping_index_options {
236 if !skipping_index_options.is_empty() {
237 let options = skipping_index_options.kv_pairs();
238 write!(f, " SKIPPING INDEX WITH({})", format_list_comma!(options))?;
239 } else {
240 write!(f, " SKIPPING INDEX")?;
241 }
242 }
243
244 if let Some(inverted_index_options) = &self.extensions.inverted_index_options {
245 if !inverted_index_options.is_empty() {
246 let options = inverted_index_options.kv_pairs();
247 write!(f, " INVERTED INDEX WITH({})", format_list_comma!(options))?;
248 } else {
249 write!(f, " INVERTED INDEX")?;
250 }
251 }
252
253 if let Some(vector_index_options) = &self.extensions.vector_index_options {
254 if !vector_index_options.is_empty() {
255 let options = vector_index_options.kv_pairs();
256 write!(f, " VECTOR INDEX WITH({})", format_list_comma!(options))?;
257 } else {
258 write!(f, " VECTOR INDEX")?;
259 }
260 }
261 Ok(())
262 }
263}
264
265impl ColumnExtensions {
266 pub fn build_fulltext_options(&self) -> Result<Option<FulltextOptions>> {
267 let Some(options) = self.fulltext_index_options.as_ref() else {
268 return Ok(None);
269 };
270
271 let options: HashMap<String, String> = options.clone().into_map();
272 Ok(Some(options.try_into().context(SetFulltextOptionSnafu)?))
273 }
274
275 pub fn build_skipping_index_options(&self) -> Result<Option<SkippingIndexOptions>> {
276 let Some(options) = self.skipping_index_options.as_ref() else {
277 return Ok(None);
278 };
279
280 let options: HashMap<String, String> = options.clone().into_map();
281 Ok(Some(
282 options.try_into().context(SetSkippingIndexOptionSnafu)?,
283 ))
284 }
285
286 pub fn build_vector_index_options(&self) -> Result<Option<VectorIndexOptions>> {
287 let Some(options) = self.vector_index_options.as_ref() else {
288 return Ok(None);
289 };
290
291 let options_map: HashMap<String, String> = options.clone().into_map();
292 let mut result = VectorIndexOptions::default();
293
294 if let Some(s) = options_map.get("engine") {
295 result.engine = s.parse::<VectorIndexEngineType>().map_err(|e| {
296 InvalidSqlSnafu {
297 msg: format!("invalid VECTOR INDEX engine: {e}"),
298 }
299 .build()
300 })?;
301 }
302
303 if let Some(s) = options_map.get("metric") {
304 result.metric = s.parse::<VectorDistanceMetric>().map_err(|e| {
305 InvalidSqlSnafu {
306 msg: format!("invalid VECTOR INDEX metric: {e}"),
307 }
308 .build()
309 })?;
310 }
311
312 if let Some(s) = options_map.get("connectivity") {
313 let value = s.parse::<u32>().map_err(|_| {
314 InvalidSqlSnafu {
315 msg: format!(
316 "invalid VECTOR INDEX connectivity: {s}, expected positive integer"
317 ),
318 }
319 .build()
320 })?;
321 if !(2..=2048).contains(&value) {
322 return InvalidSqlSnafu {
323 msg: "VECTOR INDEX connectivity must be in the range [2, 2048].".to_string(),
324 }
325 .fail();
326 }
327 result.connectivity = value;
328 }
329
330 if let Some(s) = options_map.get("expansion_add") {
331 let value = s.parse::<u32>().map_err(|_| {
332 InvalidSqlSnafu {
333 msg: format!(
334 "invalid VECTOR INDEX expansion_add: {s}, expected positive integer"
335 ),
336 }
337 .build()
338 })?;
339 if value == 0 {
340 return InvalidSqlSnafu {
341 msg: "VECTOR INDEX expansion_add must be greater than 0".to_string(),
342 }
343 .fail();
344 }
345 result.expansion_add = value;
346 }
347
348 if let Some(s) = options_map.get("expansion_search") {
349 let value = s.parse::<u32>().map_err(|_| {
350 InvalidSqlSnafu {
351 msg: format!(
352 "invalid VECTOR INDEX expansion_search: {s}, expected positive integer"
353 ),
354 }
355 .build()
356 })?;
357 if value == 0 {
358 return InvalidSqlSnafu {
359 msg: "VECTOR INDEX expansion_search must be greater than 0".to_string(),
360 }
361 .fail();
362 }
363 result.expansion_search = value;
364 }
365
366 Ok(Some(result))
367 }
368
369 pub fn build_json_settings(&self) -> Result<Option<JsonSettings>> {
370 let Some(options) = &self.json2_options else {
371 return Ok(None);
372 };
373
374 options.build_json_settings().map(Some)
375 }
376
377 pub fn set_json_settings(&mut self, settings: JsonSettings) -> Result<()> {
378 let (type_hints, max_auto_expanded_paths) = settings.into_parts();
379 let type_hints = type_hints
380 .into_iter()
381 .map(|hint| {
382 let data_type = json_type_hint_sql_data_type(&hint.data_type)?;
383 Ok(JsonTypeHint {
384 path: hint.path,
385 data_type,
386 inverted_index: hint.inverted_index,
387 })
388 })
389 .collect::<Result<Vec<_>>>()?;
390 self.json2_options = (max_auto_expanded_paths.is_some() || !type_hints.is_empty())
391 .then_some(Json2Options {
392 max_auto_expanded_paths,
393 type_hints,
394 });
395 Ok(())
396 }
397}
398
399fn json_type_hint_concrete_data_type(data_type: &DataType) -> Result<ConcreteDataType> {
400 let data_type = sql_data_type_to_concrete_data_type(data_type)?;
401 normalize_json_type_hint_concrete_data_type(&data_type)
402}
403
404fn normalize_json_type_hint_concrete_data_type(
405 data_type: &ConcreteDataType,
406) -> Result<ConcreteDataType> {
407 let normalized = match data_type {
408 ConcreteDataType::String(_) => ConcreteDataType::string_datatype(),
409 ConcreteDataType::Int8(_)
410 | ConcreteDataType::Int16(_)
411 | ConcreteDataType::Int32(_)
412 | ConcreteDataType::Int64(_) => ConcreteDataType::int64_datatype(),
413 ConcreteDataType::UInt8(_)
414 | ConcreteDataType::UInt16(_)
415 | ConcreteDataType::UInt32(_)
416 | ConcreteDataType::UInt64(_) => ConcreteDataType::uint64_datatype(),
417 ConcreteDataType::Float32(_) | ConcreteDataType::Float64(_) => {
418 ConcreteDataType::float64_datatype()
419 }
420 ConcreteDataType::Boolean(_) => ConcreteDataType::boolean_datatype(),
421 _ => {
422 return InvalidSqlSnafu {
423 msg: format!("unsupported JSON2 type hint data type: {data_type}"),
424 }
425 .fail();
426 }
427 };
428 Ok(normalized)
429}
430
431fn json_type_hint_sql_data_type(data_type: &ConcreteDataType) -> Result<DataType> {
432 let data_type = normalize_json_type_hint_concrete_data_type(data_type)?;
433 let sql_type = match data_type {
434 ConcreteDataType::String(_) => DataType::String(None),
435 ConcreteDataType::Int64(_) => DataType::BigInt(None),
436 ConcreteDataType::UInt64(_) => DataType::BigIntUnsigned(None),
437 ConcreteDataType::Float64(_) => DataType::Double(sqlparser::ast::ExactNumberInfo::None),
438 ConcreteDataType::Boolean(_) => DataType::Boolean,
439 _ => unreachable!("JSON2 type hint data type should have been normalized"),
440 };
441 Ok(sql_type)
442}
443
444fn format_json_type_hint(hint: &JsonTypeHint) -> String {
445 let path = hint
446 .path
447 .iter()
448 .map(|segment| format_json_path_segment(segment))
449 .join(".");
450 let inverted_index = if hint.inverted_index {
451 " INVERTED INDEX"
452 } else {
453 ""
454 };
455 format!("{} {}{}", path, hint.data_type, inverted_index)
456}
457
458fn format_json_path_segment(segment: &str) -> String {
459 format!("\"{}\"", segment.replace('"', "\"\""))
460}
461
462#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
469pub struct Partitions {
470 pub column_list: Vec<Ident>,
471 pub exprs: Vec<Expr>,
472}
473
474impl Partitions {
475 pub fn set_quote(&mut self, quote_style: char) {
477 self.column_list
478 .iter_mut()
479 .for_each(|c| c.quote_style = Some(quote_style));
480 }
481}
482
483#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut)]
484pub struct PartitionEntry {
485 pub name: Ident,
486 pub value_list: Vec<SqlValue>,
487}
488
489impl Display for PartitionEntry {
490 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
491 write!(
492 f,
493 "PARTITION {} VALUES LESS THAN ({})",
494 self.name,
495 format_list_comma!(self.value_list),
496 )
497 }
498}
499
500impl Display for Partitions {
501 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
502 if !self.column_list.is_empty() {
503 write!(
504 f,
505 "PARTITION ON COLUMNS ({}) (\n{}\n)",
506 format_list_comma!(self.column_list),
507 format_list_indent!(self.exprs),
508 )?;
509 }
510 Ok(())
511 }
512}
513
514impl Display for CreateTable {
515 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
516 write!(f, "CREATE ")?;
517 if self.engine == FILE_ENGINE {
518 write!(f, "EXTERNAL ")?;
519 }
520 write!(f, "TABLE ")?;
521 if self.if_not_exists {
522 write!(f, "IF NOT EXISTS ")?;
523 }
524 writeln!(f, "{} (", &self.name)?;
525 writeln!(f, "{},", format_list_indent!(self.columns))?;
526 writeln!(f, "{}", format_table_constraint(&self.constraints))?;
527 writeln!(f, ")")?;
528 if let Some(partitions) = &self.partitions {
529 writeln!(f, "{partitions}")?;
530 }
531 writeln!(f, "ENGINE={}", &self.engine)?;
532 if !self.options.is_empty() {
533 let options = self.options.kv_pairs();
534 write!(f, "WITH(\n{}\n)", format_list_indent!(options))?;
535 }
536 Ok(())
537 }
538}
539
540#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
541pub struct CreateDatabase {
542 pub name: ObjectName,
543 pub if_not_exists: bool,
545 pub options: OptionMap,
546}
547
548impl CreateDatabase {
549 pub fn new(name: ObjectName, if_not_exists: bool, options: OptionMap) -> Self {
551 Self {
552 name,
553 if_not_exists,
554 options,
555 }
556 }
557}
558
559impl Display for CreateDatabase {
560 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
561 write!(f, "CREATE DATABASE ")?;
562 if self.if_not_exists {
563 write!(f, "IF NOT EXISTS ")?;
564 }
565 write!(f, "{}", &self.name)?;
566 if !self.options.is_empty() {
567 let options = self.options.kv_pairs();
568 write!(f, "\nWITH(\n{}\n)", format_list_indent!(options))?;
569 }
570 Ok(())
571 }
572}
573
574#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
575pub struct CreateExternalTable {
576 pub name: ObjectName,
578 pub columns: Vec<Column>,
579 pub constraints: Vec<TableConstraint>,
580 pub options: OptionMap,
582 pub if_not_exists: bool,
583 pub engine: String,
584}
585
586impl Display for CreateExternalTable {
587 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
588 write!(f, "CREATE EXTERNAL TABLE ")?;
589 if self.if_not_exists {
590 write!(f, "IF NOT EXISTS ")?;
591 }
592 writeln!(f, "{} (", &self.name)?;
593 writeln!(f, "{},", format_list_indent!(self.columns))?;
594 writeln!(f, "{}", format_table_constraint(&self.constraints))?;
595 writeln!(f, ")")?;
596 writeln!(f, "ENGINE={}", &self.engine)?;
597 if !self.options.is_empty() {
598 let options = self.options.kv_pairs();
599 write!(f, "WITH(\n{}\n)", format_list_indent!(options))?;
600 }
601 Ok(())
602 }
603}
604
605#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
606pub struct CreateTableLike {
607 pub table_name: ObjectName,
609 pub source_name: ObjectName,
611}
612
613impl Display for CreateTableLike {
614 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
615 let table_name = &self.table_name;
616 let source_name = &self.source_name;
617 write!(f, r#"CREATE TABLE {table_name} LIKE {source_name}"#)
618 }
619}
620
621#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
622pub struct CreateFlow {
623 pub flow_name: ObjectName,
625 pub sink_table_name: ObjectName,
627 pub or_replace: bool,
629 pub if_not_exists: bool,
631 pub expire_after: Option<i64>,
634 pub eval_interval: Option<i64>,
638 pub eval_offset: Option<i64>,
644 pub comment: Option<String>,
646 pub flow_options: OptionMap,
648 pub query: Box<SqlOrTql>,
650}
651
652#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
654pub enum SqlOrTql {
655 Sql(GtQuery, String),
656 Tql(Tql, String),
657}
658
659impl std::fmt::Display for SqlOrTql {
660 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
661 match self {
662 Self::Sql(_, s) => write!(f, "{}", s),
663 Self::Tql(_, s) => write!(f, "{}", s),
664 }
665 }
666}
667
668impl SqlOrTql {
669 pub fn try_from_statement(
670 value: Statement,
671 original_query: &str,
672 ) -> std::result::Result<Self, crate::error::Error> {
673 match value {
674 Statement::Query(query) => Ok(Self::Sql(*query, original_query.to_string())),
675 Statement::Tql(tql) => Ok(Self::Tql(tql, original_query.to_string())),
676 _ => InvalidFlowQuerySnafu {
677 reason: format!("Expect either sql query or promql query, found {:?}", value),
678 }
679 .fail(),
680 }
681 }
682}
683
684impl Display for CreateFlow {
685 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
686 write!(f, "CREATE ")?;
687 if self.or_replace {
688 write!(f, "OR REPLACE ")?;
689 }
690 write!(f, "FLOW ")?;
691 if self.if_not_exists {
692 write!(f, "IF NOT EXISTS ")?;
693 }
694 writeln!(f, "{}", &self.flow_name)?;
695 writeln!(f, "SINK TO {}", &self.sink_table_name)?;
696 if let Some(expire_after) = &self.expire_after {
697 writeln!(f, "EXPIRE AFTER '{} s'", expire_after)?;
698 }
699 if let Some(eval_interval) = &self.eval_interval {
700 writeln!(f, "EVAL INTERVAL '{} s'", eval_interval)?;
701 }
702 if let Some(eval_offset) = &self.eval_offset
705 && *eval_offset != 0
706 {
707 writeln!(f, "EVAL OFFSET '{} s'", eval_offset)?;
708 }
709 if let Some(comment) = &self.comment {
710 writeln!(f, "COMMENT '{}'", comment)?;
711 }
712 if !self.flow_options.is_empty() {
713 let options = self.flow_options.kv_pairs();
714 writeln!(f, "WITH ({})", format_list_comma!(options))?;
715 }
716 write!(f, "AS {}", &self.query)
717 }
718}
719
720#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
722pub struct CreateView {
723 pub name: ObjectName,
725 pub columns: Vec<Ident>,
727 pub query: Box<Statement>,
730 pub or_replace: bool,
732 pub if_not_exists: bool,
734}
735
736impl Display for CreateView {
737 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
738 write!(f, "CREATE ")?;
739 if self.or_replace {
740 write!(f, "OR REPLACE ")?;
741 }
742 write!(f, "VIEW ")?;
743 if self.if_not_exists {
744 write!(f, "IF NOT EXISTS ")?;
745 }
746 write!(f, "{} ", &self.name)?;
747 if !self.columns.is_empty() {
748 write!(f, "({}) ", format_list_comma!(self.columns))?;
749 }
750 write!(f, "AS {}", &self.query)
751 }
752}
753
754#[cfg(test)]
755mod tests {
756 use std::assert_matches;
757
758 use datatypes::json::{JsonSettings, JsonTypeHint as DatatypeJsonTypeHint};
759 use datatypes::prelude::ConcreteDataType;
760
761 use super::*;
762 use crate::dialect::GreptimeDbDialect;
763 use crate::error::Error;
764 use crate::parser::{ParseOptions, ParserContext};
765 use crate::statements::statement::Statement;
766
767 #[test]
768 fn test_display_create_table() {
769 let sql = r"create table if not exists demo(
770 host string,
771 ts timestamp,
772 cpu double default 0,
773 memory double,
774 TIME INDEX (ts),
775 PRIMARY KEY(host)
776 )
777 PARTITION ON COLUMNS (host) (
778 host = 'a',
779 host > 'a',
780 )
781 engine=mito
782 with(ttl='7d', storage='File');
783 ";
784 let result =
785 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
786 .unwrap();
787 assert_eq!(1, result.len());
788
789 match &result[0] {
790 Statement::CreateTable(c) => {
791 let new_sql = format!("\n{}", c);
792 assert_eq!(
793 r#"
794CREATE TABLE IF NOT EXISTS demo (
795 host STRING,
796 ts TIMESTAMP,
797 cpu DOUBLE DEFAULT 0,
798 memory DOUBLE,
799 TIME INDEX (ts),
800 PRIMARY KEY (host)
801)
802PARTITION ON COLUMNS (host) (
803 host = 'a',
804 host > 'a'
805)
806ENGINE=mito
807WITH(
808 storage = 'File',
809 ttl = '7d'
810)"#,
811 &new_sql
812 );
813
814 let new_result = ParserContext::create_with_dialect(
815 &new_sql,
816 &GreptimeDbDialect {},
817 ParseOptions::default(),
818 )
819 .unwrap();
820 assert_eq!(result, new_result);
821 }
822 _ => unreachable!(),
823 }
824 }
825
826 #[test]
827 fn test_display_empty_partition_column() {
828 let sql = r"create table if not exists demo(
829 host string,
830 ts timestamp,
831 cpu double default 0,
832 memory double,
833 TIME INDEX (ts),
834 PRIMARY KEY(ts, host)
835 );
836 ";
837 let result =
838 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
839 .unwrap();
840 assert_eq!(1, result.len());
841
842 match &result[0] {
843 Statement::CreateTable(c) => {
844 let new_sql = format!("\n{}", c);
845 assert_eq!(
846 r#"
847CREATE TABLE IF NOT EXISTS demo (
848 host STRING,
849 ts TIMESTAMP,
850 cpu DOUBLE DEFAULT 0,
851 memory DOUBLE,
852 TIME INDEX (ts),
853 PRIMARY KEY (ts, host)
854)
855ENGINE=mito
856"#,
857 &new_sql
858 );
859
860 let new_result = ParserContext::create_with_dialect(
861 &new_sql,
862 &GreptimeDbDialect {},
863 ParseOptions::default(),
864 )
865 .unwrap();
866 assert_eq!(result, new_result);
867 }
868 _ => unreachable!(),
869 }
870 }
871
872 #[test]
873 fn test_validate_table_options() {
874 let sql = r"create table if not exists demo(
875 host string,
876 ts timestamp,
877 cpu double default 0,
878 memory double,
879 TIME INDEX (ts),
880 PRIMARY KEY(host)
881 )
882 PARTITION ON COLUMNS (host) ()
883 engine=mito
884 with(ttl='7d', 'compaction.type'='world');
885";
886 let result =
887 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
888 .unwrap();
889 match &result[0] {
890 Statement::CreateTable(c) => {
891 assert_eq!(2, c.options.len());
892 }
893 _ => unreachable!(),
894 }
895
896 let sql = r"create table if not exists demo(
897 host string,
898 ts timestamp,
899 cpu double default 0,
900 memory double,
901 TIME INDEX (ts),
902 PRIMARY KEY(host)
903 )
904 PARTITION ON COLUMNS (host) ()
905 engine=mito
906 with(ttl='7d', hello='world');
907";
908 let result =
909 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
910 assert_matches!(result, Err(Error::InvalidTableOption { .. }));
911
912 let semantic = |with: &str| {
914 let sql =
915 format!("create table demo(host string, ts timestamp time index) with({with});");
916 ParserContext::create_with_dialect(&sql, &GreptimeDbDialect {}, ParseOptions::default())
917 };
918 assert!(semantic("'greptime.semantic.signal_type'='metric'").is_ok());
919 assert_matches!(
921 semantic("'greptime.semantic.signal_type'='spans'"),
922 Err(Error::InvalidTableOption { .. })
923 );
924 assert_matches!(
926 semantic("'greptime.semantic.bogus'='x'"),
927 Err(Error::InvalidTableOption { .. })
928 );
929 }
930
931 #[test]
932 fn test_display_json2_type_hints_quotes_path_segments() {
933 let sql = r#"CREATE TABLE traces (
934 log_json_data JSON2 (
935 "service.name" STRING,
936 "a.b"."c" INT64,
937 a."b.c" STRING
938 ),
939 ts TIMESTAMP TIME INDEX
940 )"#;
941 let result =
942 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
943 .unwrap();
944
945 match &result[0] {
946 Statement::CreateTable(c) => {
947 let new_sql = format!("\n{}", c);
948 assert_eq!(
949 r#"
950CREATE TABLE traces (
951 log_json_data JSON2(
952 "service.name" STRING,
953 "a.b"."c" BIGINT,
954 "a"."b.c" STRING
955 ),
956 ts TIMESTAMP NOT NULL,
957 TIME INDEX (ts)
958)
959ENGINE=mito
960"#,
961 &new_sql
962 );
963
964 let new_result = ParserContext::create_with_dialect(
965 &new_sql,
966 &GreptimeDbDialect {},
967 ParseOptions::default(),
968 )
969 .unwrap();
970 assert_eq!(result, new_result);
971 }
972 _ => unreachable!(),
973 }
974 }
975
976 #[test]
977 fn test_parse_json2_max_auto_expanded_paths_option() -> Result<()> {
978 let sql = r#"CREATE TABLE traces (
979 log_json_data JSON2 (
980 status_code INT64,
981 max_auto_expanded_paths = 1
982 ),
983 ts TIMESTAMP TIME INDEX
984 )"#;
985 let result = ParserContext::create_with_dialect(
986 sql,
987 &GreptimeDbDialect {},
988 ParseOptions::default(),
989 )?;
990 let Statement::CreateTable(create_table) = &result[0] else {
991 unreachable!()
992 };
993 let settings = create_table.columns[0]
994 .extensions
995 .build_json_settings()?
996 .unwrap();
997 assert_eq!(settings.max_auto_expanded_paths(), Some(1));
998 Ok(())
999 }
1000
1001 #[test]
1002 fn test_display_json2_type_hints_quotes_numeric_segments() {
1003 let sql = r#"CREATE TABLE traces (
1004 log_json_data JSON2 (
1005 "1abc" STRING,
1006 a."2b" INT64
1007 ),
1008 ts TIMESTAMP TIME INDEX
1009 )"#;
1010 let result =
1011 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1012 .unwrap();
1013
1014 match &result[0] {
1015 Statement::CreateTable(c) => {
1016 let new_sql = format!("\n{}", c);
1017 assert_eq!(
1018 r#"
1019CREATE TABLE traces (
1020 log_json_data JSON2(
1021 "1abc" STRING,
1022 "a"."2b" BIGINT
1023 ),
1024 ts TIMESTAMP NOT NULL,
1025 TIME INDEX (ts)
1026)
1027ENGINE=mito
1028"#,
1029 &new_sql
1030 );
1031
1032 let new_result = ParserContext::create_with_dialect(
1033 &new_sql,
1034 &GreptimeDbDialect {},
1035 ParseOptions::default(),
1036 )
1037 .unwrap();
1038 assert_eq!(result, new_result);
1039 }
1040 _ => unreachable!(),
1041 }
1042 }
1043
1044 #[test]
1045 fn test_json2_type_hint_rejects_default() {
1046 let sql = r#"CREATE TABLE traces (
1047 log_json_data JSON2 (
1048 status_code INT64 DEFAULT -5,
1049 duration FLOAT64 DEFAULT +1.5,
1050 error BOOLEAN DEFAULT false,
1051 message STRING DEFAULT 'unknown'
1052 ),
1053 ts TIMESTAMP TIME INDEX
1054 )"#;
1055 let err =
1056 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1057 .unwrap_err();
1058 assert!(err.to_string().contains("DEFAULT is not supported"));
1059 }
1060
1061 #[test]
1062 fn test_json2_type_hint_rejects_not_null() {
1063 let sql = r#"CREATE TABLE traces (
1064 log_json_data JSON2 (
1065 status_code INT64 NOT NULL DEFAULT NULL
1066 ),
1067 ts TIMESTAMP TIME INDEX
1068 )"#;
1069 let err =
1070 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1071 .unwrap_err();
1072 assert!(err.to_string().contains("NULL/NOT NULL is not supported"));
1073 }
1074
1075 #[test]
1076 fn test_set_json_settings_normalizes_type_hint_sql_types() -> Result<()> {
1077 let mut extensions = super::ColumnExtensions::default();
1078 let settings = JsonSettings::try_new(
1079 vec![
1080 DatatypeJsonTypeHint {
1081 path: vec!["i".to_string()],
1082 data_type: ConcreteDataType::int32_datatype(),
1083 inverted_index: false,
1084 },
1085 DatatypeJsonTypeHint {
1086 path: vec!["f".to_string()],
1087 data_type: ConcreteDataType::float32_datatype(),
1088 inverted_index: false,
1089 },
1090 DatatypeJsonTypeHint {
1091 path: vec!["u".to_string()],
1092 data_type: ConcreteDataType::uint32_datatype(),
1093 inverted_index: false,
1094 },
1095 DatatypeJsonTypeHint {
1096 path: vec!["s".to_string()],
1097 data_type: ConcreteDataType::string_datatype(),
1098 inverted_index: false,
1099 },
1100 DatatypeJsonTypeHint {
1101 path: vec!["b".to_string()],
1102 data_type: ConcreteDataType::boolean_datatype(),
1103 inverted_index: false,
1104 },
1105 ],
1106 None,
1107 )?;
1108 extensions.set_json_settings(settings)?;
1109
1110 assert_eq!(
1111 extensions
1112 .json2_options
1113 .unwrap()
1114 .type_hints
1115 .iter()
1116 .map(|hint| hint.data_type.to_string())
1117 .collect::<Vec<_>>(),
1118 vec!["BIGINT", "DOUBLE", "BIGINT UNSIGNED", "STRING", "BOOLEAN"]
1119 );
1120 Ok(())
1121 }
1122
1123 #[test]
1124 fn test_set_json_settings_rejects_unsupported_type_hint_type() -> Result<()> {
1125 let err = JsonSettings::try_new(
1126 vec![DatatypeJsonTypeHint {
1127 path: vec!["u".to_string()],
1128 data_type: ConcreteDataType::date_datatype(),
1129 inverted_index: false,
1130 }],
1131 None,
1132 )
1133 .unwrap_err();
1134
1135 assert!(
1136 err.to_string()
1137 .contains("unsupported JSON2 type hint data type")
1138 );
1139 Ok(())
1140 }
1141
1142 #[test]
1143 fn test_set_empty_json_settings_omits_json2_options() -> Result<()> {
1144 let mut extensions = ColumnExtensions::default();
1145 extensions.set_json_settings(JsonSettings::default())?;
1146 assert!(extensions.json2_options.is_none());
1147 Ok(())
1148 }
1149
1150 #[test]
1151 fn test_display_create_database() {
1152 let sql = r"create database test;";
1153 let stmts =
1154 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1155 .unwrap();
1156 assert_eq!(1, stmts.len());
1157 assert_matches!(&stmts[0], Statement::CreateDatabase { .. });
1158
1159 match &stmts[0] {
1160 Statement::CreateDatabase(set) => {
1161 let new_sql = format!("\n{}", set);
1162 assert_eq!(
1163 r#"
1164CREATE DATABASE test"#,
1165 &new_sql
1166 );
1167 }
1168 _ => {
1169 unreachable!();
1170 }
1171 }
1172
1173 let sql = r"create database if not exists test;";
1174 let stmts =
1175 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1176 .unwrap();
1177 assert_eq!(1, stmts.len());
1178 assert_matches!(&stmts[0], Statement::CreateDatabase { .. });
1179
1180 match &stmts[0] {
1181 Statement::CreateDatabase(set) => {
1182 let new_sql = format!("\n{}", set);
1183 assert_eq!(
1184 r#"
1185CREATE DATABASE IF NOT EXISTS test"#,
1186 &new_sql
1187 );
1188 }
1189 _ => {
1190 unreachable!();
1191 }
1192 }
1193
1194 let sql = r#"CREATE DATABASE IF NOT EXISTS test WITH (ttl='1h');"#;
1195 let stmts =
1196 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1197 .unwrap();
1198 assert_eq!(1, stmts.len());
1199 assert_matches!(&stmts[0], Statement::CreateDatabase { .. });
1200
1201 match &stmts[0] {
1202 Statement::CreateDatabase(set) => {
1203 let new_sql = format!("\n{}", set);
1204 assert_eq!(
1205 r#"
1206CREATE DATABASE IF NOT EXISTS test
1207WITH(
1208 ttl = '1h'
1209)"#,
1210 &new_sql
1211 );
1212 }
1213 _ => {
1214 unreachable!();
1215 }
1216 }
1217 }
1218
1219 #[test]
1220 fn test_display_create_table_like() {
1221 let sql = r"create table t2 like t1;";
1222 let stmts =
1223 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1224 .unwrap();
1225 assert_eq!(1, stmts.len());
1226 assert_matches!(&stmts[0], Statement::CreateTableLike { .. });
1227
1228 match &stmts[0] {
1229 Statement::CreateTableLike(create) => {
1230 let new_sql = format!("\n{}", create);
1231 assert_eq!(
1232 r#"
1233CREATE TABLE t2 LIKE t1"#,
1234 &new_sql
1235 );
1236 }
1237 _ => {
1238 unreachable!();
1239 }
1240 }
1241 }
1242
1243 #[test]
1244 fn test_display_create_external_table() {
1245 let sql = r#"CREATE EXTERNAL TABLE city (
1246 host string,
1247 ts timestamp,
1248 cpu float64 default 0,
1249 memory float64,
1250 TIME INDEX (ts),
1251 PRIMARY KEY(host)
1252) WITH (location='/var/data/city.csv', format='csv');"#;
1253 let stmts =
1254 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1255 .unwrap();
1256 assert_eq!(1, stmts.len());
1257 assert_matches!(&stmts[0], Statement::CreateExternalTable { .. });
1258
1259 match &stmts[0] {
1260 Statement::CreateExternalTable(create) => {
1261 let new_sql = format!("\n{}", create);
1262 assert_eq!(
1263 r#"
1264CREATE EXTERNAL TABLE city (
1265 host STRING,
1266 ts TIMESTAMP,
1267 cpu DOUBLE DEFAULT 0,
1268 memory DOUBLE,
1269 TIME INDEX (ts),
1270 PRIMARY KEY (host)
1271)
1272ENGINE=file
1273WITH(
1274 format = 'csv',
1275 location = '/var/data/city.csv'
1276)"#,
1277 &new_sql
1278 );
1279 }
1280 _ => {
1281 unreachable!();
1282 }
1283 }
1284 }
1285
1286 #[test]
1287 fn test_display_create_flow() {
1288 let sql = r"CREATE FLOW filter_numbers
1289 SINK TO out_num_cnt
1290 AS SELECT number FROM numbers_input where number > 10;";
1291 let result =
1292 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1293 .unwrap();
1294 assert_eq!(1, result.len());
1295
1296 match &result[0] {
1297 Statement::CreateFlow(c) => {
1298 let new_sql = format!("\n{}", c);
1299 assert_eq!(
1300 r#"
1301CREATE FLOW filter_numbers
1302SINK TO out_num_cnt
1303AS SELECT number FROM numbers_input where number > 10"#,
1304 &new_sql
1305 );
1306
1307 let new_result = ParserContext::create_with_dialect(
1308 &new_sql,
1309 &GreptimeDbDialect {},
1310 ParseOptions::default(),
1311 )
1312 .unwrap();
1313 assert_eq!(result, new_result);
1314 }
1315 _ => unreachable!(),
1316 }
1317 }
1318
1319 #[test]
1320 fn test_vector_index_options_validation() {
1321 use super::{ColumnExtensions, OptionMap};
1322
1323 let extensions = ColumnExtensions {
1325 vector_index_options: Some(OptionMap::from([(
1326 "connectivity".to_string(),
1327 "0".to_string(),
1328 )])),
1329 ..Default::default()
1330 };
1331 let result = extensions.build_vector_index_options();
1332 assert!(result.is_err());
1333 assert!(
1334 result
1335 .unwrap_err()
1336 .to_string()
1337 .contains("connectivity must be in the range [2, 2048]")
1338 );
1339
1340 let extensions = ColumnExtensions {
1342 vector_index_options: Some(OptionMap::from([(
1343 "expansion_add".to_string(),
1344 "0".to_string(),
1345 )])),
1346 ..Default::default()
1347 };
1348 let result = extensions.build_vector_index_options();
1349 assert!(result.is_err());
1350 assert!(
1351 result
1352 .unwrap_err()
1353 .to_string()
1354 .contains("expansion_add must be greater than 0")
1355 );
1356
1357 let extensions = ColumnExtensions {
1359 vector_index_options: Some(OptionMap::from([(
1360 "expansion_search".to_string(),
1361 "0".to_string(),
1362 )])),
1363 ..Default::default()
1364 };
1365 let result = extensions.build_vector_index_options();
1366 assert!(result.is_err());
1367 assert!(
1368 result
1369 .unwrap_err()
1370 .to_string()
1371 .contains("expansion_search must be greater than 0")
1372 );
1373
1374 let extensions = ColumnExtensions {
1376 vector_index_options: Some(OptionMap::from([
1377 ("connectivity".to_string(), "32".to_string()),
1378 ("expansion_add".to_string(), "200".to_string()),
1379 ("expansion_search".to_string(), "100".to_string()),
1380 ])),
1381 ..Default::default()
1382 };
1383 let result = extensions.build_vector_index_options();
1384 assert!(result.is_ok());
1385 let options = result.unwrap().unwrap();
1386 assert_eq!(options.connectivity, 32);
1387 assert_eq!(options.expansion_add, 200);
1388 assert_eq!(options.expansion_search, 100);
1389 }
1390}