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