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