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