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::{FulltextOptions, SkippingIndexOptions};
22use itertools::Itertools;
23use serde::Serialize;
24use snafu::ResultExt;
25use sqlparser::ast::{ColumnOptionDef, DataType, Expr};
26use sqlparser_derive::{Visit, VisitMut};
27
28use crate::ast::{ColumnDef, Ident, ObjectName};
29use crate::error::{
30 InvalidFlowQuerySnafu, InvalidSqlSnafu, Result, SetFulltextOptionSnafu,
31 SetSkippingIndexOptionSnafu,
32};
33use crate::statements::query::Query as GtQuery;
34use crate::statements::statement::Statement;
35use crate::statements::tql::Tql;
36use crate::statements::{OptionMap, sql_data_type_to_concrete_data_type};
37
38const LINE_SEP: &str = ",\n";
39const COMMA_SEP: &str = ", ";
40const INDENT: usize = 2;
41pub const VECTOR_OPT_DIM: &str = "dim";
42
43macro_rules! format_indent {
44 ($fmt: expr, $arg: expr) => {
45 format!($fmt, format_args!("{: >1$}", "", INDENT), $arg)
46 };
47 ($arg: expr) => {
48 format_indent!("{}{}", $arg)
49 };
50}
51
52macro_rules! format_list_indent {
53 ($list: expr) => {
54 $list.iter().map(|e| format_indent!(e)).join(LINE_SEP)
55 };
56}
57
58macro_rules! format_list_comma {
59 ($list: expr) => {
60 $list.iter().map(|e| format!("{}", e)).join(COMMA_SEP)
61 };
62}
63
64#[cfg(feature = "enterprise")]
65pub mod trigger;
66
67fn format_table_constraint(constraints: &[TableConstraint]) -> String {
68 constraints.iter().map(|c| format_indent!(c)).join(LINE_SEP)
69}
70
71#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
73pub enum TableConstraint {
74 PrimaryKey { columns: Vec<Ident> },
76 TimeIndex { column: Ident },
78}
79
80impl Display for TableConstraint {
81 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
82 match self {
83 TableConstraint::PrimaryKey { columns } => {
84 write!(f, "PRIMARY KEY ({})", format_list_comma!(columns))
85 }
86 TableConstraint::TimeIndex { column } => {
87 write!(f, "TIME INDEX ({})", column)
88 }
89 }
90 }
91}
92
93#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
94pub struct CreateTable {
95 pub if_not_exists: bool,
97 pub table_id: u32,
98 pub name: ObjectName,
100 pub columns: Vec<Column>,
101 pub engine: String,
102 pub constraints: Vec<TableConstraint>,
103 pub options: OptionMap,
105 pub partitions: Option<Partitions>,
106}
107
108#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
110pub struct Column {
111 pub column_def: ColumnDef,
113 pub extensions: ColumnExtensions,
115}
116
117#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Default, Serialize)]
119pub struct ColumnExtensions {
120 pub vector_options: Option<OptionMap>,
122
123 pub fulltext_index_options: Option<OptionMap>,
125 pub skipping_index_options: Option<OptionMap>,
127 pub inverted_index_options: Option<OptionMap>,
131 pub json2_options: Option<Json2Options>,
133}
134
135#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Default, Serialize)]
137pub struct Json2Options {
138 pub(crate) max_auto_expanded_paths: Option<u32>,
140 pub(crate) type_hints: Vec<JsonTypeHint>,
142}
143
144impl Display for Json2Options {
145 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
146 let mut options = Vec::with_capacity(self.type_hints.len() + 1);
147 if let Some(max) = self.max_auto_expanded_paths {
148 options.push(format!("max_auto_expanded_paths = {max}"));
149 }
150 options.extend(self.type_hints.iter().map(format_json_type_hint));
151 write!(f, "(\n {}\n )", options.iter().join(",\n "))
152 }
153}
154
155impl Json2Options {
156 pub fn build_json_settings(&self) -> Result<JsonSettings> {
157 let type_hints = self
158 .type_hints
159 .iter()
160 .map(|hint| {
161 Ok(datatypes::json::JsonTypeHint {
162 path: hint.path.clone(),
163 data_type: sql_data_type_to_concrete_data_type(&hint.data_type)?,
164 inverted_index: hint.inverted_index,
165 })
166 })
167 .collect::<Result<Vec<_>>>()?;
168 let max_auto_expanded_paths = self
169 .max_auto_expanded_paths
170 .or(Some(JSON2_DEFAULT_MAX_AUTO_EXPANDED_PATHS));
171 JsonSettings::try_new(type_hints, max_auto_expanded_paths).map_err(Into::into)
172 }
173}
174
175#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
176pub struct JsonTypeHint {
177 pub path: Vec<String>,
178 pub data_type: DataType,
179 pub inverted_index: bool,
180}
181
182impl Column {
183 pub fn name(&self) -> &Ident {
184 &self.column_def.name
185 }
186
187 pub fn data_type(&self) -> &DataType {
188 &self.column_def.data_type
189 }
190
191 pub fn mut_data_type(&mut self) -> &mut DataType {
192 &mut self.column_def.data_type
193 }
194
195 pub fn options(&self) -> &[ColumnOptionDef] {
196 &self.column_def.options
197 }
198
199 pub fn mut_options(&mut self) -> &mut Vec<ColumnOptionDef> {
200 &mut self.column_def.options
201 }
202}
203
204impl Display for Column {
205 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
206 if let Some(vector_options) = &self.extensions.vector_options
207 && let Some(dim) = vector_options.get(VECTOR_OPT_DIM)
208 {
209 write!(f, "{} VECTOR({})", self.column_def.name, dim)?;
210 return Ok(());
211 }
212
213 write!(f, "{} {}", self.column_def.name, self.column_def.data_type)?;
214 if let Some(options) = &self.extensions.json2_options {
215 write!(f, "{options}")?;
216 }
217 for option in &self.column_def.options {
218 write!(f, " {option}")?;
219 }
220
221 if let Some(fulltext_options) = &self.extensions.fulltext_index_options {
222 if !fulltext_options.is_empty() {
223 let options = fulltext_options.kv_pairs();
224 write!(f, " FULLTEXT INDEX WITH({})", format_list_comma!(options))?;
225 } else {
226 write!(f, " FULLTEXT INDEX")?;
227 }
228 }
229
230 if let Some(skipping_index_options) = &self.extensions.skipping_index_options {
231 if !skipping_index_options.is_empty() {
232 let options = skipping_index_options.kv_pairs();
233 write!(f, " SKIPPING INDEX WITH({})", format_list_comma!(options))?;
234 } else {
235 write!(f, " SKIPPING INDEX")?;
236 }
237 }
238
239 if let Some(inverted_index_options) = &self.extensions.inverted_index_options {
240 if !inverted_index_options.is_empty() {
241 let options = inverted_index_options.kv_pairs();
242 write!(f, " INVERTED INDEX WITH({})", format_list_comma!(options))?;
243 } else {
244 write!(f, " INVERTED INDEX")?;
245 }
246 }
247
248 Ok(())
249 }
250}
251
252impl ColumnExtensions {
253 pub fn build_fulltext_options(&self) -> Result<Option<FulltextOptions>> {
254 let Some(options) = self.fulltext_index_options.as_ref() else {
255 return Ok(None);
256 };
257
258 let options: HashMap<String, String> = options.clone().into_map();
259 Ok(Some(options.try_into().context(SetFulltextOptionSnafu)?))
260 }
261
262 pub fn build_skipping_index_options(&self) -> Result<Option<SkippingIndexOptions>> {
263 let Some(options) = self.skipping_index_options.as_ref() else {
264 return Ok(None);
265 };
266
267 let options: HashMap<String, String> = options.clone().into_map();
268 Ok(Some(
269 options.try_into().context(SetSkippingIndexOptionSnafu)?,
270 ))
271 }
272
273 pub fn build_json_settings(&self) -> Result<Option<JsonSettings>> {
274 let Some(options) = &self.json2_options else {
275 return Ok(None);
276 };
277
278 options.build_json_settings().map(Some)
279 }
280
281 pub fn set_json_settings(&mut self, settings: JsonSettings) -> Result<()> {
282 let (type_hints, max_auto_expanded_paths) = settings.into_parts();
283 let type_hints = type_hints
284 .into_iter()
285 .map(|hint| {
286 let data_type = json_type_hint_sql_data_type(&hint.data_type)?;
287 Ok(JsonTypeHint {
288 path: hint.path,
289 data_type,
290 inverted_index: hint.inverted_index,
291 })
292 })
293 .collect::<Result<Vec<_>>>()?;
294 self.json2_options = (max_auto_expanded_paths.is_some() || !type_hints.is_empty())
295 .then_some(Json2Options {
296 max_auto_expanded_paths,
297 type_hints,
298 });
299 Ok(())
300 }
301}
302
303fn json_type_hint_sql_data_type(data_type: &ConcreteDataType) -> Result<DataType> {
304 let sql_type = match data_type {
305 ConcreteDataType::String(_) => DataType::String(None),
306 ConcreteDataType::Int64(_) => DataType::BigInt(None),
307 ConcreteDataType::UInt64(_) => DataType::BigIntUnsigned(None),
308 ConcreteDataType::Float64(_) => DataType::Double(sqlparser::ast::ExactNumberInfo::None),
309 ConcreteDataType::Boolean(_) => DataType::Boolean,
310 _ => {
311 return InvalidSqlSnafu {
312 msg: format!("unsupported JSON2 type hint data type: {data_type}"),
313 }
314 .fail();
315 }
316 };
317 Ok(sql_type)
318}
319
320fn format_json_type_hint(hint: &JsonTypeHint) -> String {
321 let path = hint
322 .path
323 .iter()
324 .map(|segment| format_json_path_segment(segment))
325 .join(".");
326 let inverted_index = if hint.inverted_index {
327 " INVERTED INDEX"
328 } else {
329 ""
330 };
331 format!("{} {}{}", path, hint.data_type, inverted_index)
332}
333
334fn format_json_path_segment(segment: &str) -> String {
335 format!("\"{}\"", segment.replace('"', "\"\""))
336}
337
338#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
345pub struct Partitions {
346 pub column_list: Vec<Ident>,
347 pub exprs: Vec<Expr>,
348}
349
350impl Partitions {
351 pub fn set_quote(&mut self, quote_style: char) {
353 self.column_list
354 .iter_mut()
355 .for_each(|c| c.quote_style = Some(quote_style));
356 }
357}
358
359impl Display for Partitions {
360 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
361 if !self.column_list.is_empty() {
362 write!(
363 f,
364 "PARTITION ON COLUMNS ({}) (\n{}\n)",
365 format_list_comma!(self.column_list),
366 format_list_indent!(self.exprs),
367 )?;
368 }
369 Ok(())
370 }
371}
372
373impl Display for CreateTable {
374 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
375 write!(f, "CREATE ")?;
376 if self.engine == FILE_ENGINE {
377 write!(f, "EXTERNAL ")?;
378 }
379 write!(f, "TABLE ")?;
380 if self.if_not_exists {
381 write!(f, "IF NOT EXISTS ")?;
382 }
383 writeln!(f, "{} (", &self.name)?;
384 writeln!(f, "{},", format_list_indent!(self.columns))?;
385 writeln!(f, "{}", format_table_constraint(&self.constraints))?;
386 writeln!(f, ")")?;
387 if let Some(partitions) = &self.partitions {
388 writeln!(f, "{partitions}")?;
389 }
390 writeln!(f, "ENGINE={}", &self.engine)?;
391 if !self.options.is_empty() {
392 let options = self.options.kv_pairs();
393 write!(f, "WITH(\n{}\n)", format_list_indent!(options))?;
394 }
395 Ok(())
396 }
397}
398
399#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
400pub struct CreateDatabase {
401 pub name: ObjectName,
402 pub if_not_exists: bool,
404 pub options: OptionMap,
405}
406
407impl CreateDatabase {
408 pub fn new(name: ObjectName, if_not_exists: bool, options: OptionMap) -> Self {
410 Self {
411 name,
412 if_not_exists,
413 options,
414 }
415 }
416}
417
418impl Display for CreateDatabase {
419 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
420 write!(f, "CREATE DATABASE ")?;
421 if self.if_not_exists {
422 write!(f, "IF NOT EXISTS ")?;
423 }
424 write!(f, "{}", &self.name)?;
425 if !self.options.is_empty() {
426 let options = self.options.kv_pairs();
427 write!(f, "\nWITH(\n{}\n)", format_list_indent!(options))?;
428 }
429 Ok(())
430 }
431}
432
433#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
434pub struct CreateExternalTable {
435 pub name: ObjectName,
437 pub columns: Vec<Column>,
438 pub constraints: Vec<TableConstraint>,
439 pub options: OptionMap,
441 pub if_not_exists: bool,
442 pub engine: String,
443}
444
445impl Display for CreateExternalTable {
446 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
447 write!(f, "CREATE EXTERNAL TABLE ")?;
448 if self.if_not_exists {
449 write!(f, "IF NOT EXISTS ")?;
450 }
451 writeln!(f, "{} (", &self.name)?;
452 writeln!(f, "{},", format_list_indent!(self.columns))?;
453 writeln!(f, "{}", format_table_constraint(&self.constraints))?;
454 writeln!(f, ")")?;
455 writeln!(f, "ENGINE={}", &self.engine)?;
456 if !self.options.is_empty() {
457 let options = self.options.kv_pairs();
458 write!(f, "WITH(\n{}\n)", format_list_indent!(options))?;
459 }
460 Ok(())
461 }
462}
463
464#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
465pub struct CreateTableLike {
466 pub table_name: ObjectName,
468 pub source_name: ObjectName,
470}
471
472impl Display for CreateTableLike {
473 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
474 let table_name = &self.table_name;
475 let source_name = &self.source_name;
476 write!(f, r#"CREATE TABLE {table_name} LIKE {source_name}"#)
477 }
478}
479
480#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
481pub struct CreateFlow {
482 pub flow_name: ObjectName,
484 pub sink_table_name: ObjectName,
486 pub or_replace: bool,
488 pub if_not_exists: bool,
490 pub expire_after: Option<i64>,
493 pub eval_interval: Option<i64>,
497 pub eval_offset: Option<i64>,
503 pub comment: Option<String>,
505 pub flow_options: OptionMap,
507 pub query: Box<SqlOrTql>,
509}
510
511#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
513pub enum SqlOrTql {
514 Sql(GtQuery, String),
515 Tql(Tql, String),
516}
517
518impl std::fmt::Display for SqlOrTql {
519 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
520 match self {
521 Self::Sql(_, s) => write!(f, "{}", s),
522 Self::Tql(_, s) => write!(f, "{}", s),
523 }
524 }
525}
526
527impl SqlOrTql {
528 pub fn try_from_statement(
529 value: Statement,
530 original_query: &str,
531 ) -> std::result::Result<Self, crate::error::Error> {
532 match value {
533 Statement::Query(query) => Ok(Self::Sql(*query, original_query.to_string())),
534 Statement::Tql(tql) => Ok(Self::Tql(tql, original_query.to_string())),
535 _ => InvalidFlowQuerySnafu {
536 reason: format!("Expect either sql query or promql query, found {:?}", value),
537 }
538 .fail(),
539 }
540 }
541}
542
543impl Display for CreateFlow {
544 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
545 write!(f, "CREATE ")?;
546 if self.or_replace {
547 write!(f, "OR REPLACE ")?;
548 }
549 write!(f, "FLOW ")?;
550 if self.if_not_exists {
551 write!(f, "IF NOT EXISTS ")?;
552 }
553 writeln!(f, "{}", &self.flow_name)?;
554 writeln!(f, "SINK TO {}", &self.sink_table_name)?;
555 if let Some(expire_after) = &self.expire_after {
556 writeln!(f, "EXPIRE AFTER '{} s'", expire_after)?;
557 }
558 if let Some(eval_interval) = &self.eval_interval {
559 writeln!(f, "EVAL INTERVAL '{} s'", eval_interval)?;
560 }
561 if let Some(eval_offset) = &self.eval_offset
564 && *eval_offset != 0
565 {
566 writeln!(f, "EVAL OFFSET '{} s'", eval_offset)?;
567 }
568 if let Some(comment) = &self.comment {
569 writeln!(f, "COMMENT '{}'", comment)?;
570 }
571 if !self.flow_options.is_empty() {
572 let options = self.flow_options.kv_pairs();
573 writeln!(f, "WITH ({})", format_list_comma!(options))?;
574 }
575 write!(f, "AS {}", &self.query)
576 }
577}
578
579#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
581pub struct CreateView {
582 pub name: ObjectName,
584 pub columns: Vec<Ident>,
586 pub query: Box<Statement>,
589 pub or_replace: bool,
591 pub if_not_exists: bool,
593}
594
595impl Display for CreateView {
596 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
597 write!(f, "CREATE ")?;
598 if self.or_replace {
599 write!(f, "OR REPLACE ")?;
600 }
601 write!(f, "VIEW ")?;
602 if self.if_not_exists {
603 write!(f, "IF NOT EXISTS ")?;
604 }
605 write!(f, "{} ", &self.name)?;
606 if !self.columns.is_empty() {
607 write!(f, "({}) ", format_list_comma!(self.columns))?;
608 }
609 write!(f, "AS {}", &self.query)
610 }
611}
612
613#[cfg(test)]
614mod tests {
615 use std::assert_matches;
616
617 use datatypes::json::{JsonSettings, JsonTypeHint as DatatypeJsonTypeHint};
618 use datatypes::prelude::ConcreteDataType;
619
620 use super::*;
621 use crate::dialect::GreptimeDbDialect;
622 use crate::error::Error;
623 use crate::parser::{ParseOptions, ParserContext};
624 use crate::statements::statement::Statement;
625
626 #[test]
627 fn test_display_create_table() {
628 let sql = r"create table if not exists demo(
629 host string,
630 ts timestamp,
631 cpu double default 0,
632 memory double,
633 TIME INDEX (ts),
634 PRIMARY KEY(host)
635 )
636 PARTITION ON COLUMNS (host) (
637 host = 'a',
638 host > 'a',
639 )
640 engine=mito
641 with(ttl='7d', storage='File');
642 ";
643 let result =
644 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
645 .unwrap();
646 assert_eq!(1, result.len());
647
648 match &result[0] {
649 Statement::CreateTable(c) => {
650 let new_sql = format!("\n{}", c);
651 assert_eq!(
652 r#"
653CREATE TABLE IF NOT EXISTS demo (
654 host STRING,
655 ts TIMESTAMP,
656 cpu DOUBLE DEFAULT 0,
657 memory DOUBLE,
658 TIME INDEX (ts),
659 PRIMARY KEY (host)
660)
661PARTITION ON COLUMNS (host) (
662 host = 'a',
663 host > 'a'
664)
665ENGINE=mito
666WITH(
667 storage = 'File',
668 ttl = '7d'
669)"#,
670 &new_sql
671 );
672
673 let new_result = ParserContext::create_with_dialect(
674 &new_sql,
675 &GreptimeDbDialect {},
676 ParseOptions::default(),
677 )
678 .unwrap();
679 assert_eq!(result, new_result);
680 }
681 _ => unreachable!(),
682 }
683 }
684
685 #[test]
686 fn test_display_empty_partition_column() {
687 let sql = r"create table if not exists demo(
688 host string,
689 ts timestamp,
690 cpu double default 0,
691 memory double,
692 TIME INDEX (ts),
693 PRIMARY KEY(ts, host)
694 );
695 ";
696 let result =
697 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
698 .unwrap();
699 assert_eq!(1, result.len());
700
701 match &result[0] {
702 Statement::CreateTable(c) => {
703 let new_sql = format!("\n{}", c);
704 assert_eq!(
705 r#"
706CREATE TABLE IF NOT EXISTS demo (
707 host STRING,
708 ts TIMESTAMP,
709 cpu DOUBLE DEFAULT 0,
710 memory DOUBLE,
711 TIME INDEX (ts),
712 PRIMARY KEY (ts, host)
713)
714ENGINE=mito
715"#,
716 &new_sql
717 );
718
719 let new_result = ParserContext::create_with_dialect(
720 &new_sql,
721 &GreptimeDbDialect {},
722 ParseOptions::default(),
723 )
724 .unwrap();
725 assert_eq!(result, new_result);
726 }
727 _ => unreachable!(),
728 }
729 }
730
731 #[test]
732 fn test_validate_table_options() {
733 let sql = r"create table if not exists demo(
734 host string,
735 ts timestamp,
736 cpu double default 0,
737 memory double,
738 TIME INDEX (ts),
739 PRIMARY KEY(host)
740 )
741 PARTITION ON COLUMNS (host) ()
742 engine=mito
743 with(ttl='7d', 'compaction.type'='world');
744";
745 let result =
746 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
747 .unwrap();
748 match &result[0] {
749 Statement::CreateTable(c) => {
750 assert_eq!(2, c.options.len());
751 }
752 _ => unreachable!(),
753 }
754
755 let sql = r"create table if not exists demo(
756 host string,
757 ts timestamp,
758 cpu double default 0,
759 memory double,
760 TIME INDEX (ts),
761 PRIMARY KEY(host)
762 )
763 PARTITION ON COLUMNS (host) ()
764 engine=mito
765 with(ttl='7d', hello='world');
766";
767 let result =
768 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
769 assert_matches!(result, Err(Error::InvalidTableOption { .. }));
770
771 let semantic = |with: &str| {
773 let sql =
774 format!("create table demo(host string, ts timestamp time index) with({with});");
775 ParserContext::create_with_dialect(&sql, &GreptimeDbDialect {}, ParseOptions::default())
776 };
777 assert!(semantic("'greptime.semantic.signal_type'='metric'").is_ok());
778 assert_matches!(
780 semantic("'greptime.semantic.signal_type'='spans'"),
781 Err(Error::InvalidTableOption { .. })
782 );
783 assert_matches!(
785 semantic("'greptime.semantic.bogus'='x'"),
786 Err(Error::InvalidTableOption { .. })
787 );
788 }
789
790 #[test]
791 fn test_display_json2_type_hints_quotes_path_segments() {
792 let sql = r#"CREATE TABLE traces (
793 log_json_data JSON2 (
794 "service.name" STRING,
795 "a.b"."c" BIGINT,
796 a."b.c" STRING
797 ),
798 ts TIMESTAMP TIME INDEX
799 )"#;
800 let result =
801 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
802 .unwrap();
803
804 match &result[0] {
805 Statement::CreateTable(c) => {
806 let new_sql = format!("\n{}", c);
807 assert_eq!(
808 r#"
809CREATE TABLE traces (
810 log_json_data JSON2(
811 "service.name" STRING,
812 "a.b"."c" BIGINT,
813 "a"."b.c" STRING
814 ),
815 ts TIMESTAMP NOT NULL,
816 TIME INDEX (ts)
817)
818ENGINE=mito
819"#,
820 &new_sql
821 );
822
823 let new_result = ParserContext::create_with_dialect(
824 &new_sql,
825 &GreptimeDbDialect {},
826 ParseOptions::default(),
827 )
828 .unwrap();
829 assert_eq!(result, new_result);
830 }
831 _ => unreachable!(),
832 }
833 }
834
835 #[test]
836 fn test_parse_json2_max_auto_expanded_paths_option() -> Result<()> {
837 let sql = r#"CREATE TABLE traces (
838 log_json_data JSON2 (
839 status_code BIGINT,
840 max_auto_expanded_paths = 1
841 ),
842 ts TIMESTAMP TIME INDEX
843 )"#;
844 let result = ParserContext::create_with_dialect(
845 sql,
846 &GreptimeDbDialect {},
847 ParseOptions::default(),
848 )?;
849 let Statement::CreateTable(create_table) = &result[0] else {
850 unreachable!()
851 };
852 let settings = create_table.columns[0]
853 .extensions
854 .build_json_settings()?
855 .unwrap();
856 assert_eq!(settings.max_auto_expanded_paths(), Some(1));
857 Ok(())
858 }
859
860 #[test]
861 fn test_display_json2_type_hints_quotes_numeric_segments() {
862 let sql = r#"CREATE TABLE traces (
863 log_json_data JSON2 (
864 "1abc" STRING,
865 a."2b" BIGINT
866 ),
867 ts TIMESTAMP TIME INDEX
868 )"#;
869 let result =
870 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
871 .unwrap();
872
873 match &result[0] {
874 Statement::CreateTable(c) => {
875 let new_sql = format!("\n{}", c);
876 assert_eq!(
877 r#"
878CREATE TABLE traces (
879 log_json_data JSON2(
880 "1abc" STRING,
881 "a"."2b" BIGINT
882 ),
883 ts TIMESTAMP NOT NULL,
884 TIME INDEX (ts)
885)
886ENGINE=mito
887"#,
888 &new_sql
889 );
890
891 let new_result = ParserContext::create_with_dialect(
892 &new_sql,
893 &GreptimeDbDialect {},
894 ParseOptions::default(),
895 )
896 .unwrap();
897 assert_eq!(result, new_result);
898 }
899 _ => unreachable!(),
900 }
901 }
902
903 #[test]
904 fn test_json2_type_hint_rejects_default() {
905 let sql = r#"CREATE TABLE traces (
906 log_json_data JSON2 (
907 status_code BIGINT DEFAULT -5,
908 duration DOUBLE DEFAULT +1.5,
909 error BOOLEAN DEFAULT false,
910 message STRING DEFAULT 'unknown'
911 ),
912 ts TIMESTAMP TIME INDEX
913 )"#;
914 let err =
915 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
916 .unwrap_err();
917 assert!(err.to_string().contains("DEFAULT is not supported"));
918 }
919
920 #[test]
921 fn test_json2_type_hint_rejects_not_null() {
922 let sql = r#"CREATE TABLE traces (
923 log_json_data JSON2 (
924 status_code BIGINT NOT NULL DEFAULT NULL
925 ),
926 ts TIMESTAMP TIME INDEX
927 )"#;
928 let err =
929 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
930 .unwrap_err();
931 assert!(err.to_string().contains("NULL/NOT NULL is not supported"));
932 }
933
934 #[test]
935 fn test_set_json_settings_preserves_type_hint_sql_types() -> Result<()> {
936 let mut extensions = super::ColumnExtensions::default();
937 let settings = JsonSettings::try_new(
938 vec![
939 DatatypeJsonTypeHint {
940 path: vec!["i".to_string()],
941 data_type: ConcreteDataType::int64_datatype(),
942 inverted_index: false,
943 },
944 DatatypeJsonTypeHint {
945 path: vec!["f".to_string()],
946 data_type: ConcreteDataType::float64_datatype(),
947 inverted_index: false,
948 },
949 DatatypeJsonTypeHint {
950 path: vec!["u".to_string()],
951 data_type: ConcreteDataType::uint64_datatype(),
952 inverted_index: false,
953 },
954 DatatypeJsonTypeHint {
955 path: vec!["s".to_string()],
956 data_type: ConcreteDataType::string_datatype(),
957 inverted_index: false,
958 },
959 DatatypeJsonTypeHint {
960 path: vec!["b".to_string()],
961 data_type: ConcreteDataType::boolean_datatype(),
962 inverted_index: false,
963 },
964 ],
965 None,
966 )?;
967 extensions.set_json_settings(settings)?;
968
969 assert_eq!(
970 extensions
971 .json2_options
972 .unwrap()
973 .type_hints
974 .iter()
975 .map(|hint| hint.data_type.to_string())
976 .collect::<Vec<_>>(),
977 vec!["BIGINT", "DOUBLE", "BIGINT UNSIGNED", "STRING", "BOOLEAN"]
978 );
979 Ok(())
980 }
981
982 #[test]
983 fn test_set_json_settings_rejects_unsupported_type_hint_type() -> Result<()> {
984 let err = JsonSettings::try_new(
985 vec![DatatypeJsonTypeHint {
986 path: vec!["u".to_string()],
987 data_type: ConcreteDataType::date_datatype(),
988 inverted_index: false,
989 }],
990 None,
991 )
992 .unwrap_err();
993
994 assert!(
995 err.to_string()
996 .contains("unsupported JSON2 type hint data type")
997 );
998 Ok(())
999 }
1000
1001 #[test]
1002 fn test_set_empty_json_settings_omits_json2_options() -> Result<()> {
1003 let mut extensions = ColumnExtensions::default();
1004 extensions.set_json_settings(JsonSettings::default())?;
1005 assert!(extensions.json2_options.is_none());
1006 Ok(())
1007 }
1008
1009 #[test]
1010 fn test_display_create_database() {
1011 let sql = r"create database test;";
1012 let stmts =
1013 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1014 .unwrap();
1015 assert_eq!(1, stmts.len());
1016 assert_matches!(&stmts[0], Statement::CreateDatabase { .. });
1017
1018 match &stmts[0] {
1019 Statement::CreateDatabase(set) => {
1020 let new_sql = format!("\n{}", set);
1021 assert_eq!(
1022 r#"
1023CREATE DATABASE test"#,
1024 &new_sql
1025 );
1026 }
1027 _ => {
1028 unreachable!();
1029 }
1030 }
1031
1032 let sql = r"create database if not exists test;";
1033 let stmts =
1034 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1035 .unwrap();
1036 assert_eq!(1, stmts.len());
1037 assert_matches!(&stmts[0], Statement::CreateDatabase { .. });
1038
1039 match &stmts[0] {
1040 Statement::CreateDatabase(set) => {
1041 let new_sql = format!("\n{}", set);
1042 assert_eq!(
1043 r#"
1044CREATE DATABASE IF NOT EXISTS test"#,
1045 &new_sql
1046 );
1047 }
1048 _ => {
1049 unreachable!();
1050 }
1051 }
1052
1053 let sql = r#"CREATE DATABASE IF NOT EXISTS test WITH (ttl='1h');"#;
1054 let stmts =
1055 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1056 .unwrap();
1057 assert_eq!(1, stmts.len());
1058 assert_matches!(&stmts[0], Statement::CreateDatabase { .. });
1059
1060 match &stmts[0] {
1061 Statement::CreateDatabase(set) => {
1062 let new_sql = format!("\n{}", set);
1063 assert_eq!(
1064 r#"
1065CREATE DATABASE IF NOT EXISTS test
1066WITH(
1067 ttl = '1h'
1068)"#,
1069 &new_sql
1070 );
1071 }
1072 _ => {
1073 unreachable!();
1074 }
1075 }
1076 }
1077
1078 #[test]
1079 fn test_display_create_table_like() {
1080 let sql = r"create table t2 like t1;";
1081 let stmts =
1082 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1083 .unwrap();
1084 assert_eq!(1, stmts.len());
1085 assert_matches!(&stmts[0], Statement::CreateTableLike { .. });
1086
1087 match &stmts[0] {
1088 Statement::CreateTableLike(create) => {
1089 let new_sql = format!("\n{}", create);
1090 assert_eq!(
1091 r#"
1092CREATE TABLE t2 LIKE t1"#,
1093 &new_sql
1094 );
1095 }
1096 _ => {
1097 unreachable!();
1098 }
1099 }
1100 }
1101
1102 #[test]
1103 fn test_display_create_external_table() {
1104 let sql = r#"CREATE EXTERNAL TABLE city (
1105 host string,
1106 ts timestamp,
1107 cpu float64 default 0,
1108 memory float64,
1109 TIME INDEX (ts),
1110 PRIMARY KEY(host)
1111) WITH (location='/var/data/city.csv', format='csv');"#;
1112 let stmts =
1113 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1114 .unwrap();
1115 assert_eq!(1, stmts.len());
1116 assert_matches!(&stmts[0], Statement::CreateExternalTable { .. });
1117
1118 match &stmts[0] {
1119 Statement::CreateExternalTable(create) => {
1120 let new_sql = format!("\n{}", create);
1121 assert_eq!(
1122 r#"
1123CREATE EXTERNAL TABLE city (
1124 host STRING,
1125 ts TIMESTAMP,
1126 cpu DOUBLE DEFAULT 0,
1127 memory DOUBLE,
1128 TIME INDEX (ts),
1129 PRIMARY KEY (host)
1130)
1131ENGINE=file
1132WITH(
1133 format = 'csv',
1134 location = '/var/data/city.csv'
1135)"#,
1136 &new_sql
1137 );
1138 }
1139 _ => {
1140 unreachable!();
1141 }
1142 }
1143 }
1144
1145 #[test]
1146 fn test_display_create_flow() {
1147 let sql = r"CREATE FLOW filter_numbers
1148 SINK TO out_num_cnt
1149 AS SELECT number FROM numbers_input where number > 10;";
1150 let result =
1151 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1152 .unwrap();
1153 assert_eq!(1, result.len());
1154
1155 match &result[0] {
1156 Statement::CreateFlow(c) => {
1157 let new_sql = format!("\n{}", c);
1158 assert_eq!(
1159 r#"
1160CREATE FLOW filter_numbers
1161SINK TO out_num_cnt
1162AS SELECT number FROM numbers_input where number > 10"#,
1163 &new_sql
1164 );
1165
1166 let new_result = ParserContext::create_with_dialect(
1167 &new_sql,
1168 &GreptimeDbDialect {},
1169 ParseOptions::default(),
1170 )
1171 .unwrap();
1172 assert_eq!(result, new_result);
1173 }
1174 _ => unreachable!(),
1175 }
1176 }
1177}