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: sql_data_type_to_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_sql_data_type(data_type: &ConcreteDataType) -> Result<DataType> {
400 let sql_type = match data_type {
401 ConcreteDataType::String(_) => DataType::String(None),
402 ConcreteDataType::Int64(_) => DataType::BigInt(None),
403 ConcreteDataType::UInt64(_) => DataType::BigIntUnsigned(None),
404 ConcreteDataType::Float64(_) => DataType::Double(sqlparser::ast::ExactNumberInfo::None),
405 ConcreteDataType::Boolean(_) => DataType::Boolean,
406 _ => {
407 return InvalidSqlSnafu {
408 msg: format!("unsupported JSON2 type hint data type: {data_type}"),
409 }
410 .fail();
411 }
412 };
413 Ok(sql_type)
414}
415
416fn format_json_type_hint(hint: &JsonTypeHint) -> String {
417 let path = hint
418 .path
419 .iter()
420 .map(|segment| format_json_path_segment(segment))
421 .join(".");
422 let inverted_index = if hint.inverted_index {
423 " INVERTED INDEX"
424 } else {
425 ""
426 };
427 format!("{} {}{}", path, hint.data_type, inverted_index)
428}
429
430fn format_json_path_segment(segment: &str) -> String {
431 format!("\"{}\"", segment.replace('"', "\"\""))
432}
433
434#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
441pub struct Partitions {
442 pub column_list: Vec<Ident>,
443 pub exprs: Vec<Expr>,
444}
445
446impl Partitions {
447 pub fn set_quote(&mut self, quote_style: char) {
449 self.column_list
450 .iter_mut()
451 .for_each(|c| c.quote_style = Some(quote_style));
452 }
453}
454
455#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut)]
456pub struct PartitionEntry {
457 pub name: Ident,
458 pub value_list: Vec<SqlValue>,
459}
460
461impl Display for PartitionEntry {
462 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
463 write!(
464 f,
465 "PARTITION {} VALUES LESS THAN ({})",
466 self.name,
467 format_list_comma!(self.value_list),
468 )
469 }
470}
471
472impl Display for Partitions {
473 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
474 if !self.column_list.is_empty() {
475 write!(
476 f,
477 "PARTITION ON COLUMNS ({}) (\n{}\n)",
478 format_list_comma!(self.column_list),
479 format_list_indent!(self.exprs),
480 )?;
481 }
482 Ok(())
483 }
484}
485
486impl Display for CreateTable {
487 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
488 write!(f, "CREATE ")?;
489 if self.engine == FILE_ENGINE {
490 write!(f, "EXTERNAL ")?;
491 }
492 write!(f, "TABLE ")?;
493 if self.if_not_exists {
494 write!(f, "IF NOT EXISTS ")?;
495 }
496 writeln!(f, "{} (", &self.name)?;
497 writeln!(f, "{},", format_list_indent!(self.columns))?;
498 writeln!(f, "{}", format_table_constraint(&self.constraints))?;
499 writeln!(f, ")")?;
500 if let Some(partitions) = &self.partitions {
501 writeln!(f, "{partitions}")?;
502 }
503 writeln!(f, "ENGINE={}", &self.engine)?;
504 if !self.options.is_empty() {
505 let options = self.options.kv_pairs();
506 write!(f, "WITH(\n{}\n)", format_list_indent!(options))?;
507 }
508 Ok(())
509 }
510}
511
512#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
513pub struct CreateDatabase {
514 pub name: ObjectName,
515 pub if_not_exists: bool,
517 pub options: OptionMap,
518}
519
520impl CreateDatabase {
521 pub fn new(name: ObjectName, if_not_exists: bool, options: OptionMap) -> Self {
523 Self {
524 name,
525 if_not_exists,
526 options,
527 }
528 }
529}
530
531impl Display for CreateDatabase {
532 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
533 write!(f, "CREATE DATABASE ")?;
534 if self.if_not_exists {
535 write!(f, "IF NOT EXISTS ")?;
536 }
537 write!(f, "{}", &self.name)?;
538 if !self.options.is_empty() {
539 let options = self.options.kv_pairs();
540 write!(f, "\nWITH(\n{}\n)", format_list_indent!(options))?;
541 }
542 Ok(())
543 }
544}
545
546#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
547pub struct CreateExternalTable {
548 pub name: ObjectName,
550 pub columns: Vec<Column>,
551 pub constraints: Vec<TableConstraint>,
552 pub options: OptionMap,
554 pub if_not_exists: bool,
555 pub engine: String,
556}
557
558impl Display for CreateExternalTable {
559 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
560 write!(f, "CREATE EXTERNAL TABLE ")?;
561 if self.if_not_exists {
562 write!(f, "IF NOT EXISTS ")?;
563 }
564 writeln!(f, "{} (", &self.name)?;
565 writeln!(f, "{},", format_list_indent!(self.columns))?;
566 writeln!(f, "{}", format_table_constraint(&self.constraints))?;
567 writeln!(f, ")")?;
568 writeln!(f, "ENGINE={}", &self.engine)?;
569 if !self.options.is_empty() {
570 let options = self.options.kv_pairs();
571 write!(f, "WITH(\n{}\n)", format_list_indent!(options))?;
572 }
573 Ok(())
574 }
575}
576
577#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
578pub struct CreateTableLike {
579 pub table_name: ObjectName,
581 pub source_name: ObjectName,
583}
584
585impl Display for CreateTableLike {
586 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
587 let table_name = &self.table_name;
588 let source_name = &self.source_name;
589 write!(f, r#"CREATE TABLE {table_name} LIKE {source_name}"#)
590 }
591}
592
593#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
594pub struct CreateFlow {
595 pub flow_name: ObjectName,
597 pub sink_table_name: ObjectName,
599 pub or_replace: bool,
601 pub if_not_exists: bool,
603 pub expire_after: Option<i64>,
606 pub eval_interval: Option<i64>,
610 pub eval_offset: Option<i64>,
616 pub comment: Option<String>,
618 pub flow_options: OptionMap,
620 pub query: Box<SqlOrTql>,
622}
623
624#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
626pub enum SqlOrTql {
627 Sql(GtQuery, String),
628 Tql(Tql, String),
629}
630
631impl std::fmt::Display for SqlOrTql {
632 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
633 match self {
634 Self::Sql(_, s) => write!(f, "{}", s),
635 Self::Tql(_, s) => write!(f, "{}", s),
636 }
637 }
638}
639
640impl SqlOrTql {
641 pub fn try_from_statement(
642 value: Statement,
643 original_query: &str,
644 ) -> std::result::Result<Self, crate::error::Error> {
645 match value {
646 Statement::Query(query) => Ok(Self::Sql(*query, original_query.to_string())),
647 Statement::Tql(tql) => Ok(Self::Tql(tql, original_query.to_string())),
648 _ => InvalidFlowQuerySnafu {
649 reason: format!("Expect either sql query or promql query, found {:?}", value),
650 }
651 .fail(),
652 }
653 }
654}
655
656impl Display for CreateFlow {
657 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
658 write!(f, "CREATE ")?;
659 if self.or_replace {
660 write!(f, "OR REPLACE ")?;
661 }
662 write!(f, "FLOW ")?;
663 if self.if_not_exists {
664 write!(f, "IF NOT EXISTS ")?;
665 }
666 writeln!(f, "{}", &self.flow_name)?;
667 writeln!(f, "SINK TO {}", &self.sink_table_name)?;
668 if let Some(expire_after) = &self.expire_after {
669 writeln!(f, "EXPIRE AFTER '{} s'", expire_after)?;
670 }
671 if let Some(eval_interval) = &self.eval_interval {
672 writeln!(f, "EVAL INTERVAL '{} s'", eval_interval)?;
673 }
674 if let Some(eval_offset) = &self.eval_offset
677 && *eval_offset != 0
678 {
679 writeln!(f, "EVAL OFFSET '{} s'", eval_offset)?;
680 }
681 if let Some(comment) = &self.comment {
682 writeln!(f, "COMMENT '{}'", comment)?;
683 }
684 if !self.flow_options.is_empty() {
685 let options = self.flow_options.kv_pairs();
686 writeln!(f, "WITH ({})", format_list_comma!(options))?;
687 }
688 write!(f, "AS {}", &self.query)
689 }
690}
691
692#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
694pub struct CreateView {
695 pub name: ObjectName,
697 pub columns: Vec<Ident>,
699 pub query: Box<Statement>,
702 pub or_replace: bool,
704 pub if_not_exists: bool,
706}
707
708impl Display for CreateView {
709 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
710 write!(f, "CREATE ")?;
711 if self.or_replace {
712 write!(f, "OR REPLACE ")?;
713 }
714 write!(f, "VIEW ")?;
715 if self.if_not_exists {
716 write!(f, "IF NOT EXISTS ")?;
717 }
718 write!(f, "{} ", &self.name)?;
719 if !self.columns.is_empty() {
720 write!(f, "({}) ", format_list_comma!(self.columns))?;
721 }
722 write!(f, "AS {}", &self.query)
723 }
724}
725
726#[cfg(test)]
727mod tests {
728 use std::assert_matches;
729
730 use datatypes::json::{JsonSettings, JsonTypeHint as DatatypeJsonTypeHint};
731 use datatypes::prelude::ConcreteDataType;
732
733 use super::*;
734 use crate::dialect::GreptimeDbDialect;
735 use crate::error::Error;
736 use crate::parser::{ParseOptions, ParserContext};
737 use crate::statements::statement::Statement;
738
739 #[test]
740 fn test_display_create_table() {
741 let sql = r"create table if not exists demo(
742 host string,
743 ts timestamp,
744 cpu double default 0,
745 memory double,
746 TIME INDEX (ts),
747 PRIMARY KEY(host)
748 )
749 PARTITION ON COLUMNS (host) (
750 host = 'a',
751 host > 'a',
752 )
753 engine=mito
754 with(ttl='7d', storage='File');
755 ";
756 let result =
757 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
758 .unwrap();
759 assert_eq!(1, result.len());
760
761 match &result[0] {
762 Statement::CreateTable(c) => {
763 let new_sql = format!("\n{}", c);
764 assert_eq!(
765 r#"
766CREATE TABLE IF NOT EXISTS demo (
767 host STRING,
768 ts TIMESTAMP,
769 cpu DOUBLE DEFAULT 0,
770 memory DOUBLE,
771 TIME INDEX (ts),
772 PRIMARY KEY (host)
773)
774PARTITION ON COLUMNS (host) (
775 host = 'a',
776 host > 'a'
777)
778ENGINE=mito
779WITH(
780 storage = 'File',
781 ttl = '7d'
782)"#,
783 &new_sql
784 );
785
786 let new_result = ParserContext::create_with_dialect(
787 &new_sql,
788 &GreptimeDbDialect {},
789 ParseOptions::default(),
790 )
791 .unwrap();
792 assert_eq!(result, new_result);
793 }
794 _ => unreachable!(),
795 }
796 }
797
798 #[test]
799 fn test_display_empty_partition_column() {
800 let sql = r"create table if not exists demo(
801 host string,
802 ts timestamp,
803 cpu double default 0,
804 memory double,
805 TIME INDEX (ts),
806 PRIMARY KEY(ts, host)
807 );
808 ";
809 let result =
810 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
811 .unwrap();
812 assert_eq!(1, result.len());
813
814 match &result[0] {
815 Statement::CreateTable(c) => {
816 let new_sql = format!("\n{}", c);
817 assert_eq!(
818 r#"
819CREATE TABLE IF NOT EXISTS demo (
820 host STRING,
821 ts TIMESTAMP,
822 cpu DOUBLE DEFAULT 0,
823 memory DOUBLE,
824 TIME INDEX (ts),
825 PRIMARY KEY (ts, host)
826)
827ENGINE=mito
828"#,
829 &new_sql
830 );
831
832 let new_result = ParserContext::create_with_dialect(
833 &new_sql,
834 &GreptimeDbDialect {},
835 ParseOptions::default(),
836 )
837 .unwrap();
838 assert_eq!(result, new_result);
839 }
840 _ => unreachable!(),
841 }
842 }
843
844 #[test]
845 fn test_validate_table_options() {
846 let sql = r"create table if not exists demo(
847 host string,
848 ts timestamp,
849 cpu double default 0,
850 memory double,
851 TIME INDEX (ts),
852 PRIMARY KEY(host)
853 )
854 PARTITION ON COLUMNS (host) ()
855 engine=mito
856 with(ttl='7d', 'compaction.type'='world');
857";
858 let result =
859 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
860 .unwrap();
861 match &result[0] {
862 Statement::CreateTable(c) => {
863 assert_eq!(2, c.options.len());
864 }
865 _ => unreachable!(),
866 }
867
868 let sql = r"create table if not exists demo(
869 host string,
870 ts timestamp,
871 cpu double default 0,
872 memory double,
873 TIME INDEX (ts),
874 PRIMARY KEY(host)
875 )
876 PARTITION ON COLUMNS (host) ()
877 engine=mito
878 with(ttl='7d', hello='world');
879";
880 let result =
881 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
882 assert_matches!(result, Err(Error::InvalidTableOption { .. }));
883
884 let semantic = |with: &str| {
886 let sql =
887 format!("create table demo(host string, ts timestamp time index) with({with});");
888 ParserContext::create_with_dialect(&sql, &GreptimeDbDialect {}, ParseOptions::default())
889 };
890 assert!(semantic("'greptime.semantic.signal_type'='metric'").is_ok());
891 assert_matches!(
893 semantic("'greptime.semantic.signal_type'='spans'"),
894 Err(Error::InvalidTableOption { .. })
895 );
896 assert_matches!(
898 semantic("'greptime.semantic.bogus'='x'"),
899 Err(Error::InvalidTableOption { .. })
900 );
901 }
902
903 #[test]
904 fn test_display_json2_type_hints_quotes_path_segments() {
905 let sql = r#"CREATE TABLE traces (
906 log_json_data JSON2 (
907 "service.name" STRING,
908 "a.b"."c" BIGINT,
909 a."b.c" STRING
910 ),
911 ts TIMESTAMP TIME INDEX
912 )"#;
913 let result =
914 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
915 .unwrap();
916
917 match &result[0] {
918 Statement::CreateTable(c) => {
919 let new_sql = format!("\n{}", c);
920 assert_eq!(
921 r#"
922CREATE TABLE traces (
923 log_json_data JSON2(
924 "service.name" STRING,
925 "a.b"."c" BIGINT,
926 "a"."b.c" STRING
927 ),
928 ts TIMESTAMP NOT NULL,
929 TIME INDEX (ts)
930)
931ENGINE=mito
932"#,
933 &new_sql
934 );
935
936 let new_result = ParserContext::create_with_dialect(
937 &new_sql,
938 &GreptimeDbDialect {},
939 ParseOptions::default(),
940 )
941 .unwrap();
942 assert_eq!(result, new_result);
943 }
944 _ => unreachable!(),
945 }
946 }
947
948 #[test]
949 fn test_parse_json2_max_auto_expanded_paths_option() -> Result<()> {
950 let sql = r#"CREATE TABLE traces (
951 log_json_data JSON2 (
952 status_code BIGINT,
953 max_auto_expanded_paths = 1
954 ),
955 ts TIMESTAMP TIME INDEX
956 )"#;
957 let result = ParserContext::create_with_dialect(
958 sql,
959 &GreptimeDbDialect {},
960 ParseOptions::default(),
961 )?;
962 let Statement::CreateTable(create_table) = &result[0] else {
963 unreachable!()
964 };
965 let settings = create_table.columns[0]
966 .extensions
967 .build_json_settings()?
968 .unwrap();
969 assert_eq!(settings.max_auto_expanded_paths(), Some(1));
970 Ok(())
971 }
972
973 #[test]
974 fn test_display_json2_type_hints_quotes_numeric_segments() {
975 let sql = r#"CREATE TABLE traces (
976 log_json_data JSON2 (
977 "1abc" STRING,
978 a."2b" BIGINT
979 ),
980 ts TIMESTAMP TIME INDEX
981 )"#;
982 let result =
983 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
984 .unwrap();
985
986 match &result[0] {
987 Statement::CreateTable(c) => {
988 let new_sql = format!("\n{}", c);
989 assert_eq!(
990 r#"
991CREATE TABLE traces (
992 log_json_data JSON2(
993 "1abc" STRING,
994 "a"."2b" BIGINT
995 ),
996 ts TIMESTAMP NOT NULL,
997 TIME INDEX (ts)
998)
999ENGINE=mito
1000"#,
1001 &new_sql
1002 );
1003
1004 let new_result = ParserContext::create_with_dialect(
1005 &new_sql,
1006 &GreptimeDbDialect {},
1007 ParseOptions::default(),
1008 )
1009 .unwrap();
1010 assert_eq!(result, new_result);
1011 }
1012 _ => unreachable!(),
1013 }
1014 }
1015
1016 #[test]
1017 fn test_json2_type_hint_rejects_default() {
1018 let sql = r#"CREATE TABLE traces (
1019 log_json_data JSON2 (
1020 status_code BIGINT DEFAULT -5,
1021 duration DOUBLE DEFAULT +1.5,
1022 error BOOLEAN DEFAULT false,
1023 message STRING DEFAULT 'unknown'
1024 ),
1025 ts TIMESTAMP TIME INDEX
1026 )"#;
1027 let err =
1028 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1029 .unwrap_err();
1030 assert!(err.to_string().contains("DEFAULT is not supported"));
1031 }
1032
1033 #[test]
1034 fn test_json2_type_hint_rejects_not_null() {
1035 let sql = r#"CREATE TABLE traces (
1036 log_json_data JSON2 (
1037 status_code BIGINT NOT NULL DEFAULT NULL
1038 ),
1039 ts TIMESTAMP TIME INDEX
1040 )"#;
1041 let err =
1042 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1043 .unwrap_err();
1044 assert!(err.to_string().contains("NULL/NOT NULL is not supported"));
1045 }
1046
1047 #[test]
1048 fn test_set_json_settings_preserves_type_hint_sql_types() -> Result<()> {
1049 let mut extensions = super::ColumnExtensions::default();
1050 let settings = JsonSettings::try_new(
1051 vec![
1052 DatatypeJsonTypeHint {
1053 path: vec!["i".to_string()],
1054 data_type: ConcreteDataType::int64_datatype(),
1055 inverted_index: false,
1056 },
1057 DatatypeJsonTypeHint {
1058 path: vec!["f".to_string()],
1059 data_type: ConcreteDataType::float64_datatype(),
1060 inverted_index: false,
1061 },
1062 DatatypeJsonTypeHint {
1063 path: vec!["u".to_string()],
1064 data_type: ConcreteDataType::uint64_datatype(),
1065 inverted_index: false,
1066 },
1067 DatatypeJsonTypeHint {
1068 path: vec!["s".to_string()],
1069 data_type: ConcreteDataType::string_datatype(),
1070 inverted_index: false,
1071 },
1072 DatatypeJsonTypeHint {
1073 path: vec!["b".to_string()],
1074 data_type: ConcreteDataType::boolean_datatype(),
1075 inverted_index: false,
1076 },
1077 ],
1078 None,
1079 )?;
1080 extensions.set_json_settings(settings)?;
1081
1082 assert_eq!(
1083 extensions
1084 .json2_options
1085 .unwrap()
1086 .type_hints
1087 .iter()
1088 .map(|hint| hint.data_type.to_string())
1089 .collect::<Vec<_>>(),
1090 vec!["BIGINT", "DOUBLE", "BIGINT UNSIGNED", "STRING", "BOOLEAN"]
1091 );
1092 Ok(())
1093 }
1094
1095 #[test]
1096 fn test_set_json_settings_rejects_unsupported_type_hint_type() -> Result<()> {
1097 let err = JsonSettings::try_new(
1098 vec![DatatypeJsonTypeHint {
1099 path: vec!["u".to_string()],
1100 data_type: ConcreteDataType::date_datatype(),
1101 inverted_index: false,
1102 }],
1103 None,
1104 )
1105 .unwrap_err();
1106
1107 assert!(
1108 err.to_string()
1109 .contains("unsupported JSON2 type hint data type")
1110 );
1111 Ok(())
1112 }
1113
1114 #[test]
1115 fn test_set_empty_json_settings_omits_json2_options() -> Result<()> {
1116 let mut extensions = ColumnExtensions::default();
1117 extensions.set_json_settings(JsonSettings::default())?;
1118 assert!(extensions.json2_options.is_none());
1119 Ok(())
1120 }
1121
1122 #[test]
1123 fn test_display_create_database() {
1124 let sql = r"create database test;";
1125 let stmts =
1126 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1127 .unwrap();
1128 assert_eq!(1, stmts.len());
1129 assert_matches!(&stmts[0], Statement::CreateDatabase { .. });
1130
1131 match &stmts[0] {
1132 Statement::CreateDatabase(set) => {
1133 let new_sql = format!("\n{}", set);
1134 assert_eq!(
1135 r#"
1136CREATE DATABASE test"#,
1137 &new_sql
1138 );
1139 }
1140 _ => {
1141 unreachable!();
1142 }
1143 }
1144
1145 let sql = r"create database if not exists test;";
1146 let stmts =
1147 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1148 .unwrap();
1149 assert_eq!(1, stmts.len());
1150 assert_matches!(&stmts[0], Statement::CreateDatabase { .. });
1151
1152 match &stmts[0] {
1153 Statement::CreateDatabase(set) => {
1154 let new_sql = format!("\n{}", set);
1155 assert_eq!(
1156 r#"
1157CREATE DATABASE IF NOT EXISTS test"#,
1158 &new_sql
1159 );
1160 }
1161 _ => {
1162 unreachable!();
1163 }
1164 }
1165
1166 let sql = r#"CREATE DATABASE IF NOT EXISTS test WITH (ttl='1h');"#;
1167 let stmts =
1168 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1169 .unwrap();
1170 assert_eq!(1, stmts.len());
1171 assert_matches!(&stmts[0], Statement::CreateDatabase { .. });
1172
1173 match &stmts[0] {
1174 Statement::CreateDatabase(set) => {
1175 let new_sql = format!("\n{}", set);
1176 assert_eq!(
1177 r#"
1178CREATE DATABASE IF NOT EXISTS test
1179WITH(
1180 ttl = '1h'
1181)"#,
1182 &new_sql
1183 );
1184 }
1185 _ => {
1186 unreachable!();
1187 }
1188 }
1189 }
1190
1191 #[test]
1192 fn test_display_create_table_like() {
1193 let sql = r"create table t2 like t1;";
1194 let stmts =
1195 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1196 .unwrap();
1197 assert_eq!(1, stmts.len());
1198 assert_matches!(&stmts[0], Statement::CreateTableLike { .. });
1199
1200 match &stmts[0] {
1201 Statement::CreateTableLike(create) => {
1202 let new_sql = format!("\n{}", create);
1203 assert_eq!(
1204 r#"
1205CREATE TABLE t2 LIKE t1"#,
1206 &new_sql
1207 );
1208 }
1209 _ => {
1210 unreachable!();
1211 }
1212 }
1213 }
1214
1215 #[test]
1216 fn test_display_create_external_table() {
1217 let sql = r#"CREATE EXTERNAL TABLE city (
1218 host string,
1219 ts timestamp,
1220 cpu float64 default 0,
1221 memory float64,
1222 TIME INDEX (ts),
1223 PRIMARY KEY(host)
1224) WITH (location='/var/data/city.csv', format='csv');"#;
1225 let stmts =
1226 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1227 .unwrap();
1228 assert_eq!(1, stmts.len());
1229 assert_matches!(&stmts[0], Statement::CreateExternalTable { .. });
1230
1231 match &stmts[0] {
1232 Statement::CreateExternalTable(create) => {
1233 let new_sql = format!("\n{}", create);
1234 assert_eq!(
1235 r#"
1236CREATE EXTERNAL TABLE city (
1237 host STRING,
1238 ts TIMESTAMP,
1239 cpu DOUBLE DEFAULT 0,
1240 memory DOUBLE,
1241 TIME INDEX (ts),
1242 PRIMARY KEY (host)
1243)
1244ENGINE=file
1245WITH(
1246 format = 'csv',
1247 location = '/var/data/city.csv'
1248)"#,
1249 &new_sql
1250 );
1251 }
1252 _ => {
1253 unreachable!();
1254 }
1255 }
1256 }
1257
1258 #[test]
1259 fn test_display_create_flow() {
1260 let sql = r"CREATE FLOW filter_numbers
1261 SINK TO out_num_cnt
1262 AS SELECT number FROM numbers_input where number > 10;";
1263 let result =
1264 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1265 .unwrap();
1266 assert_eq!(1, result.len());
1267
1268 match &result[0] {
1269 Statement::CreateFlow(c) => {
1270 let new_sql = format!("\n{}", c);
1271 assert_eq!(
1272 r#"
1273CREATE FLOW filter_numbers
1274SINK TO out_num_cnt
1275AS SELECT number FROM numbers_input where number > 10"#,
1276 &new_sql
1277 );
1278
1279 let new_result = ParserContext::create_with_dialect(
1280 &new_sql,
1281 &GreptimeDbDialect {},
1282 ParseOptions::default(),
1283 )
1284 .unwrap();
1285 assert_eq!(result, new_result);
1286 }
1287 _ => unreachable!(),
1288 }
1289 }
1290
1291 #[test]
1292 fn test_vector_index_options_validation() {
1293 use super::{ColumnExtensions, OptionMap};
1294
1295 let extensions = ColumnExtensions {
1297 vector_index_options: Some(OptionMap::from([(
1298 "connectivity".to_string(),
1299 "0".to_string(),
1300 )])),
1301 ..Default::default()
1302 };
1303 let result = extensions.build_vector_index_options();
1304 assert!(result.is_err());
1305 assert!(
1306 result
1307 .unwrap_err()
1308 .to_string()
1309 .contains("connectivity must be in the range [2, 2048]")
1310 );
1311
1312 let extensions = ColumnExtensions {
1314 vector_index_options: Some(OptionMap::from([(
1315 "expansion_add".to_string(),
1316 "0".to_string(),
1317 )])),
1318 ..Default::default()
1319 };
1320 let result = extensions.build_vector_index_options();
1321 assert!(result.is_err());
1322 assert!(
1323 result
1324 .unwrap_err()
1325 .to_string()
1326 .contains("expansion_add must be greater than 0")
1327 );
1328
1329 let extensions = ColumnExtensions {
1331 vector_index_options: Some(OptionMap::from([(
1332 "expansion_search".to_string(),
1333 "0".to_string(),
1334 )])),
1335 ..Default::default()
1336 };
1337 let result = extensions.build_vector_index_options();
1338 assert!(result.is_err());
1339 assert!(
1340 result
1341 .unwrap_err()
1342 .to_string()
1343 .contains("expansion_search must be greater than 0")
1344 );
1345
1346 let extensions = ColumnExtensions {
1348 vector_index_options: Some(OptionMap::from([
1349 ("connectivity".to_string(), "32".to_string()),
1350 ("expansion_add".to_string(), "200".to_string()),
1351 ("expansion_search".to_string(), "100".to_string()),
1352 ])),
1353 ..Default::default()
1354 };
1355 let result = extensions.build_vector_index_options();
1356 assert!(result.is_ok());
1357 let options = result.unwrap().unwrap();
1358 assert_eq!(options.connectivity, 32);
1359 assert_eq!(options.expansion_add, 200);
1360 assert_eq!(options.expansion_search, 100);
1361 }
1362}