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, Value as SqlValue};
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
359#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut)]
360pub struct PartitionEntry {
361 pub name: Ident,
362 pub value_list: Vec<SqlValue>,
363}
364
365impl Display for PartitionEntry {
366 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
367 write!(
368 f,
369 "PARTITION {} VALUES LESS THAN ({})",
370 self.name,
371 format_list_comma!(self.value_list),
372 )
373 }
374}
375
376impl Display for Partitions {
377 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
378 if !self.column_list.is_empty() {
379 write!(
380 f,
381 "PARTITION ON COLUMNS ({}) (\n{}\n)",
382 format_list_comma!(self.column_list),
383 format_list_indent!(self.exprs),
384 )?;
385 }
386 Ok(())
387 }
388}
389
390impl Display for CreateTable {
391 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
392 write!(f, "CREATE ")?;
393 if self.engine == FILE_ENGINE {
394 write!(f, "EXTERNAL ")?;
395 }
396 write!(f, "TABLE ")?;
397 if self.if_not_exists {
398 write!(f, "IF NOT EXISTS ")?;
399 }
400 writeln!(f, "{} (", &self.name)?;
401 writeln!(f, "{},", format_list_indent!(self.columns))?;
402 writeln!(f, "{}", format_table_constraint(&self.constraints))?;
403 writeln!(f, ")")?;
404 if let Some(partitions) = &self.partitions {
405 writeln!(f, "{partitions}")?;
406 }
407 writeln!(f, "ENGINE={}", &self.engine)?;
408 if !self.options.is_empty() {
409 let options = self.options.kv_pairs();
410 write!(f, "WITH(\n{}\n)", format_list_indent!(options))?;
411 }
412 Ok(())
413 }
414}
415
416#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
417pub struct CreateDatabase {
418 pub name: ObjectName,
419 pub if_not_exists: bool,
421 pub options: OptionMap,
422}
423
424impl CreateDatabase {
425 pub fn new(name: ObjectName, if_not_exists: bool, options: OptionMap) -> Self {
427 Self {
428 name,
429 if_not_exists,
430 options,
431 }
432 }
433}
434
435impl Display for CreateDatabase {
436 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
437 write!(f, "CREATE DATABASE ")?;
438 if self.if_not_exists {
439 write!(f, "IF NOT EXISTS ")?;
440 }
441 write!(f, "{}", &self.name)?;
442 if !self.options.is_empty() {
443 let options = self.options.kv_pairs();
444 write!(f, "\nWITH(\n{}\n)", format_list_indent!(options))?;
445 }
446 Ok(())
447 }
448}
449
450#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
451pub struct CreateExternalTable {
452 pub name: ObjectName,
454 pub columns: Vec<Column>,
455 pub constraints: Vec<TableConstraint>,
456 pub options: OptionMap,
458 pub if_not_exists: bool,
459 pub engine: String,
460}
461
462impl Display for CreateExternalTable {
463 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
464 write!(f, "CREATE EXTERNAL TABLE ")?;
465 if self.if_not_exists {
466 write!(f, "IF NOT EXISTS ")?;
467 }
468 writeln!(f, "{} (", &self.name)?;
469 writeln!(f, "{},", format_list_indent!(self.columns))?;
470 writeln!(f, "{}", format_table_constraint(&self.constraints))?;
471 writeln!(f, ")")?;
472 writeln!(f, "ENGINE={}", &self.engine)?;
473 if !self.options.is_empty() {
474 let options = self.options.kv_pairs();
475 write!(f, "WITH(\n{}\n)", format_list_indent!(options))?;
476 }
477 Ok(())
478 }
479}
480
481#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
482pub struct CreateTableLike {
483 pub table_name: ObjectName,
485 pub source_name: ObjectName,
487}
488
489impl Display for CreateTableLike {
490 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
491 let table_name = &self.table_name;
492 let source_name = &self.source_name;
493 write!(f, r#"CREATE TABLE {table_name} LIKE {source_name}"#)
494 }
495}
496
497#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
498pub struct CreateFlow {
499 pub flow_name: ObjectName,
501 pub sink_table_name: ObjectName,
503 pub or_replace: bool,
505 pub if_not_exists: bool,
507 pub expire_after: Option<i64>,
510 pub eval_interval: Option<i64>,
514 pub eval_offset: Option<i64>,
520 pub comment: Option<String>,
522 pub flow_options: OptionMap,
524 pub query: Box<SqlOrTql>,
526}
527
528#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
530pub enum SqlOrTql {
531 Sql(GtQuery, String),
532 Tql(Tql, String),
533}
534
535impl std::fmt::Display for SqlOrTql {
536 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
537 match self {
538 Self::Sql(_, s) => write!(f, "{}", s),
539 Self::Tql(_, s) => write!(f, "{}", s),
540 }
541 }
542}
543
544impl SqlOrTql {
545 pub fn try_from_statement(
546 value: Statement,
547 original_query: &str,
548 ) -> std::result::Result<Self, crate::error::Error> {
549 match value {
550 Statement::Query(query) => Ok(Self::Sql(*query, original_query.to_string())),
551 Statement::Tql(tql) => Ok(Self::Tql(tql, original_query.to_string())),
552 _ => InvalidFlowQuerySnafu {
553 reason: format!("Expect either sql query or promql query, found {:?}", value),
554 }
555 .fail(),
556 }
557 }
558}
559
560impl Display for CreateFlow {
561 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
562 write!(f, "CREATE ")?;
563 if self.or_replace {
564 write!(f, "OR REPLACE ")?;
565 }
566 write!(f, "FLOW ")?;
567 if self.if_not_exists {
568 write!(f, "IF NOT EXISTS ")?;
569 }
570 writeln!(f, "{}", &self.flow_name)?;
571 writeln!(f, "SINK TO {}", &self.sink_table_name)?;
572 if let Some(expire_after) = &self.expire_after {
573 writeln!(f, "EXPIRE AFTER '{} s'", expire_after)?;
574 }
575 if let Some(eval_interval) = &self.eval_interval {
576 writeln!(f, "EVAL INTERVAL '{} s'", eval_interval)?;
577 }
578 if let Some(eval_offset) = &self.eval_offset
581 && *eval_offset != 0
582 {
583 writeln!(f, "EVAL OFFSET '{} s'", eval_offset)?;
584 }
585 if let Some(comment) = &self.comment {
586 writeln!(f, "COMMENT '{}'", comment)?;
587 }
588 if !self.flow_options.is_empty() {
589 let options = self.flow_options.kv_pairs();
590 writeln!(f, "WITH ({})", format_list_comma!(options))?;
591 }
592 write!(f, "AS {}", &self.query)
593 }
594}
595
596#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
598pub struct CreateView {
599 pub name: ObjectName,
601 pub columns: Vec<Ident>,
603 pub query: Box<Statement>,
606 pub or_replace: bool,
608 pub if_not_exists: bool,
610}
611
612impl Display for CreateView {
613 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
614 write!(f, "CREATE ")?;
615 if self.or_replace {
616 write!(f, "OR REPLACE ")?;
617 }
618 write!(f, "VIEW ")?;
619 if self.if_not_exists {
620 write!(f, "IF NOT EXISTS ")?;
621 }
622 write!(f, "{} ", &self.name)?;
623 if !self.columns.is_empty() {
624 write!(f, "({}) ", format_list_comma!(self.columns))?;
625 }
626 write!(f, "AS {}", &self.query)
627 }
628}
629
630#[cfg(test)]
631mod tests {
632 use std::assert_matches;
633
634 use datatypes::json::{JsonSettings, JsonTypeHint as DatatypeJsonTypeHint};
635 use datatypes::prelude::ConcreteDataType;
636
637 use super::*;
638 use crate::dialect::GreptimeDbDialect;
639 use crate::error::Error;
640 use crate::parser::{ParseOptions, ParserContext};
641 use crate::statements::statement::Statement;
642
643 #[test]
644 fn test_display_create_table() {
645 let sql = r"create table if not exists demo(
646 host string,
647 ts timestamp,
648 cpu double default 0,
649 memory double,
650 TIME INDEX (ts),
651 PRIMARY KEY(host)
652 )
653 PARTITION ON COLUMNS (host) (
654 host = 'a',
655 host > 'a',
656 )
657 engine=mito
658 with(ttl='7d', storage='File');
659 ";
660 let result =
661 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
662 .unwrap();
663 assert_eq!(1, result.len());
664
665 match &result[0] {
666 Statement::CreateTable(c) => {
667 let new_sql = format!("\n{}", c);
668 assert_eq!(
669 r#"
670CREATE TABLE IF NOT EXISTS demo (
671 host STRING,
672 ts TIMESTAMP,
673 cpu DOUBLE DEFAULT 0,
674 memory DOUBLE,
675 TIME INDEX (ts),
676 PRIMARY KEY (host)
677)
678PARTITION ON COLUMNS (host) (
679 host = 'a',
680 host > 'a'
681)
682ENGINE=mito
683WITH(
684 storage = 'File',
685 ttl = '7d'
686)"#,
687 &new_sql
688 );
689
690 let new_result = ParserContext::create_with_dialect(
691 &new_sql,
692 &GreptimeDbDialect {},
693 ParseOptions::default(),
694 )
695 .unwrap();
696 assert_eq!(result, new_result);
697 }
698 _ => unreachable!(),
699 }
700 }
701
702 #[test]
703 fn test_display_empty_partition_column() {
704 let sql = r"create table if not exists demo(
705 host string,
706 ts timestamp,
707 cpu double default 0,
708 memory double,
709 TIME INDEX (ts),
710 PRIMARY KEY(ts, host)
711 );
712 ";
713 let result =
714 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
715 .unwrap();
716 assert_eq!(1, result.len());
717
718 match &result[0] {
719 Statement::CreateTable(c) => {
720 let new_sql = format!("\n{}", c);
721 assert_eq!(
722 r#"
723CREATE TABLE IF NOT EXISTS demo (
724 host STRING,
725 ts TIMESTAMP,
726 cpu DOUBLE DEFAULT 0,
727 memory DOUBLE,
728 TIME INDEX (ts),
729 PRIMARY KEY (ts, host)
730)
731ENGINE=mito
732"#,
733 &new_sql
734 );
735
736 let new_result = ParserContext::create_with_dialect(
737 &new_sql,
738 &GreptimeDbDialect {},
739 ParseOptions::default(),
740 )
741 .unwrap();
742 assert_eq!(result, new_result);
743 }
744 _ => unreachable!(),
745 }
746 }
747
748 #[test]
749 fn test_validate_table_options() {
750 let sql = r"create table if not exists demo(
751 host string,
752 ts timestamp,
753 cpu double default 0,
754 memory double,
755 TIME INDEX (ts),
756 PRIMARY KEY(host)
757 )
758 PARTITION ON COLUMNS (host) ()
759 engine=mito
760 with(ttl='7d', 'compaction.type'='world');
761";
762 let result =
763 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
764 .unwrap();
765 match &result[0] {
766 Statement::CreateTable(c) => {
767 assert_eq!(2, c.options.len());
768 }
769 _ => unreachable!(),
770 }
771
772 let sql = r"create table if not exists demo(
773 host string,
774 ts timestamp,
775 cpu double default 0,
776 memory double,
777 TIME INDEX (ts),
778 PRIMARY KEY(host)
779 )
780 PARTITION ON COLUMNS (host) ()
781 engine=mito
782 with(ttl='7d', hello='world');
783";
784 let result =
785 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
786 assert_matches!(result, Err(Error::InvalidTableOption { .. }));
787
788 let semantic = |with: &str| {
790 let sql =
791 format!("create table demo(host string, ts timestamp time index) with({with});");
792 ParserContext::create_with_dialect(&sql, &GreptimeDbDialect {}, ParseOptions::default())
793 };
794 assert!(semantic("'greptime.semantic.signal_type'='metric'").is_ok());
795 assert_matches!(
797 semantic("'greptime.semantic.signal_type'='spans'"),
798 Err(Error::InvalidTableOption { .. })
799 );
800 assert_matches!(
802 semantic("'greptime.semantic.bogus'='x'"),
803 Err(Error::InvalidTableOption { .. })
804 );
805 }
806
807 #[test]
808 fn test_display_json2_type_hints_quotes_path_segments() {
809 let sql = r#"CREATE 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 TIME INDEX
816 )"#;
817 let result =
818 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
819 .unwrap();
820
821 match &result[0] {
822 Statement::CreateTable(c) => {
823 let new_sql = format!("\n{}", c);
824 assert_eq!(
825 r#"
826CREATE TABLE traces (
827 log_json_data JSON2(
828 "service.name" STRING,
829 "a.b"."c" BIGINT,
830 "a"."b.c" STRING
831 ),
832 ts TIMESTAMP NOT NULL,
833 TIME INDEX (ts)
834)
835ENGINE=mito
836"#,
837 &new_sql
838 );
839
840 let new_result = ParserContext::create_with_dialect(
841 &new_sql,
842 &GreptimeDbDialect {},
843 ParseOptions::default(),
844 )
845 .unwrap();
846 assert_eq!(result, new_result);
847 }
848 _ => unreachable!(),
849 }
850 }
851
852 #[test]
853 fn test_parse_json2_max_auto_expanded_paths_option() -> Result<()> {
854 let sql = r#"CREATE TABLE traces (
855 log_json_data JSON2 (
856 status_code BIGINT,
857 max_auto_expanded_paths = 1
858 ),
859 ts TIMESTAMP TIME INDEX
860 )"#;
861 let result = ParserContext::create_with_dialect(
862 sql,
863 &GreptimeDbDialect {},
864 ParseOptions::default(),
865 )?;
866 let Statement::CreateTable(create_table) = &result[0] else {
867 unreachable!()
868 };
869 let settings = create_table.columns[0]
870 .extensions
871 .build_json_settings()?
872 .unwrap();
873 assert_eq!(settings.max_auto_expanded_paths(), Some(1));
874 Ok(())
875 }
876
877 #[test]
878 fn test_display_json2_type_hints_quotes_numeric_segments() {
879 let sql = r#"CREATE TABLE traces (
880 log_json_data JSON2 (
881 "1abc" STRING,
882 a."2b" BIGINT
883 ),
884 ts TIMESTAMP TIME INDEX
885 )"#;
886 let result =
887 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
888 .unwrap();
889
890 match &result[0] {
891 Statement::CreateTable(c) => {
892 let new_sql = format!("\n{}", c);
893 assert_eq!(
894 r#"
895CREATE TABLE traces (
896 log_json_data JSON2(
897 "1abc" STRING,
898 "a"."2b" BIGINT
899 ),
900 ts TIMESTAMP NOT NULL,
901 TIME INDEX (ts)
902)
903ENGINE=mito
904"#,
905 &new_sql
906 );
907
908 let new_result = ParserContext::create_with_dialect(
909 &new_sql,
910 &GreptimeDbDialect {},
911 ParseOptions::default(),
912 )
913 .unwrap();
914 assert_eq!(result, new_result);
915 }
916 _ => unreachable!(),
917 }
918 }
919
920 #[test]
921 fn test_json2_type_hint_rejects_default() {
922 let sql = r#"CREATE TABLE traces (
923 log_json_data JSON2 (
924 status_code BIGINT DEFAULT -5,
925 duration DOUBLE DEFAULT +1.5,
926 error BOOLEAN DEFAULT false,
927 message STRING DEFAULT 'unknown'
928 ),
929 ts TIMESTAMP TIME INDEX
930 )"#;
931 let err =
932 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
933 .unwrap_err();
934 assert!(err.to_string().contains("DEFAULT is not supported"));
935 }
936
937 #[test]
938 fn test_json2_type_hint_rejects_not_null() {
939 let sql = r#"CREATE TABLE traces (
940 log_json_data JSON2 (
941 status_code BIGINT NOT NULL DEFAULT NULL
942 ),
943 ts TIMESTAMP TIME INDEX
944 )"#;
945 let err =
946 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
947 .unwrap_err();
948 assert!(err.to_string().contains("NULL/NOT NULL is not supported"));
949 }
950
951 #[test]
952 fn test_set_json_settings_preserves_type_hint_sql_types() -> Result<()> {
953 let mut extensions = super::ColumnExtensions::default();
954 let settings = JsonSettings::try_new(
955 vec![
956 DatatypeJsonTypeHint {
957 path: vec!["i".to_string()],
958 data_type: ConcreteDataType::int64_datatype(),
959 inverted_index: false,
960 },
961 DatatypeJsonTypeHint {
962 path: vec!["f".to_string()],
963 data_type: ConcreteDataType::float64_datatype(),
964 inverted_index: false,
965 },
966 DatatypeJsonTypeHint {
967 path: vec!["u".to_string()],
968 data_type: ConcreteDataType::uint64_datatype(),
969 inverted_index: false,
970 },
971 DatatypeJsonTypeHint {
972 path: vec!["s".to_string()],
973 data_type: ConcreteDataType::string_datatype(),
974 inverted_index: false,
975 },
976 DatatypeJsonTypeHint {
977 path: vec!["b".to_string()],
978 data_type: ConcreteDataType::boolean_datatype(),
979 inverted_index: false,
980 },
981 ],
982 None,
983 )?;
984 extensions.set_json_settings(settings)?;
985
986 assert_eq!(
987 extensions
988 .json2_options
989 .unwrap()
990 .type_hints
991 .iter()
992 .map(|hint| hint.data_type.to_string())
993 .collect::<Vec<_>>(),
994 vec!["BIGINT", "DOUBLE", "BIGINT UNSIGNED", "STRING", "BOOLEAN"]
995 );
996 Ok(())
997 }
998
999 #[test]
1000 fn test_set_json_settings_rejects_unsupported_type_hint_type() -> Result<()> {
1001 let err = JsonSettings::try_new(
1002 vec![DatatypeJsonTypeHint {
1003 path: vec!["u".to_string()],
1004 data_type: ConcreteDataType::date_datatype(),
1005 inverted_index: false,
1006 }],
1007 None,
1008 )
1009 .unwrap_err();
1010
1011 assert!(
1012 err.to_string()
1013 .contains("unsupported JSON2 type hint data type")
1014 );
1015 Ok(())
1016 }
1017
1018 #[test]
1019 fn test_set_empty_json_settings_omits_json2_options() -> Result<()> {
1020 let mut extensions = ColumnExtensions::default();
1021 extensions.set_json_settings(JsonSettings::default())?;
1022 assert!(extensions.json2_options.is_none());
1023 Ok(())
1024 }
1025
1026 #[test]
1027 fn test_display_create_database() {
1028 let sql = r"create database test;";
1029 let stmts =
1030 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1031 .unwrap();
1032 assert_eq!(1, stmts.len());
1033 assert_matches!(&stmts[0], Statement::CreateDatabase { .. });
1034
1035 match &stmts[0] {
1036 Statement::CreateDatabase(set) => {
1037 let new_sql = format!("\n{}", set);
1038 assert_eq!(
1039 r#"
1040CREATE DATABASE test"#,
1041 &new_sql
1042 );
1043 }
1044 _ => {
1045 unreachable!();
1046 }
1047 }
1048
1049 let sql = r"create database if not exists test;";
1050 let stmts =
1051 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1052 .unwrap();
1053 assert_eq!(1, stmts.len());
1054 assert_matches!(&stmts[0], Statement::CreateDatabase { .. });
1055
1056 match &stmts[0] {
1057 Statement::CreateDatabase(set) => {
1058 let new_sql = format!("\n{}", set);
1059 assert_eq!(
1060 r#"
1061CREATE DATABASE IF NOT EXISTS test"#,
1062 &new_sql
1063 );
1064 }
1065 _ => {
1066 unreachable!();
1067 }
1068 }
1069
1070 let sql = r#"CREATE DATABASE IF NOT EXISTS test WITH (ttl='1h');"#;
1071 let stmts =
1072 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1073 .unwrap();
1074 assert_eq!(1, stmts.len());
1075 assert_matches!(&stmts[0], Statement::CreateDatabase { .. });
1076
1077 match &stmts[0] {
1078 Statement::CreateDatabase(set) => {
1079 let new_sql = format!("\n{}", set);
1080 assert_eq!(
1081 r#"
1082CREATE DATABASE IF NOT EXISTS test
1083WITH(
1084 ttl = '1h'
1085)"#,
1086 &new_sql
1087 );
1088 }
1089 _ => {
1090 unreachable!();
1091 }
1092 }
1093 }
1094
1095 #[test]
1096 fn test_display_create_table_like() {
1097 let sql = r"create table t2 like t1;";
1098 let stmts =
1099 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1100 .unwrap();
1101 assert_eq!(1, stmts.len());
1102 assert_matches!(&stmts[0], Statement::CreateTableLike { .. });
1103
1104 match &stmts[0] {
1105 Statement::CreateTableLike(create) => {
1106 let new_sql = format!("\n{}", create);
1107 assert_eq!(
1108 r#"
1109CREATE TABLE t2 LIKE t1"#,
1110 &new_sql
1111 );
1112 }
1113 _ => {
1114 unreachable!();
1115 }
1116 }
1117 }
1118
1119 #[test]
1120 fn test_display_create_external_table() {
1121 let sql = r#"CREATE EXTERNAL TABLE city (
1122 host string,
1123 ts timestamp,
1124 cpu float64 default 0,
1125 memory float64,
1126 TIME INDEX (ts),
1127 PRIMARY KEY(host)
1128) WITH (location='/var/data/city.csv', format='csv');"#;
1129 let stmts =
1130 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1131 .unwrap();
1132 assert_eq!(1, stmts.len());
1133 assert_matches!(&stmts[0], Statement::CreateExternalTable { .. });
1134
1135 match &stmts[0] {
1136 Statement::CreateExternalTable(create) => {
1137 let new_sql = format!("\n{}", create);
1138 assert_eq!(
1139 r#"
1140CREATE EXTERNAL TABLE city (
1141 host STRING,
1142 ts TIMESTAMP,
1143 cpu DOUBLE DEFAULT 0,
1144 memory DOUBLE,
1145 TIME INDEX (ts),
1146 PRIMARY KEY (host)
1147)
1148ENGINE=file
1149WITH(
1150 format = 'csv',
1151 location = '/var/data/city.csv'
1152)"#,
1153 &new_sql
1154 );
1155 }
1156 _ => {
1157 unreachable!();
1158 }
1159 }
1160 }
1161
1162 #[test]
1163 fn test_display_create_flow() {
1164 let sql = r"CREATE FLOW filter_numbers
1165 SINK TO out_num_cnt
1166 AS SELECT number FROM numbers_input where number > 10;";
1167 let result =
1168 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1169 .unwrap();
1170 assert_eq!(1, result.len());
1171
1172 match &result[0] {
1173 Statement::CreateFlow(c) => {
1174 let new_sql = format!("\n{}", c);
1175 assert_eq!(
1176 r#"
1177CREATE FLOW filter_numbers
1178SINK TO out_num_cnt
1179AS SELECT number FROM numbers_input where number > 10"#,
1180 &new_sql
1181 );
1182
1183 let new_result = ParserContext::create_with_dialect(
1184 &new_sql,
1185 &GreptimeDbDialect {},
1186 ParseOptions::default(),
1187 )
1188 .unwrap();
1189 assert_eq!(result, new_result);
1190 }
1191 _ => unreachable!(),
1192 }
1193 }
1194}