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