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