From 48a0a3ae165419c431c651ed6d9dbfdfa8c94c74 Mon Sep 17 00:00:00 2001 From: irenjj Date: Fri, 19 Apr 2024 11:44:57 +0800 Subject: [PATCH 01/17] feat: impl Display for Statement --- src/common/query/src/lib.rs | 13 ++- src/sql/src/statements/alter.rs | 32 ++++++ src/sql/src/statements/copy.rs | 137 ++++++++++++++++++++++ src/sql/src/statements/create.rs | 106 +++++++++++++++++ src/sql/src/statements/describe.rs | 9 ++ src/sql/src/statements/drop.rs | 36 ++++++ src/sql/src/statements/set_variables.rs | 24 ++++ src/sql/src/statements/show.rs | 145 +++++++++++++++++++++++- src/sql/src/statements/statement.rs | 31 +++++ src/sql/src/statements/tql.rs | 104 +++++++++++++++++ src/sql/src/statements/truncate.rs | 9 ++ 11 files changed, 642 insertions(+), 4 deletions(-) diff --git a/src/common/query/src/lib.rs b/src/common/query/src/lib.rs index cb39d37fb444..5e562ee7fe83 100644 --- a/src/common/query/src/lib.rs +++ b/src/common/query/src/lib.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fmt::{Debug, Formatter}; +use std::fmt::{Debug, Display, Formatter}; use std::sync::Arc; use api::greptime_proto::v1::add_column_location::LocationType; @@ -126,6 +126,17 @@ pub enum AddColumnLocation { After { column_name: String }, } +impl Display for AddColumnLocation { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + AddColumnLocation::First => write!(f, r#"FIRST"#), + AddColumnLocation::After { column_name } => { + write!(f, r#"AFTER {column_name}"#) + } + } + } +} + impl From<&AddColumnLocation> for Location { fn from(value: &AddColumnLocation) -> Self { match value { diff --git a/src/sql/src/statements/alter.rs b/src/sql/src/statements/alter.rs index cf3dc1bf9179..b61ea3102fe3 100644 --- a/src/sql/src/statements/alter.rs +++ b/src/sql/src/statements/alter.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt::{Debug, Display}; + use common_query::AddColumnLocation; use sqlparser::ast::{ColumnDef, Ident, ObjectName, TableConstraint}; use sqlparser_derive::{Visit, VisitMut}; @@ -39,6 +41,14 @@ impl AlterTable { } } +impl Display for AlterTable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let table_name = self.table_name(); + let alter_operation = self.alter_operation(); + write!(f, r#"ALTER TABLE {table_name} {alter_operation}"#) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)] pub enum AlterTableOperation { /// `ADD ` @@ -53,3 +63,25 @@ pub enum AlterTableOperation { /// `RENAME ` RenameTable { new_table_name: String }, } + +impl Display for AlterTableOperation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AlterTableOperation::AddConstraint(constraint) => write!(f, r#"ADD {constraint}"#), + AlterTableOperation::AddColumn { + column_def, + location, + } => { + if let Some(location) = location { + write!(f, r#"ADD {column_def} {location}"#) + } else { + write!(f, r#"ADD {column_def}"#) + } + } + AlterTableOperation::DropColumn { name } => write!(f, r#"DROP COLUMN {name}"#), + AlterTableOperation::RenameTable { new_table_name } => { + write!(f, r#"RENAME {new_table_name}"#) + } + } + } +} diff --git a/src/sql/src/statements/copy.rs b/src/sql/src/statements/copy.rs index 8d3104f29e69..22759055ca28 100644 --- a/src/sql/src/statements/copy.rs +++ b/src/sql/src/statements/copy.rs @@ -12,29 +12,106 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt::Display; + use sqlparser::ast::ObjectName; use sqlparser_derive::{Visit, VisitMut}; use crate::statements::OptionMap; +macro_rules! format_sorted_hashmap { + ($hashmap:expr) => {{ + let hashmap = $hashmap; + let mut sorted_keys: Vec<&String> = hashmap.keys().collect(); + sorted_keys.sort(); + let mut result = String::new(); + for key in sorted_keys { + if let Some(val) = hashmap.get(key) { + result.push_str(&format!("{} = {}, ", key, val)); + } + } + result + }}; +} + #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)] pub enum Copy { CopyTable(CopyTable), CopyDatabase(CopyDatabase), } +impl Display for Copy { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Copy::CopyTable(s) => s.fmt(f), + Copy::CopyDatabase(s) => s.fmt(f), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)] pub enum CopyTable { To(CopyTableArgument), From(CopyTableArgument), } +impl Display for CopyTable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CopyTable::To(s) => { + let table_name = &s.table_name; + let with = s.format_with(); + let connection = s.format_connection(); + let location = &s.location; + write!(f, r#"COPY {table_name} TO {location} {with} {connection}"#) + } + CopyTable::From(s) => { + let table_name = &s.table_name; + let with = s.format_with(); + let connection = s.format_connection(); + let location = &s.location; + write!( + f, + r#"COPY {table_name} FROM {location} {with} {connection}"# + ) + } + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)] pub enum CopyDatabase { To(CopyDatabaseArgument), From(CopyDatabaseArgument), } +impl Display for CopyDatabase { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CopyDatabase::To(s) => { + let database_name = &s.database_name; + let with = s.format_with(); + let connection = s.format_connection(); + let location = &s.location; + write!( + f, + r#"COPY DATABASE {database_name} TO {location} {with} {connection}"# + ) + } + CopyDatabase::From(s) => { + let database_name = &s.database_name; + let with = s.format_with(); + let connection = s.format_connection(); + let location = &s.location; + write!( + f, + r#"COPY DATABASE {database_name} FROM {location} {with} {connection}"# + ) + } + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)] pub struct CopyDatabaseArgument { pub database_name: ObjectName, @@ -43,6 +120,36 @@ pub struct CopyDatabaseArgument { pub location: String, } +impl CopyDatabaseArgument { + #[inline] + fn format_with(&self) -> String { + if self.with.map.is_empty() { + String::default() + } else { + let options = format_sorted_hashmap!(&self.with.map); + format!( + r#"WITH( +{options} +)"# + ) + } + } + + #[inline] + fn format_connection(&self) -> String { + if self.connection.map.is_empty() { + String::default() + } else { + let options = format_sorted_hashmap!(&self.connection.map); + format!( + r#"CONNECTION( +{options} +)"# + ) + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)] pub struct CopyTableArgument { pub table_name: ObjectName, @@ -52,6 +159,36 @@ pub struct CopyTableArgument { pub location: String, } +impl CopyTableArgument { + #[inline] + fn format_with(&self) -> String { + if self.with.map.is_empty() { + String::default() + } else { + let options = format_sorted_hashmap!(&self.with.map); + format!( + r#"WITH( +{options} +)"# + ) + } + } + + #[inline] + fn format_connection(&self) -> String { + if self.connection.map.is_empty() { + String::default() + } else { + let options = format_sorted_hashmap!(&self.connection.map); + format!( + r#"CONNECTION( +{options} +)"# + ) + } + } +} + #[cfg(test)] impl CopyTableArgument { pub fn format(&self) -> Option { diff --git a/src/sql/src/statements/create.rs b/src/sql/src/statements/create.rs index cfcbd8d68242..f0ee1affb9f3 100644 --- a/src/sql/src/statements/create.rs +++ b/src/sql/src/statements/create.rs @@ -47,6 +47,21 @@ macro_rules! format_list_comma { }; } +macro_rules! format_sorted_hashmap { + ($hashmap:expr) => {{ + let hashmap = $hashmap; + let mut sorted_keys: Vec<&String> = hashmap.keys().collect(); + sorted_keys.sort(); + let mut result = String::new(); + for key in sorted_keys { + if let Some(val) = hashmap.get(key) { + result.push_str(&format!("{} = {}, ", key, val)); + } + } + result + }}; +} + /// Time index name, used in table constraints. pub const TIME_INDEX: &str = "__time_index"; @@ -214,6 +229,27 @@ impl CreateDatabase { if_not_exists, } } + + pub fn name(&self) -> &ObjectName { + &self.name + } + + #[inline] + fn format_if_not_exists(&self) -> &str { + if self.if_not_exists { + "IF NOT EXISTS" + } else { + "" + } + } +} + +impl Display for CreateDatabase { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let if_not_exists = self.format_if_not_exists(); + let name = self.name(); + write!(f, r#"CREATE DATABASE {if_not_exists} {name}"#) + } } #[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut)] @@ -229,6 +265,68 @@ pub struct CreateExternalTable { pub engine: String, } +impl CreateExternalTable { + fn format_constraints(&self) -> String { + self.constraints + .iter() + .map(|c| { + if is_time_index(c) { + let TableConstraint::Unique { columns, .. } = c else { + unreachable!() + }; + + format_indent!("{}TIME INDEX ({})", format_list_comma!(columns)) + } else { + format_indent!(c) + } + }) + .join(LINE_SEP) + } + + #[inline] + fn format_if_not_exists(&self) -> &str { + if self.if_not_exists { + "IF NOT EXISTS" + } else { + "" + } + } + + #[inline] + fn format_options(&self) -> String { + if self.options.map.is_empty() { + String::default() + } else { + let options = format_sorted_hashmap!(&self.options.map); + format!( + r#"WITH( +{options} +)"# + ) + } + } +} + +impl Display for CreateExternalTable { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let name = &self.name; + let columns = format_list_indent!(self.columns); + let constraints = self.format_constraints(); + let options = self.format_options(); + let if_not_exists = self.format_if_not_exists(); + let engine = &self.engine; + write!( + f, + r#"CREATE EXTERNAL TABLE {if_not_exists} {name} ( +{columns}, +{constraints} +) +ENGINE={engine} +{options}"# + ) + } +} + #[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut)] pub struct CreateTableLike { /// Table name @@ -237,6 +335,14 @@ pub struct CreateTableLike { pub source_name: ObjectName, } +impl Display for CreateTableLike { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let table_name = &self.table_name; + let source_name = &self.source_name; + write!(f, r#"CREATE TABLE {table_name} LIKE {source_name}"#) + } +} + #[cfg(test)] mod tests { use std::assert_matches::assert_matches; diff --git a/src/sql/src/statements/describe.rs b/src/sql/src/statements/describe.rs index 6fe190800900..71f12b38d58e 100644 --- a/src/sql/src/statements/describe.rs +++ b/src/sql/src/statements/describe.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt::Display; + use sqlparser::ast::ObjectName; use sqlparser_derive::{Visit, VisitMut}; @@ -32,6 +34,13 @@ impl DescribeTable { } } +impl Display for DescribeTable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = self.name(); + write!(f, r#"DESCRIBEE TABLE {name}"#) + } +} + #[cfg(test)] mod tests { use std::assert_matches::assert_matches; diff --git a/src/sql/src/statements/drop.rs b/src/sql/src/statements/drop.rs index 62da68a90c9c..8d5b6b43a8b5 100644 --- a/src/sql/src/statements/drop.rs +++ b/src/sql/src/statements/drop.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt::Display; + use sqlparser::ast::ObjectName; use sqlparser_derive::{Visit, VisitMut}; @@ -39,6 +41,23 @@ impl DropTable { pub fn drop_if_exists(&self) -> bool { self.drop_if_exists } + + #[inline] + fn format_if_exists(&self) -> &str { + if self.drop_if_exists { + "IF EXISTS" + } else { + "" + } + } +} + +impl Display for DropTable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let if_exists = self.format_if_exists(); + let table_name = self.table_name(); + write!(f, r#"DROP TABLE {if_exists} {table_name}"#) + } } /// DROP DATABASE statement. @@ -65,4 +84,21 @@ impl DropDatabase { pub fn drop_if_exists(&self) -> bool { self.drop_if_exists } + + #[inline] + fn format_if_exists(&self) -> &str { + if self.drop_if_exists { + "IF EXISTS" + } else { + "" + } + } +} + +impl Display for DropDatabase { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let if_exists = self.format_if_exists(); + let name = self.name(); + write!(f, r#"DROP DATABASE {if_exists} {name}"#) + } } diff --git a/src/sql/src/statements/set_variables.rs b/src/sql/src/statements/set_variables.rs index 71d6849833a8..a660125a2537 100644 --- a/src/sql/src/statements/set_variables.rs +++ b/src/sql/src/statements/set_variables.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt::Display; + use sqlparser::ast::{Expr, ObjectName}; use sqlparser_derive::{Visit, VisitMut}; @@ -21,3 +23,25 @@ pub struct SetVariables { pub variable: ObjectName, pub value: Vec, } + +impl SetVariables { + pub fn variable(&self) -> &ObjectName { + &self.variable + } + + pub fn format_value(&self) -> String { + self.value + .iter() + .map(|expr| format!("{}", expr)) + .collect::>() + .join(", ") + } +} + +impl Display for SetVariables { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let variable = self.variable(); + let value = self.format_value(); + write!(f, r#"SET {variable} = {value}"#) + } +} diff --git a/src/sql/src/statements/show.rs b/src/sql/src/statements/show.rs index 13cbb2f69ce0..2c8b0de8baad 100644 --- a/src/sql/src/statements/show.rs +++ b/src/sql/src/statements/show.rs @@ -1,5 +1,3 @@ -// Copyright 2023 Greptime Team -// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,7 +10,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fmt; +use std::fmt::{self, Display}; use sqlparser_derive::{Visit, VisitMut}; @@ -51,6 +49,48 @@ pub struct ShowColumns { pub full: bool, } +impl ShowColumns { + pub fn kind(&self) -> &ShowKind { + &self.kind + } + + pub fn table(&self) -> &String { + &self.table + } + + #[inline] + fn format_table(&self) -> String { + format!("IN {}", self.table) + } + + #[inline] + fn format_database(&self) -> String { + match &self.database { + Some(database) => format!("IN {}", database), + None => String::default(), + } + } + + #[inline] + fn format_full(&self) -> &str { + if self.full { + "FULL" + } else { + "" + } + } +} + +impl Display for ShowColumns { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let kind = self.kind(); + let table = self.format_table(); + let database = self.format_database(); + let full = self.format_full(); + write!(f, r#"SHOW {full} {table} {database} {kind}"#) + } +} + /// The SQL `SHOW INDEX` statement #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)] pub struct ShowIndex { @@ -59,11 +99,50 @@ pub struct ShowIndex { pub database: Option, } +impl ShowIndex { + pub fn kind(&self) -> &ShowKind { + &self.kind + } + + #[inline] + fn format_table(&self) -> String { + format!("IN {}", self.table) + } + + #[inline] + fn format_database(&self) -> String { + match &self.database { + Some(database) => format!("IN {}", database), + None => String::default(), + } + } +} + +impl Display for ShowIndex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let kind = self.kind(); + let table = self.format_table(); + let database = self.format_database(); + write!(f, r#"SHOW INDEX {table} {database} {kind}"#) + } +} + impl ShowDatabases { /// Creates a statement for `SHOW DATABASES` pub fn new(kind: ShowKind) -> Self { ShowDatabases { kind } } + + pub fn kind(&self) -> &ShowKind { + &self.kind + } +} + +impl Display for ShowDatabases { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let kind = self.kind(); + write!(f, r#"SHOW DATABASES {kind}"#) + } } /// SQL structure for `SHOW TABLES`. @@ -74,18 +153,78 @@ pub struct ShowTables { pub full: bool, } +impl ShowTables { + pub fn kind(&self) -> &ShowKind { + &self.kind + } + + #[inline] + fn format_database(&self) -> String { + match &self.database { + None => String::default(), + Some(database) => { + format!("IN {}", database) + } + } + } + + #[inline] + fn format_full(&self) -> &str { + if self.full { + "FULL" + } else { + "" + } + } +} + +impl Display for ShowTables { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let full = self.format_full(); + let database = self.format_database(); + let kind = self.kind(); + write!(f, r#"SHOW {full} TABLES {database} {kind}"#) + } +} + /// SQL structure for `SHOW CREATE TABLE`. #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)] pub struct ShowCreateTable { pub table_name: ObjectName, } +impl ShowCreateTable { + fn table_name(&self) -> &ObjectName { + &self.table_name + } +} + +impl Display for ShowCreateTable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let table_name = self.table_name(); + write!(f, r#"SHOW CREATE {table_name}"#) + } +} + /// SQL structure for `SHOW VARIABLES xxx`. #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)] pub struct ShowVariables { pub variable: ObjectName, } +impl ShowVariables { + fn variable(&self) -> &ObjectName { + &self.variable + } +} + +impl Display for ShowVariables { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let variable = self.variable(); + write!(f, r#"SHOW VARIABLES {variable}"#) + } +} + #[cfg(test)] mod tests { use std::assert_matches::assert_matches; diff --git a/src/sql/src/statements/statement.rs b/src/sql/src/statements/statement.rs index 54ca637283ef..25313da83731 100644 --- a/src/sql/src/statements/statement.rs +++ b/src/sql/src/statements/statement.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt::Display; + use datafusion_sql::parser::Statement as DfStatement; use sqlparser::ast::Statement as SpStatement; use sqlparser_derive::{Visit, VisitMut}; @@ -83,6 +85,35 @@ pub enum Statement { ShowVariables(ShowVariables), } +impl Display for Statement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Statement::Query(s) => s.inner.fmt(f), + Statement::Insert(s) => s.inner.fmt(f), + Statement::Delete(s) => s.inner.fmt(f), + Statement::CreateTable(s) => s.fmt(f), + Statement::CreateExternalTable(s) => s.fmt(f), + Statement::CreateTableLike(s) => s.fmt(f), + Statement::DropTable(s) => s.fmt(f), + Statement::DropDatabase(s) => s.fmt(f), + Statement::CreateDatabase(s) => s.fmt(f), + Statement::Alter(s) => s.fmt(f), + Statement::ShowDatabases(s) => s.fmt(f), + Statement::ShowTables(s) => s.fmt(f), + Statement::ShowColumns(s) => s.fmt(f), + Statement::ShowIndex(s) => s.fmt(f), + Statement::ShowCreateTable(s) => s.fmt(f), + Statement::DescribeTable(s) => s.fmt(f), + Statement::Explain(s) => s.inner.fmt(f), + Statement::Copy(s) => s.fmt(f), + Statement::Tql(s) => s.fmt(f), + Statement::TruncateTable(s) => s.fmt(f), + Statement::SetVariables(s) => s.fmt(f), + Statement::ShowVariables(s) => s.fmt(f), + } + } +} + /// Comment hints from SQL. /// It'll be enabled when using `--comment` in mysql client. /// Eg: `SELECT * FROM system.number LIMIT 1; -- { ErrorCode 25 }` diff --git a/src/sql/src/statements/tql.rs b/src/sql/src/statements/tql.rs index 6bc4136068ea..190f5e04a37b 100644 --- a/src/sql/src/statements/tql.rs +++ b/src/sql/src/statements/tql.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt::Display; + use sqlparser_derive::{Visit, VisitMut}; #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)] @@ -21,6 +23,16 @@ pub enum Tql { Analyze(TqlAnalyze), } +impl Display for Tql { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Tql::Eval(t) => t.fmt(f), + Tql::Explain(t) => t.fmt(f), + Tql::Analyze(t) => t.fmt(f), + } + } +} + /// TQL EVAL (, , , [lookback]) #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)] pub struct TqlEval { @@ -31,6 +43,28 @@ pub struct TqlEval { pub query: String, } +impl TqlEval { + #[inline] + fn format_lookback(&self) -> String { + if let Some(lookback) = &self.lookback { + format!(", {}\n", lookback) + } else { + String::default() + } + } +} + +impl Display for TqlEval { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let start = &self.start; + let end = &self; + let step = &self; + let lookback = self.format_lookback(); + let query = &self.query; + write!(f, "TQL EVAL ({start}, {end}, {step}{lookback}) {query}") + } +} + /// TQL EXPLAIN [VERBOSE] [, , , [lookback]] /// doesn't execute the query but tells how the query would be executed (similar to SQL EXPLAIN). #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)] @@ -43,6 +77,41 @@ pub struct TqlExplain { pub is_verbose: bool, } +impl TqlExplain { + #[inline] + fn format_lookback(&self) -> String { + if let Some(lookback) = &self.lookback { + format!(", {}\n", lookback) + } else { + String::default() + } + } + + #[inline] + fn format_is_verbose(&self) -> &str { + if self.is_verbose { + "VERBOSE" + } else { + "" + } + } +} + +impl Display for TqlExplain { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let start = &self.start; + let end = &self.end; + let step = &self.step; + let lookback = self.format_lookback(); + let query = &self.query; + let is_verbose = self.format_is_verbose(); + write!( + f, + "TQL EXPLAIN {is_verbose} ({start}, {end}, {step}{lookback}) {query}" + ) + } +} + /// TQL ANALYZE [VERBOSE] (, , , [lookback]) /// executes the plan and tells the detailed per-step execution time (similar to SQL ANALYZE). #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)] @@ -55,6 +124,41 @@ pub struct TqlAnalyze { pub is_verbose: bool, } +impl TqlAnalyze { + #[inline] + fn format_lookback(&self) -> String { + if let Some(lookback) = &self.lookback { + format!(", {}\n", lookback) + } else { + String::default() + } + } + + #[inline] + fn format_is_verbose(&self) -> &str { + if self.is_verbose { + "VERBOSE" + } else { + "" + } + } +} + +impl Display for TqlAnalyze { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let start = &self.start; + let end = &self.end; + let step = &self.step; + let lookback = self.format_lookback(); + let query = &self.query; + let is_verbose = self.format_is_verbose(); + write!( + f, + "TQL ANALYZE {is_verbose} ({start}, {end}, {step}{lookback}) {query}" + ) + } +} + /// Intermediate structure used to unify parameter mappings for various TQL operations. /// This struct serves as a common parameter container for parsing TQL queries /// and constructing corresponding TQL operations: `TqlEval`, `TqlAnalyze` or `TqlExplain`. diff --git a/src/sql/src/statements/truncate.rs b/src/sql/src/statements/truncate.rs index aa08cde559b4..9135b9888f28 100644 --- a/src/sql/src/statements/truncate.rs +++ b/src/sql/src/statements/truncate.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt::Display; + use sqlparser::ast::ObjectName; use sqlparser_derive::{Visit, VisitMut}; @@ -31,3 +33,10 @@ impl TruncateTable { &self.table_name } } + +impl Display for TruncateTable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let table_name = self.table_name(); + write!(f, r#"TRUNCATE TABLE {table_name}"#) + } +} From 4f64ea29f121103edbec672b7b07f837bc5efdc0 Mon Sep 17 00:00:00 2001 From: irenjj Date: Fri, 19 Apr 2024 11:52:00 +0800 Subject: [PATCH 02/17] fix: add license header --- src/sql/src/statements/show.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sql/src/statements/show.rs b/src/sql/src/statements/show.rs index 2c8b0de8baad..079878f49bdc 100644 --- a/src/sql/src/statements/show.rs +++ b/src/sql/src/statements/show.rs @@ -1,3 +1,5 @@ +// Copyright 2023 Greptime Team +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at From 31c246c74df9e23154d20ac43d3c969df876c5ab Mon Sep 17 00:00:00 2001 From: irenjj Date: Fri, 19 Apr 2024 15:49:50 +0800 Subject: [PATCH 03/17] fix: inline function manually --- src/sql/src/statements/create.rs | 16 ++++--------- src/sql/src/statements/drop.rs | 32 ++++++++----------------- src/sql/src/statements/set_variables.rs | 1 + src/sql/src/statements/show.rs | 32 ++++++++----------------- 4 files changed, 26 insertions(+), 55 deletions(-) diff --git a/src/sql/src/statements/create.rs b/src/sql/src/statements/create.rs index f0ee1affb9f3..4c7d7c71e4b2 100644 --- a/src/sql/src/statements/create.rs +++ b/src/sql/src/statements/create.rs @@ -233,22 +233,16 @@ impl CreateDatabase { pub fn name(&self) -> &ObjectName { &self.name } - - #[inline] - fn format_if_not_exists(&self) -> &str { - if self.if_not_exists { - "IF NOT EXISTS" - } else { - "" - } - } } impl Display for CreateDatabase { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let if_not_exists = self.format_if_not_exists(); + f.write_str("CREATE DATABASE")?; + if self.if_not_exists { + f.write_str(" IF NOT EXISTS")?; + } let name = self.name(); - write!(f, r#"CREATE DATABASE {if_not_exists} {name}"#) + write!(f, r#" {name}"#) } } diff --git a/src/sql/src/statements/drop.rs b/src/sql/src/statements/drop.rs index 8d5b6b43a8b5..564a84601f86 100644 --- a/src/sql/src/statements/drop.rs +++ b/src/sql/src/statements/drop.rs @@ -41,22 +41,16 @@ impl DropTable { pub fn drop_if_exists(&self) -> bool { self.drop_if_exists } - - #[inline] - fn format_if_exists(&self) -> &str { - if self.drop_if_exists { - "IF EXISTS" - } else { - "" - } - } } impl Display for DropTable { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let if_exists = self.format_if_exists(); + f.write_str("DROP TABLE")?; + if self.drop_if_exists() { + f.write_str(" IF EXISTS")?; + } let table_name = self.table_name(); - write!(f, r#"DROP TABLE {if_exists} {table_name}"#) + write!(f, r#" {table_name}"#) } } @@ -84,21 +78,15 @@ impl DropDatabase { pub fn drop_if_exists(&self) -> bool { self.drop_if_exists } - - #[inline] - fn format_if_exists(&self) -> &str { - if self.drop_if_exists { - "IF EXISTS" - } else { - "" - } - } } impl Display for DropDatabase { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let if_exists = self.format_if_exists(); + f.write_str("DROP DATABASE")?; + if self.drop_if_exists() { + f.write_str(" IF EXISTS")?; + } let name = self.name(); - write!(f, r#"DROP DATABASE {if_exists} {name}"#) + write!(f, r#" {name}"#) } } diff --git a/src/sql/src/statements/set_variables.rs b/src/sql/src/statements/set_variables.rs index a660125a2537..e1d2c765da84 100644 --- a/src/sql/src/statements/set_variables.rs +++ b/src/sql/src/statements/set_variables.rs @@ -30,6 +30,7 @@ impl SetVariables { } pub fn format_value(&self) -> String { + // The number of value is always one. self.value .iter() .map(|expr| format!("{}", expr)) diff --git a/src/sql/src/statements/show.rs b/src/sql/src/statements/show.rs index 079878f49bdc..0c9bb3154c4b 100644 --- a/src/sql/src/statements/show.rs +++ b/src/sql/src/statements/show.rs @@ -72,24 +72,18 @@ impl ShowColumns { None => String::default(), } } - - #[inline] - fn format_full(&self) -> &str { - if self.full { - "FULL" - } else { - "" - } - } } impl Display for ShowColumns { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("SHOW")?; + if self.full { + f.write_str(" FULL")?; + } let kind = self.kind(); let table = self.format_table(); let database = self.format_database(); - let full = self.format_full(); - write!(f, r#"SHOW {full} {table} {database} {kind}"#) + write!(f, r#" {table} {database} {kind}"#) } } @@ -169,23 +163,17 @@ impl ShowTables { } } } - - #[inline] - fn format_full(&self) -> &str { - if self.full { - "FULL" - } else { - "" - } - } } impl Display for ShowTables { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let full = self.format_full(); + f.write_str("SHOW")?; + if self.full { + f.write_str(" FULL")?; + } let database = self.format_database(); let kind = self.kind(); - write!(f, r#"SHOW {full} TABLES {database} {kind}"#) + write!(f, r#" {database} {kind}"#) } } From 8d86b8617c62048e311f7904f14e4a972812980c Mon Sep 17 00:00:00 2001 From: irenjj Date: Fri, 19 Apr 2024 16:25:01 +0800 Subject: [PATCH 04/17] fix: redacte options --- src/sql/src/statements/copy.rs | 2 ++ src/sql/src/statements/create.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/sql/src/statements/copy.rs b/src/sql/src/statements/copy.rs index 22759055ca28..6fdfc9b34f5c 100644 --- a/src/sql/src/statements/copy.rs +++ b/src/sql/src/statements/copy.rs @@ -18,6 +18,7 @@ use sqlparser::ast::ObjectName; use sqlparser_derive::{Visit, VisitMut}; use crate::statements::OptionMap; +use crate::util::redact_sql_secrets; macro_rules! format_sorted_hashmap { ($hashmap:expr) => {{ @@ -27,6 +28,7 @@ macro_rules! format_sorted_hashmap { let mut result = String::new(); for key in sorted_keys { if let Some(val) = hashmap.get(key) { + redact_sql_secrets(val); result.push_str(&format!("{} = {}, ", key, val)); } } diff --git a/src/sql/src/statements/create.rs b/src/sql/src/statements/create.rs index 4c7d7c71e4b2..da81eaaeccd2 100644 --- a/src/sql/src/statements/create.rs +++ b/src/sql/src/statements/create.rs @@ -21,6 +21,7 @@ use sqlparser_derive::{Visit, VisitMut}; use crate::ast::{ColumnDef, Ident, ObjectName, SqlOption, TableConstraint, Value as SqlValue}; use crate::statements::OptionMap; +use crate::util::redact_sql_secrets; const LINE_SEP: &str = ",\n"; const COMMA_SEP: &str = ", "; @@ -55,6 +56,7 @@ macro_rules! format_sorted_hashmap { let mut result = String::new(); for key in sorted_keys { if let Some(val) = hashmap.get(key) { + redact_sql_secrets(val); result.push_str(&format!("{} = {}, ", key, val)); } } From 22bbf26a6b8287ef64dc0d1e9f72e075c2f5cea1 Mon Sep 17 00:00:00 2001 From: irenjj Date: Sat, 20 Apr 2024 11:00:30 +0800 Subject: [PATCH 05/17] fix: check secret key and replace value --- src/sql/src/statements/copy.rs | 5 ++--- src/sql/src/statements/create.rs | 5 ++--- src/sql/src/util.rs | 10 ++++++++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/sql/src/statements/copy.rs b/src/sql/src/statements/copy.rs index 6fdfc9b34f5c..cc510987b1be 100644 --- a/src/sql/src/statements/copy.rs +++ b/src/sql/src/statements/copy.rs @@ -18,7 +18,7 @@ use sqlparser::ast::ObjectName; use sqlparser_derive::{Visit, VisitMut}; use crate::statements::OptionMap; -use crate::util::redact_sql_secrets; +use crate::util::redact_option_secret; macro_rules! format_sorted_hashmap { ($hashmap:expr) => {{ @@ -28,8 +28,7 @@ macro_rules! format_sorted_hashmap { let mut result = String::new(); for key in sorted_keys { if let Some(val) = hashmap.get(key) { - redact_sql_secrets(val); - result.push_str(&format!("{} = {}, ", key, val)); + result.push_str(&redact_option_secret(key, val)); } } result diff --git a/src/sql/src/statements/create.rs b/src/sql/src/statements/create.rs index da81eaaeccd2..5cde4f424500 100644 --- a/src/sql/src/statements/create.rs +++ b/src/sql/src/statements/create.rs @@ -21,7 +21,7 @@ use sqlparser_derive::{Visit, VisitMut}; use crate::ast::{ColumnDef, Ident, ObjectName, SqlOption, TableConstraint, Value as SqlValue}; use crate::statements::OptionMap; -use crate::util::redact_sql_secrets; +use crate::util::redact_option_secret; const LINE_SEP: &str = ",\n"; const COMMA_SEP: &str = ", "; @@ -56,8 +56,7 @@ macro_rules! format_sorted_hashmap { let mut result = String::new(); for key in sorted_keys { if let Some(val) = hashmap.get(key) { - redact_sql_secrets(val); - result.push_str(&format!("{} = {}, ", key, val)); + result.push_str(&redact_option_secret(key, val)); } } result diff --git a/src/sql/src/util.rs b/src/sql/src/util.rs index c9c135225280..214ff771abce 100644 --- a/src/sql/src/util.rs +++ b/src/sql/src/util.rs @@ -85,6 +85,16 @@ pub fn redact_sql_secrets(sql: &str) -> String { s } +/// Use regex to match and replace common seen secret values in SQL. +pub fn redact_option_secret(key: &str, val: &str) -> String { + let pattern = Regex::new(r#"(?i)(access_key_id|secret_access_key)"#).unwrap(); + if pattern.is_match(key) { + format!("{} = ******", key) + } else { + format!("{} = {}", key, val) + } +} + #[cfg(test)] mod test { use super::*; From 2b85e9d8e1ca1a77f2b5e2d7983836d383aa6ab6 Mon Sep 17 00:00:00 2001 From: irenjj Date: Sat, 20 Apr 2024 23:34:02 +0800 Subject: [PATCH 06/17] test: add test for statement display --- src/sql/src/statements/alter.rs | 79 ++++++++++++- src/sql/src/statements/copy.rs | 129 +++++++++++++++++++++ src/sql/src/statements/create.rs | 111 ++++++++++++++++++ src/sql/src/statements/describe.rs | 26 ++++- src/sql/src/statements/drop.rs | 99 ++++++++++++++++ src/sql/src/statements/set_variables.rs | 33 ++++++ src/sql/src/statements/show.rs | 144 +++++++++++++++++++++++- src/sql/src/statements/truncate.rs | 32 ++++++ src/sql/src/util.rs | 4 +- 9 files changed, 649 insertions(+), 8 deletions(-) diff --git a/src/sql/src/statements/alter.rs b/src/sql/src/statements/alter.rs index b61ea3102fe3..a54ba2d41b74 100644 --- a/src/sql/src/statements/alter.rs +++ b/src/sql/src/statements/alter.rs @@ -73,9 +73,9 @@ impl Display for AlterTableOperation { location, } => { if let Some(location) = location { - write!(f, r#"ADD {column_def} {location}"#) + write!(f, r#"ADD COLUMN {column_def} {location}"#) } else { - write!(f, r#"ADD {column_def}"#) + write!(f, r#"ADD COLUMN {column_def}"#) } } AlterTableOperation::DropColumn { name } => write!(f, r#"DROP COLUMN {name}"#), @@ -85,3 +85,78 @@ impl Display for AlterTableOperation { } } } + +#[cfg(test)] +mod tests { + use std::assert_matches::assert_matches; + + use crate::dialect::GreptimeDbDialect; + use crate::parser::{ParseOptions, ParserContext}; + use crate::statements::statement::Statement; + + #[test] + fn test_display_alter() { + let sql = r"alter table monitor add column app string default 'shop' primary key;"; + let stmts = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::Alter { .. }); + + match &stmts[0] { + Statement::Alter(set) => { + let new_sql = format!("\n{}", set); + assert_eq!( + r#" +ALTER TABLE monitor ADD COLUMN app STRING DEFAULT 'shop' PRIMARY KEY"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + + let sql = r"alter table monitor drop column load_15;"; + let stmts = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::Alter { .. }); + + match &stmts[0] { + Statement::Alter(set) => { + let new_sql = format!("\n{}", set); + assert_eq!( + r#" +ALTER TABLE monitor DROP COLUMN load_15"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + + let sql = r"alter table monitor rename monitor_new;"; + let stmts = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::Alter { .. }); + + match &stmts[0] { + Statement::Alter(set) => { + let new_sql = format!("\n{}", set); + assert_eq!( + r#" +ALTER TABLE monitor RENAME monitor_new"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + } +} diff --git a/src/sql/src/statements/copy.rs b/src/sql/src/statements/copy.rs index cc510987b1be..b09be5c47f83 100644 --- a/src/sql/src/statements/copy.rs +++ b/src/sql/src/statements/copy.rs @@ -205,3 +205,132 @@ impl CopyTableArgument { .cloned() } } + +#[cfg(test)] +mod tests { + use std::assert_matches::assert_matches; + + use crate::dialect::GreptimeDbDialect; + use crate::parser::{ParseOptions, ParserContext}; + use crate::statements::statement::Statement; + + #[test] + fn test_display_copy_from_tb() { + let sql = r"copy tbl from 's3://my-bucket/data.parquet' + with (format = 'parquet', pattern = '.*parquet.*') + connection(region = 'us-west-2', secret_access_key = '12345678');"; + let stmts = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::Copy { .. }); + + match &stmts[0] { + Statement::Copy(copy) => { + let new_sql = format!("\n{}", copy); + assert_eq!( + r#" +COPY tbl FROM s3://my-bucket/data.parquet WITH( +format = parquet,pattern = .*parquet.*, +) CONNECTION( +region = us-west-2,secret_access_key = ******, +)"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + } + + #[test] + fn test_display_copy_to_tb() { + let sql = r"copy tbl to 's3://my-bucket/data.parquet' + with (format = 'parquet', pattern = '.*parquet.*') + connection(region = 'us-west-2', secret_access_key = '12345678');"; + let stmts = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::Copy { .. }); + + match &stmts[0] { + Statement::Copy(copy) => { + let new_sql = format!("\n{}", copy); + assert_eq!( + r#" +COPY tbl TO s3://my-bucket/data.parquet WITH( +format = parquet,pattern = .*parquet.*, +) CONNECTION( +region = us-west-2,secret_access_key = ******, +)"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + } + + #[test] + fn test_display_copy_from_db() { + let sql = r"copy database db1 from 's3://my-bucket/data.parquet' + with (format = 'parquet', pattern = '.*parquet.*') + connection(region = 'us-west-2', secret_access_key = '12345678');"; + let stmts = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::Copy { .. }); + + match &stmts[0] { + Statement::Copy(copy) => { + let new_sql = format!("\n{}", copy); + assert_eq!( + r#" +COPY DATABASE db1 FROM s3://my-bucket/data.parquet WITH( +format = parquet,pattern = .*parquet.*, +) CONNECTION( +region = us-west-2,secret_access_key = ******, +)"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + } + + #[test] + fn test_display_copy_to_db() { + let sql = r"copy database db1 to 's3://my-bucket/data.parquet' + with (format = 'parquet', pattern = '.*parquet.*') + connection(region = 'us-west-2', secret_access_key = '12345678');"; + let stmts = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::Copy { .. }); + + match &stmts[0] { + Statement::Copy(copy) => { + let new_sql = format!("\n{}", copy); + assert_eq!( + r#" +COPY DATABASE db1 TO s3://my-bucket/data.parquet WITH( +format = parquet,pattern = .*parquet.*, +) CONNECTION( +region = us-west-2,secret_access_key = ******, +)"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + } +} diff --git a/src/sql/src/statements/create.rs b/src/sql/src/statements/create.rs index 5cde4f424500..093836bf05a4 100644 --- a/src/sql/src/statements/create.rs +++ b/src/sql/src/statements/create.rs @@ -493,4 +493,115 @@ ENGINE=mito ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()); assert_matches!(result, Err(Error::InvalidTableOption { .. })) } + + #[test] + fn test_display_create_database() { + let sql = r"create database test;"; + let stmts = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::CreateDatabase { .. }); + + match &stmts[0] { + Statement::CreateDatabase(set) => { + let new_sql = format!("\n{}", set); + assert_eq!( + r#" +CREATE DATABASE test"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + + let sql = r"create database if not exists test;"; + let stmts = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::CreateDatabase { .. }); + + match &stmts[0] { + Statement::CreateDatabase(set) => { + let new_sql = format!("\n{}", set); + assert_eq!( + r#" +CREATE DATABASE IF NOT EXISTS test"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + } + + #[test] + fn test_display_create_table_like() { + let sql = r"create table t2 like t1;"; + let stmts = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::CreateTableLike { .. }); + + match &stmts[0] { + Statement::CreateTableLike(create) => { + let new_sql = format!("\n{}", create); + assert_eq!( + r#" +CREATE TABLE t2 LIKE t1"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + } + + #[test] + fn test_display_create_external_table() { + let sql = r#"CREATE EXTERNAL TABLE city ( + host string, + ts timestamp, + cpu float64 default 0, + memory float64, + TIME INDEX (ts), + PRIMARY KEY(host) +) WITH (location='/var/data/city.csv', format='csv');"#; + let stmts = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::CreateExternalTable { .. }); + + match &stmts[0] { + Statement::CreateExternalTable(create) => { + let new_sql = format!("\n{}", create); + assert_eq!( + r#" +CREATE EXTERNAL TABLE city ( + host STRING, + ts TIMESTAMP, + cpu FLOAT64 DEFAULT 0, + memory FLOAT64, + TIME INDEX (ts), + PRIMARY KEY (host) +) +ENGINE=file +WITH( +format = csv,location = /var/data/city.csv, +)"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + } } diff --git a/src/sql/src/statements/describe.rs b/src/sql/src/statements/describe.rs index 71f12b38d58e..743f2b0123c2 100644 --- a/src/sql/src/statements/describe.rs +++ b/src/sql/src/statements/describe.rs @@ -37,7 +37,7 @@ impl DescribeTable { impl Display for DescribeTable { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let name = self.name(); - write!(f, r#"DESCRIBEE TABLE {name}"#) + write!(f, r#"DESCRIBE TABLE {name}"#) } } @@ -117,4 +117,28 @@ mod tests { ) .is_err()); } + + #[test] + fn test_display_describe_table() { + let sql = r"describe table monitor;"; + let stmts = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::DescribeTable { .. }); + + match &stmts[0] { + Statement::DescribeTable(set) => { + let new_sql = format!("\n{}", set); + assert_eq!( + r#" +DESCRIBE TABLE monitor"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + } } diff --git a/src/sql/src/statements/drop.rs b/src/sql/src/statements/drop.rs index 564a84601f86..4725f512816d 100644 --- a/src/sql/src/statements/drop.rs +++ b/src/sql/src/statements/drop.rs @@ -90,3 +90,102 @@ impl Display for DropDatabase { write!(f, r#" {name}"#) } } + +#[cfg(test)] +mod tests { + use std::assert_matches::assert_matches; + + use crate::dialect::GreptimeDbDialect; + use crate::parser::{ParseOptions, ParserContext}; + use crate::statements::statement::Statement; + + #[test] + fn test_display_drop_database() { + let sql = r"drop database test;"; + let stmts = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::DropDatabase { .. }); + + match &stmts[0] { + Statement::DropDatabase(set) => { + let new_sql = format!("\n{}", set); + assert_eq!( + r#" +DROP DATABASE test"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + + let sql = r"drop database if exists test;"; + let stmts = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::DropDatabase { .. }); + + match &stmts[0] { + Statement::DropDatabase(set) => { + let new_sql = format!("\n{}", set); + assert_eq!( + r#" +DROP DATABASE IF EXISTS test"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + } + + #[test] + fn test_display_drop_table() { + let sql = r"drop table test;"; + let stmts = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::DropTable { .. }); + + match &stmts[0] { + Statement::DropTable(set) => { + let new_sql = format!("\n{}", set); + assert_eq!( + r#" +DROP TABLE test"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + + let sql = r"drop table if exists test;"; + let stmts = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::DropTable { .. }); + + match &stmts[0] { + Statement::DropTable(set) => { + let new_sql = format!("\n{}", set); + assert_eq!( + r#" +DROP TABLE IF EXISTS test"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + } +} diff --git a/src/sql/src/statements/set_variables.rs b/src/sql/src/statements/set_variables.rs index e1d2c765da84..8a6a19a31a66 100644 --- a/src/sql/src/statements/set_variables.rs +++ b/src/sql/src/statements/set_variables.rs @@ -46,3 +46,36 @@ impl Display for SetVariables { write!(f, r#"SET {variable} = {value}"#) } } + +#[cfg(test)] +mod tests { + use std::assert_matches::assert_matches; + + use crate::dialect::GreptimeDbDialect; + use crate::parser::{ParseOptions, ParserContext}; + use crate::statements::statement::Statement; + + #[test] + fn test_display_show_variables() { + let sql = r"set delayed_insert_timeout=300;"; + let stmts = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::SetVariables { .. }); + + match &stmts[0] { + Statement::SetVariables(set) => { + let new_sql = format!("\n{}", set); + assert_eq!( + r#" +SET delayed_insert_timeout = 300"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + } +} diff --git a/src/sql/src/statements/show.rs b/src/sql/src/statements/show.rs index 0c9bb3154c4b..789a1edf6890 100644 --- a/src/sql/src/statements/show.rs +++ b/src/sql/src/statements/show.rs @@ -83,7 +83,7 @@ impl Display for ShowColumns { let kind = self.kind(); let table = self.format_table(); let database = self.format_database(); - write!(f, r#" {table} {database} {kind}"#) + write!(f, r#" COLUMNS {table} {database} {kind}"#) } } @@ -173,7 +173,7 @@ impl Display for ShowTables { } let database = self.format_database(); let kind = self.kind(); - write!(f, r#" {database} {kind}"#) + write!(f, r#" TABLES {database} {kind}"#) } } @@ -192,7 +192,7 @@ impl ShowCreateTable { impl Display for ShowCreateTable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let table_name = self.table_name(); - write!(f, r#"SHOW CREATE {table_name}"#) + write!(f, r#"SHOW CREATE TABLE {table_name}"#) } } @@ -300,4 +300,142 @@ mod tests { ) .is_err()); } + + #[test] + fn test_display_show_variables() { + let sql = r"show variables v1;"; + let stmts: Vec = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::ShowVariables { .. }); + match &stmts[0] { + Statement::ShowVariables(show) => { + let new_sql = format!("\n{}", show); + assert_eq!( + r#" +SHOW VARIABLES v1"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + } + + #[test] + fn test_display_show_create_table() { + let sql = r"show create table monitor;"; + let stmts: Vec = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::ShowCreateTable { .. }); + match &stmts[0] { + Statement::ShowCreateTable(show) => { + let new_sql = format!("\n{}", show); + assert_eq!( + r#" +SHOW CREATE TABLE monitor"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + } + + #[test] + fn test_display_show_index() { + let sql = r"show index from t1 from d1;"; + let stmts: Vec = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::ShowIndex { .. }); + match &stmts[0] { + Statement::ShowIndex(show) => { + let new_sql = format!("\n{}", show); + assert_eq!( + r#" +SHOW INDEX IN t1 IN d1 ALL"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + } + + #[test] + fn test_display_show_columns() { + let sql = r"show full columns in t1 in d1;"; + let stmts: Vec = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::ShowColumns { .. }); + match &stmts[0] { + Statement::ShowColumns(show) => { + let new_sql = format!("\n{}", show); + assert_eq!( + r#" +SHOW FULL COLUMNS IN t1 IN d1 ALL"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + } + + #[test] + fn test_display_show_tables() { + let sql = r"show full tables in d1;"; + let stmts: Vec = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::ShowTables { .. }); + match &stmts[0] { + Statement::ShowTables(show) => { + let new_sql = format!("\n{}", show); + assert_eq!( + r#" +SHOW FULL TABLES IN d1 ALL"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + } + + #[test] + fn test_display_show_databases() { + let sql = r"show databases;"; + let stmts: Vec = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::ShowDatabases { .. }); + match &stmts[0] { + Statement::ShowDatabases(show) => { + let new_sql = format!("\n{}", show); + assert_eq!( + r#" +SHOW DATABASES ALL"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + } } diff --git a/src/sql/src/statements/truncate.rs b/src/sql/src/statements/truncate.rs index 9135b9888f28..c1a063f959ce 100644 --- a/src/sql/src/statements/truncate.rs +++ b/src/sql/src/statements/truncate.rs @@ -40,3 +40,35 @@ impl Display for TruncateTable { write!(f, r#"TRUNCATE TABLE {table_name}"#) } } + +#[cfg(test)] +mod tests { + use std::assert_matches::assert_matches; + + use crate::dialect::GreptimeDbDialect; + use crate::parser::{ParseOptions, ParserContext}; + use crate::statements::statement::Statement; + + #[test] + fn test_display_for_tuncate_table() { + let sql = r"truncate table t1;"; + let stmts: Vec = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::TruncateTable { .. }); + match &stmts[0] { + Statement::TruncateTable(trunc) => { + let new_sql = format!("\n{}", trunc); + assert_eq!( + r#" +TRUNCATE TABLE t1"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + } +} diff --git a/src/sql/src/util.rs b/src/sql/src/util.rs index 214ff771abce..0b1daec01208 100644 --- a/src/sql/src/util.rs +++ b/src/sql/src/util.rs @@ -89,9 +89,9 @@ pub fn redact_sql_secrets(sql: &str) -> String { pub fn redact_option_secret(key: &str, val: &str) -> String { let pattern = Regex::new(r#"(?i)(access_key_id|secret_access_key)"#).unwrap(); if pattern.is_match(key) { - format!("{} = ******", key) + format!("{} = ******,", key) } else { - format!("{} = {}", key, val) + format!("{} = {},", key, val) } } From 3c9114eda5804b89ee82d94828e52724959d76c2 Mon Sep 17 00:00:00 2001 From: irenjj Date: Sat, 20 Apr 2024 23:54:28 +0800 Subject: [PATCH 07/17] fix: fix check --- src/sql/src/statements/statement.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sql/src/statements/statement.rs b/src/sql/src/statements/statement.rs index bd47acdb4b76..9bacb9944069 100644 --- a/src/sql/src/statements/statement.rs +++ b/src/sql/src/statements/statement.rs @@ -116,6 +116,8 @@ impl Display for Statement { Statement::TruncateTable(s) => s.fmt(f), Statement::SetVariables(s) => s.fmt(f), Statement::ShowVariables(s) => s.fmt(f), + Statement::ShowCharset(s) => s.fmt(f), + Statement::ShowCollation(s) => s.fmt(f), } } } From 63120d11b9b5bc5c3309687dfe70bd6ce70b81c4 Mon Sep 17 00:00:00 2001 From: irenjj Date: Tue, 23 Apr 2024 21:50:22 +0800 Subject: [PATCH 08/17] fix: inline method --- src/sql/src/statements/show.rs | 69 ++++++++++++-------------- src/sql/src/statements/tql.rs | 89 +++++++++++----------------------- 2 files changed, 58 insertions(+), 100 deletions(-) diff --git a/src/sql/src/statements/show.rs b/src/sql/src/statements/show.rs index 789a1edf6890..8990e81a9d2e 100644 --- a/src/sql/src/statements/show.rs +++ b/src/sql/src/statements/show.rs @@ -128,15 +128,11 @@ impl ShowDatabases { pub fn new(kind: ShowKind) -> Self { ShowDatabases { kind } } - - pub fn kind(&self) -> &ShowKind { - &self.kind - } } impl Display for ShowDatabases { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let kind = self.kind(); + let kind = &self.kind; write!(f, r#"SHOW DATABASES {kind}"#) } } @@ -149,31 +145,20 @@ pub struct ShowTables { pub full: bool, } -impl ShowTables { - pub fn kind(&self) -> &ShowKind { - &self.kind - } - - #[inline] - fn format_database(&self) -> String { - match &self.database { - None => String::default(), - Some(database) => { - format!("IN {}", database) - } - } - } -} - impl Display for ShowTables { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("SHOW")?; if self.full { f.write_str(" FULL")?; } - let database = self.format_database(); - let kind = self.kind(); - write!(f, r#" TABLES {database} {kind}"#) + f.write_str(" TABLES")?; + let database = match &self.database { + Some(d) => format!("IN {} ", d), + None => String::default(), + }; + + let kind = &self.kind; + write!(f, r#" {database}{kind}"#) } } @@ -183,15 +168,9 @@ pub struct ShowCreateTable { pub table_name: ObjectName, } -impl ShowCreateTable { - fn table_name(&self) -> &ObjectName { - &self.table_name - } -} - impl Display for ShowCreateTable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let table_name = self.table_name(); + let table_name = &self.table_name; write!(f, r#"SHOW CREATE TABLE {table_name}"#) } } @@ -202,15 +181,9 @@ pub struct ShowVariables { pub variable: ObjectName, } -impl ShowVariables { - fn variable(&self) -> &ObjectName { - &self.variable - } -} - impl Display for ShowVariables { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let variable = self.variable(); + let variable = &self.variable; write!(f, r#"SHOW VARIABLES {variable}"#) } } @@ -414,6 +387,26 @@ SHOW FULL TABLES IN d1 ALL"#, unreachable!(); } } + + let sql = r"show full tables;"; + let stmts: Vec = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::ShowTables { .. }); + match &stmts[0] { + Statement::ShowTables(show) => { + let new_sql = format!("\n{}", show); + assert_eq!( + r#" +SHOW FULL TABLES ALL"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } } #[test] diff --git a/src/sql/src/statements/tql.rs b/src/sql/src/statements/tql.rs index 190f5e04a37b..e8e1edb4a69a 100644 --- a/src/sql/src/statements/tql.rs +++ b/src/sql/src/statements/tql.rs @@ -43,23 +43,16 @@ pub struct TqlEval { pub query: String, } -impl TqlEval { - #[inline] - fn format_lookback(&self) -> String { - if let Some(lookback) = &self.lookback { - format!(", {}\n", lookback) - } else { - String::default() - } - } -} - impl Display for TqlEval { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let lookback = match &self.lookback { + Some(lookback) => format!(", {}", lookback), + None => String::default(), + }; + let start = &self.start; let end = &self; let step = &self; - let lookback = self.format_lookback(); let query = &self.query; write!(f, "TQL EVAL ({start}, {end}, {step}{lookback}) {query}") } @@ -77,38 +70,24 @@ pub struct TqlExplain { pub is_verbose: bool, } -impl TqlExplain { - #[inline] - fn format_lookback(&self) -> String { - if let Some(lookback) = &self.lookback { - format!(", {}\n", lookback) - } else { - String::default() - } - } - - #[inline] - fn format_is_verbose(&self) -> &str { +impl Display for TqlExplain { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("TQL EXPLAIN")?; if self.is_verbose { - "VERBOSE" - } else { - "" + f.write_str(" VERBOSE")?; } - } -} -impl Display for TqlExplain { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let lookback = match &self.lookback { + Some(lookback) => format!(", {}", lookback), + None => String::default(), + }; + let start = &self.start; let end = &self.end; let step = &self.step; - let lookback = self.format_lookback(); let query = &self.query; - let is_verbose = self.format_is_verbose(); - write!( - f, - "TQL EXPLAIN {is_verbose} ({start}, {end}, {step}{lookback}) {query}" - ) + + write!(f, " ({start}, {end}, {step}{lookback}) {query}") } } @@ -124,38 +103,24 @@ pub struct TqlAnalyze { pub is_verbose: bool, } -impl TqlAnalyze { - #[inline] - fn format_lookback(&self) -> String { - if let Some(lookback) = &self.lookback { - format!(", {}\n", lookback) - } else { - String::default() - } - } - - #[inline] - fn format_is_verbose(&self) -> &str { +impl Display for TqlAnalyze { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("TQL ANALYZE")?; if self.is_verbose { - "VERBOSE" - } else { - "" + f.write_str(" VERBOSE")?; } - } -} -impl Display for TqlAnalyze { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let lookback = match &self.lookback { + Some(lookback) => format!(", {}", lookback), + None => String::default(), + }; + let start = &self.start; let end = &self.end; let step = &self.step; - let lookback = self.format_lookback(); let query = &self.query; - let is_verbose = self.format_is_verbose(); - write!( - f, - "TQL ANALYZE {is_verbose} ({start}, {end}, {step}{lookback}) {query}" - ) + + write!(f, " ({start}, {end}, {step}{lookback}) {query}") } } From 398986569cfbbca90859778c60781b2bf9d10d66 Mon Sep 17 00:00:00 2001 From: irenjj Date: Tue, 23 Apr 2024 22:21:16 +0800 Subject: [PATCH 09/17] fix: inline methods --- src/sql/src/statements/create.rs | 33 ++++--------- src/sql/src/statements/set_variables.rs | 20 ++------ src/sql/src/statements/show.rs | 62 ++++++------------------- 3 files changed, 29 insertions(+), 86 deletions(-) diff --git a/src/sql/src/statements/create.rs b/src/sql/src/statements/create.rs index 093836bf05a4..0e4618738a32 100644 --- a/src/sql/src/statements/create.rs +++ b/src/sql/src/statements/create.rs @@ -230,10 +230,6 @@ impl CreateDatabase { if_not_exists, } } - - pub fn name(&self) -> &ObjectName { - &self.name - } } impl Display for CreateDatabase { @@ -242,7 +238,7 @@ impl Display for CreateDatabase { if self.if_not_exists { f.write_str(" IF NOT EXISTS")?; } - let name = self.name(); + let name = &self.name; write!(f, r#" {name}"#) } } @@ -277,19 +273,16 @@ impl CreateExternalTable { }) .join(LINE_SEP) } +} - #[inline] - fn format_if_not_exists(&self) -> &str { - if self.if_not_exists { - "IF NOT EXISTS" - } else { - "" - } - } +impl Display for CreateExternalTable { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let if_not_exists = match self.if_not_exists { + true => "IF NOT EXISTS", + false => "", + }; - #[inline] - fn format_options(&self) -> String { - if self.options.map.is_empty() { + let options = if self.options.map.is_empty() { String::default() } else { let options = format_sorted_hashmap!(&self.options.map); @@ -298,17 +291,11 @@ impl CreateExternalTable { {options} )"# ) - } - } -} + }; -impl Display for CreateExternalTable { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let name = &self.name; let columns = format_list_indent!(self.columns); let constraints = self.format_constraints(); - let options = self.format_options(); - let if_not_exists = self.format_if_not_exists(); let engine = &self.engine; write!( f, diff --git a/src/sql/src/statements/set_variables.rs b/src/sql/src/statements/set_variables.rs index 8a6a19a31a66..a67409691b3f 100644 --- a/src/sql/src/statements/set_variables.rs +++ b/src/sql/src/statements/set_variables.rs @@ -24,25 +24,15 @@ pub struct SetVariables { pub value: Vec, } -impl SetVariables { - pub fn variable(&self) -> &ObjectName { - &self.variable - } - - pub fn format_value(&self) -> String { - // The number of value is always one. - self.value +impl Display for SetVariables { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let variable = &self.variable; + let value = &self.value .iter() .map(|expr| format!("{}", expr)) .collect::>() - .join(", ") - } -} + .join(", "); -impl Display for SetVariables { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let variable = self.variable(); - let value = self.format_value(); write!(f, r#"SET {variable} = {value}"#) } } diff --git a/src/sql/src/statements/show.rs b/src/sql/src/statements/show.rs index 8990e81a9d2e..145afe791e2f 100644 --- a/src/sql/src/statements/show.rs +++ b/src/sql/src/statements/show.rs @@ -51,38 +51,20 @@ pub struct ShowColumns { pub full: bool, } -impl ShowColumns { - pub fn kind(&self) -> &ShowKind { - &self.kind - } - - pub fn table(&self) -> &String { - &self.table - } - - #[inline] - fn format_table(&self) -> String { - format!("IN {}", self.table) - } - - #[inline] - fn format_database(&self) -> String { - match &self.database { - Some(database) => format!("IN {}", database), - None => String::default(), - } - } -} - impl Display for ShowColumns { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("SHOW")?; if self.full { f.write_str(" FULL")?; } - let kind = self.kind(); - let table = self.format_table(); - let database = self.format_database(); + + let table = format!("IN {}", &self.table); + let database = match &self.database { + Some(database) => format!("IN {}", database), + None => String::default(), + }; + + let kind = &self.kind; write!(f, r#" COLUMNS {table} {database} {kind}"#) } } @@ -95,30 +77,14 @@ pub struct ShowIndex { pub database: Option, } -impl ShowIndex { - pub fn kind(&self) -> &ShowKind { - &self.kind - } - - #[inline] - fn format_table(&self) -> String { - format!("IN {}", self.table) - } - - #[inline] - fn format_database(&self) -> String { - match &self.database { - Some(database) => format!("IN {}", database), - None => String::default(), - } - } -} - impl Display for ShowIndex { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let kind = self.kind(); - let table = self.format_table(); - let database = self.format_database(); + let kind = &self.kind; + let table = format!("IN {}", &self.table); + let database = match &self.database { + Some(database) => format!("IN {}", database), + None => String::default(), + }; write!(f, r#"SHOW INDEX {table} {database} {kind}"#) } } From deb43aebc854eff817a28a06968625bd7c13c764 Mon Sep 17 00:00:00 2001 From: irenjj Date: Tue, 23 Apr 2024 22:24:51 +0800 Subject: [PATCH 10/17] fix: format --- src/sql/src/statements/set_variables.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sql/src/statements/set_variables.rs b/src/sql/src/statements/set_variables.rs index a67409691b3f..7a2a94a531df 100644 --- a/src/sql/src/statements/set_variables.rs +++ b/src/sql/src/statements/set_variables.rs @@ -27,7 +27,8 @@ pub struct SetVariables { impl Display for SetVariables { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let variable = &self.variable; - let value = &self.value + let value = &self + .value .iter() .map(|expr| format!("{}", expr)) .collect::>() From b24d334036e43de5128962ef51b188f7a67d5f7d Mon Sep 17 00:00:00 2001 From: tison Date: Tue, 23 Apr 2024 23:49:56 +0800 Subject: [PATCH 11/17] showcase how to write Display impl Signed-off-by: tison --- src/sql/src/statements/show.rs | 49 ++++++++++++++-------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/src/sql/src/statements/show.rs b/src/sql/src/statements/show.rs index 145afe791e2f..f20a6a59191c 100644 --- a/src/sql/src/statements/show.rs +++ b/src/sql/src/statements/show.rs @@ -26,7 +26,7 @@ pub enum ShowKind { Where(Expr), } -impl fmt::Display for ShowKind { +impl Display for ShowKind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { ShowKind::All => write!(f, "ALL"), @@ -53,19 +53,15 @@ pub struct ShowColumns { impl Display for ShowColumns { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("SHOW")?; + write!(f, "SHOW ")?; if self.full { - f.write_str(" FULL")?; + write!(f, "FULL ")?; } - - let table = format!("IN {}", &self.table); - let database = match &self.database { - Some(database) => format!("IN {}", database), - None => String::default(), - }; - - let kind = &self.kind; - write!(f, r#" COLUMNS {table} {database} {kind}"#) + write!(f, "COLUMNS IN {} ", &self.table)?; + if let Some(database) = &self.database { + write!(f, "IN {database} ")?; + } + write!(f, "{}", &self.kind) } } @@ -79,13 +75,11 @@ pub struct ShowIndex { impl Display for ShowIndex { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let kind = &self.kind; - let table = format!("IN {}", &self.table); - let database = match &self.database { - Some(database) => format!("IN {}", database), - None => String::default(), - }; - write!(f, r#"SHOW INDEX {table} {database} {kind}"#) + write!(f, "SHOW INDEX IN {} ", &self.table)?; + if let Some(database) = &self.database { + write!(f, "IN {database} ")?; + } + write!(f, "{}", &self.kind) } } @@ -113,18 +107,15 @@ pub struct ShowTables { impl Display for ShowTables { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("SHOW")?; + write!(f, "SHOW ")?; if self.full { - f.write_str(" FULL")?; + write!(f, "FULL ")?; } - f.write_str(" TABLES")?; - let database = match &self.database { - Some(d) => format!("IN {} ", d), - None => String::default(), - }; - - let kind = &self.kind; - write!(f, r#" {database}{kind}"#) + write!(f, "TABLES ")?; + if let Some(database) = &self.database { + write!(f, "IN {database} ")?; + } + write!(f, "{}", &self.kind) } } From 22ff85f76168211c8a267cfdd32cfd2c7630d7e7 Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 24 Apr 2024 00:14:43 +0800 Subject: [PATCH 12/17] for others Signed-off-by: tison --- src/sql/src/statements/statement.rs | 10 +++-- src/sql/src/statements/tql.rs | 66 ++++++++++++----------------- 2 files changed, 35 insertions(+), 41 deletions(-) diff --git a/src/sql/src/statements/statement.rs b/src/sql/src/statements/statement.rs index 9bacb9944069..70566893d65a 100644 --- a/src/sql/src/statements/statement.rs +++ b/src/sql/src/statements/statement.rs @@ -110,14 +110,18 @@ impl Display for Statement { Statement::ShowIndex(s) => s.fmt(f), Statement::ShowCreateTable(s) => s.fmt(f), Statement::DescribeTable(s) => s.fmt(f), - Statement::Explain(s) => s.inner.fmt(f), + Statement::Explain(s) => s.fmt(f), Statement::Copy(s) => s.fmt(f), Statement::Tql(s) => s.fmt(f), Statement::TruncateTable(s) => s.fmt(f), Statement::SetVariables(s) => s.fmt(f), Statement::ShowVariables(s) => s.fmt(f), - Statement::ShowCharset(s) => s.fmt(f), - Statement::ShowCollation(s) => s.fmt(f), + Statement::ShowCharset(kind) => { + write!(f, "SHOW CHARSET {kind}") + } + Statement::ShowCollation(kind) => { + write!(f, "SHOW COLLATION {kind}") + } } } } diff --git a/src/sql/src/statements/tql.rs b/src/sql/src/statements/tql.rs index e8e1edb4a69a..b9a6e0e4feb6 100644 --- a/src/sql/src/statements/tql.rs +++ b/src/sql/src/statements/tql.rs @@ -45,16 +45,14 @@ pub struct TqlEval { impl Display for TqlEval { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let lookback = match &self.lookback { - Some(lookback) => format!(", {}", lookback), - None => String::default(), - }; - - let start = &self.start; - let end = &self; - let step = &self; - let query = &self.query; - write!(f, "TQL EVAL ({start}, {end}, {step}{lookback}) {query}") + write!(f, "TQL EVAL(")?; + write!(f, "{}, ", &self.start)?; + write!(f, "{}, ", &self.end)?; + write!(f, "{}", &self.step)?; + if let Some(lookback) = &self.lookback { + write!(f, ", {}", lookback)?; + } + write!(f, ") {}", &self.query) } } @@ -72,22 +70,18 @@ pub struct TqlExplain { impl Display for TqlExplain { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("TQL EXPLAIN")?; + write!(f, "TQL EXPLAIN ")?; if self.is_verbose { - f.write_str(" VERBOSE")?; + write!(f, "VERBOSE ")?; } - - let lookback = match &self.lookback { - Some(lookback) => format!(", {}", lookback), - None => String::default(), - }; - - let start = &self.start; - let end = &self.end; - let step = &self.step; - let query = &self.query; - - write!(f, " ({start}, {end}, {step}{lookback}) {query}") + write!(f, "(")?; + write!(f, "{}, ", &self.start)?; + write!(f, "{}, ", &self.end)?; + write!(f, "{}", &self.step)?; + if let Some(lookback) = &self.lookback { + write!(f, ", {}", lookback)?; + } + write!(f, ") {}", &self.query) } } @@ -105,22 +99,18 @@ pub struct TqlAnalyze { impl Display for TqlAnalyze { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("TQL ANALYZE")?; + write!(f, "TQL ANALYZE ")?; if self.is_verbose { - f.write_str(" VERBOSE")?; + write!(f, "VERBOSE ")?; } - - let lookback = match &self.lookback { - Some(lookback) => format!(", {}", lookback), - None => String::default(), - }; - - let start = &self.start; - let end = &self.end; - let step = &self.step; - let query = &self.query; - - write!(f, " ({start}, {end}, {step}{lookback}) {query}") + write!(f, "(")?; + write!(f, "{}, ", &self.start)?; + write!(f, "{}, ", &self.end)?; + write!(f, "{}", &self.step)?; + if let Some(lookback) = &self.lookback { + write!(f, ", {}", lookback)?; + } + write!(f, ") {}", &self.query) } } From da984dd3dd009d6612737775b31805751521786c Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 24 Apr 2024 08:55:37 +0800 Subject: [PATCH 13/17] create and copy Signed-off-by: tison --- src/sql/src/statements.rs | 22 ++++ src/sql/src/statements/copy.rs | 183 ++++++++------------------------- 2 files changed, 65 insertions(+), 140 deletions(-) diff --git a/src/sql/src/statements.rs b/src/sql/src/statements.rs index de35b71a90a8..3d7ba3e9f822 100644 --- a/src/sql/src/statements.rs +++ b/src/sql/src/statements.rs @@ -43,6 +43,7 @@ use datatypes::schema::constraint::{CURRENT_TIMESTAMP, CURRENT_TIMESTAMP_FN}; use datatypes::schema::{ColumnDefaultConstraint, ColumnSchema, COMMENT_KEY}; use datatypes::types::{cast, TimestampType}; use datatypes::value::{OrderedF32, OrderedF64, Value}; +use itertools::Itertools; pub use option_map::OptionMap; use snafu::{ensure, OptionExt, ResultExt}; use sqlparser::ast::{ExactNumberInfo, UnaryOperator}; @@ -58,6 +59,27 @@ use crate::error::{ SerializeColumnDefaultConstraintSnafu, TimestampOverflowSnafu, UnsupportedDefaultValueSnafu, }; +const REDACTED_OPTIONS: [&str; 2] = ["access_key_id", "secret_access_key"]; + +fn redact_and_sort_options(options: &OptionMap) -> Vec { + let mut result = vec![]; + let keys = options.as_ref().keys().sorted(); + for key in keys { + if let Some(val) = options.get(key) { + let redacted = REDACTED_OPTIONS + .iter() + .find(|opt| opt.eq_ignore_ascii_case(key)) + .is_some(); + if redacted { + result.push(format!("{key} = ******")); + } else { + result.push(format!("{key} = {val}")); + } + } + } + result +} + fn parse_string_to_value( column_name: &str, s: String, diff --git a/src/sql/src/statements/copy.rs b/src/sql/src/statements/copy.rs index b09be5c47f83..b5fcf0d74f52 100644 --- a/src/sql/src/statements/copy.rs +++ b/src/sql/src/statements/copy.rs @@ -17,23 +17,7 @@ use std::fmt::Display; use sqlparser::ast::ObjectName; use sqlparser_derive::{Visit, VisitMut}; -use crate::statements::OptionMap; -use crate::util::redact_option_secret; - -macro_rules! format_sorted_hashmap { - ($hashmap:expr) => {{ - let hashmap = $hashmap; - let mut sorted_keys: Vec<&String> = hashmap.keys().collect(); - sorted_keys.sort(); - let mut result = String::new(); - for key in sorted_keys { - if let Some(val) = hashmap.get(key) { - result.push_str(&redact_option_secret(key, val)); - } - } - result - }}; -} +use crate::statements::{OptionMap, redact_and_sort_options}; #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)] pub enum Copy { @@ -58,25 +42,26 @@ pub enum CopyTable { impl Display for CopyTable { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - CopyTable::To(s) => { - let table_name = &s.table_name; - let with = s.format_with(); - let connection = s.format_connection(); - let location = &s.location; - write!(f, r#"COPY {table_name} TO {location} {with} {connection}"#) + write!(f, "COPY ")?; + let (with, connection) = match self { + CopyTable::To(args) => { + write!(f, "{} TO {}", &args.table_name, &args.location)?; + (&args.with, &args.connection) } - CopyTable::From(s) => { - let table_name = &s.table_name; - let with = s.format_with(); - let connection = s.format_connection(); - let location = &s.location; - write!( - f, - r#"COPY {table_name} FROM {location} {with} {connection}"# - ) + CopyTable::From(args) => { + write!(f, "{} FROM {}", &args.table_name, &args.location)?; + (&args.with, &args.connection) } + }; + if !with.map.is_empty() { + let options = redact_and_sort_options(with); + write!(f, " WITH ({})", options.join(", "))?; + } + if !connection.map.is_empty() { + let options = redact_and_sort_options(connection); + write!(f, " CONNECTION ({})", options.join(", "))?; } + Ok(()) } } @@ -88,28 +73,26 @@ pub enum CopyDatabase { impl Display for CopyDatabase { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - CopyDatabase::To(s) => { - let database_name = &s.database_name; - let with = s.format_with(); - let connection = s.format_connection(); - let location = &s.location; - write!( - f, - r#"COPY DATABASE {database_name} TO {location} {with} {connection}"# - ) + write!(f, "COPY DATABASE ")?; + let (with, connection) = match self { + CopyDatabase::To(args) => { + write!(f, "{} TO {}", &args.database_name, &args.location)?; + (&args.with, &args.connection) } - CopyDatabase::From(s) => { - let database_name = &s.database_name; - let with = s.format_with(); - let connection = s.format_connection(); - let location = &s.location; - write!( - f, - r#"COPY DATABASE {database_name} FROM {location} {with} {connection}"# - ) + CopyDatabase::From(args) => { + write!(f, "{} FROM {}", &args.database_name, &args.location)?; + (&args.with, &args.connection) } + }; + if !with.map.is_empty() { + let options = redact_and_sort_options(with); + write!(f, " WITH ({})", options.join(", "))?; + } + if !connection.map.is_empty() { + let options = redact_and_sort_options(connection); + write!(f, " CONNECTION ({})", options.join(", "))?; } + Ok(()) } } @@ -121,36 +104,6 @@ pub struct CopyDatabaseArgument { pub location: String, } -impl CopyDatabaseArgument { - #[inline] - fn format_with(&self) -> String { - if self.with.map.is_empty() { - String::default() - } else { - let options = format_sorted_hashmap!(&self.with.map); - format!( - r#"WITH( -{options} -)"# - ) - } - } - - #[inline] - fn format_connection(&self) -> String { - if self.connection.map.is_empty() { - String::default() - } else { - let options = format_sorted_hashmap!(&self.connection.map); - format!( - r#"CONNECTION( -{options} -)"# - ) - } - } -} - #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)] pub struct CopyTableArgument { pub table_name: ObjectName, @@ -160,36 +113,6 @@ pub struct CopyTableArgument { pub location: String, } -impl CopyTableArgument { - #[inline] - fn format_with(&self) -> String { - if self.with.map.is_empty() { - String::default() - } else { - let options = format_sorted_hashmap!(&self.with.map); - format!( - r#"WITH( -{options} -)"# - ) - } - } - - #[inline] - fn format_connection(&self) -> String { - if self.connection.map.is_empty() { - String::default() - } else { - let options = format_sorted_hashmap!(&self.connection.map); - format!( - r#"CONNECTION( -{options} -)"# - ) - } - } -} - #[cfg(test)] impl CopyTableArgument { pub fn format(&self) -> Option { @@ -227,14 +150,9 @@ mod tests { match &stmts[0] { Statement::Copy(copy) => { - let new_sql = format!("\n{}", copy); + let new_sql = format!("{}", copy); assert_eq!( - r#" -COPY tbl FROM s3://my-bucket/data.parquet WITH( -format = parquet,pattern = .*parquet.*, -) CONNECTION( -region = us-west-2,secret_access_key = ******, -)"#, + r#"COPY tbl FROM s3://my-bucket/data.parquet WITH (format = parquet, pattern = .*parquet.*) CONNECTION (region = us-west-2, secret_access_key = ******)"#, &new_sql ); } @@ -257,14 +175,9 @@ region = us-west-2,secret_access_key = ******, match &stmts[0] { Statement::Copy(copy) => { - let new_sql = format!("\n{}", copy); + let new_sql = format!("{}", copy); assert_eq!( - r#" -COPY tbl TO s3://my-bucket/data.parquet WITH( -format = parquet,pattern = .*parquet.*, -) CONNECTION( -region = us-west-2,secret_access_key = ******, -)"#, + r#"COPY tbl TO s3://my-bucket/data.parquet WITH (format = parquet, pattern = .*parquet.*) CONNECTION (region = us-west-2, secret_access_key = ******)"#, &new_sql ); } @@ -287,14 +200,9 @@ region = us-west-2,secret_access_key = ******, match &stmts[0] { Statement::Copy(copy) => { - let new_sql = format!("\n{}", copy); + let new_sql = format!("{}", copy); assert_eq!( - r#" -COPY DATABASE db1 FROM s3://my-bucket/data.parquet WITH( -format = parquet,pattern = .*parquet.*, -) CONNECTION( -region = us-west-2,secret_access_key = ******, -)"#, + r#"COPY DATABASE db1 FROM s3://my-bucket/data.parquet WITH (format = parquet, pattern = .*parquet.*) CONNECTION (region = us-west-2, secret_access_key = ******)"#, &new_sql ); } @@ -317,14 +225,9 @@ region = us-west-2,secret_access_key = ******, match &stmts[0] { Statement::Copy(copy) => { - let new_sql = format!("\n{}", copy); + let new_sql = format!("{}", copy); assert_eq!( - r#" -COPY DATABASE db1 TO s3://my-bucket/data.parquet WITH( -format = parquet,pattern = .*parquet.*, -) CONNECTION( -region = us-west-2,secret_access_key = ******, -)"#, + r#"COPY DATABASE db1 TO s3://my-bucket/data.parquet WITH (format = parquet, pattern = .*parquet.*) CONNECTION (region = us-west-2, secret_access_key = ******)"#, &new_sql ); } From 320cffd3c62d70398cbd81d212af3c90619f7b6d Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 24 Apr 2024 09:53:52 +0800 Subject: [PATCH 14/17] create rest Signed-off-by: tison --- src/sql/src/statements.rs | 3 +- src/sql/src/statements/copy.rs | 2 +- src/sql/src/statements/create.rs | 206 ++++++++++--------------------- src/sql/src/util.rs | 10 -- 4 files changed, 64 insertions(+), 157 deletions(-) diff --git a/src/sql/src/statements.rs b/src/sql/src/statements.rs index 3d7ba3e9f822..b7c6807ba53a 100644 --- a/src/sql/src/statements.rs +++ b/src/sql/src/statements.rs @@ -68,8 +68,7 @@ fn redact_and_sort_options(options: &OptionMap) -> Vec { if let Some(val) = options.get(key) { let redacted = REDACTED_OPTIONS .iter() - .find(|opt| opt.eq_ignore_ascii_case(key)) - .is_some(); + .any(|opt| opt.eq_ignore_ascii_case(key)); if redacted { result.push(format!("{key} = ******")); } else { diff --git a/src/sql/src/statements/copy.rs b/src/sql/src/statements/copy.rs index b5fcf0d74f52..263ff832f733 100644 --- a/src/sql/src/statements/copy.rs +++ b/src/sql/src/statements/copy.rs @@ -17,7 +17,7 @@ use std::fmt::Display; use sqlparser::ast::ObjectName; use sqlparser_derive::{Visit, VisitMut}; -use crate::statements::{OptionMap, redact_and_sort_options}; +use crate::statements::{redact_and_sort_options, OptionMap}; #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)] pub enum Copy { diff --git a/src/sql/src/statements/create.rs b/src/sql/src/statements/create.rs index 0e4618738a32..b273ccc55278 100644 --- a/src/sql/src/statements/create.rs +++ b/src/sql/src/statements/create.rs @@ -20,8 +20,7 @@ use sqlparser::ast::Expr; use sqlparser_derive::{Visit, VisitMut}; use crate::ast::{ColumnDef, Ident, ObjectName, SqlOption, TableConstraint, Value as SqlValue}; -use crate::statements::OptionMap; -use crate::util::redact_option_secret; +use crate::statements::{redact_and_sort_options, OptionMap}; const LINE_SEP: &str = ",\n"; const COMMA_SEP: &str = ", "; @@ -48,19 +47,21 @@ macro_rules! format_list_comma { }; } -macro_rules! format_sorted_hashmap { - ($hashmap:expr) => {{ - let hashmap = $hashmap; - let mut sorted_keys: Vec<&String> = hashmap.keys().collect(); - sorted_keys.sort(); - let mut result = String::new(); - for key in sorted_keys { - if let Some(val) = hashmap.get(key) { - result.push_str(&redact_option_secret(key, val)); +fn format_table_constraint(constraints: &[TableConstraint]) -> String { + constraints + .iter() + .map(|c| { + if is_time_index(c) { + let TableConstraint::Unique { columns, .. } = c else { + unreachable!() + }; + + format_indent!("{}TIME INDEX ({})", format_list_comma!(columns)) + } else { + format_indent!(c) } - } - result - }}; + }) + .join(LINE_SEP) } /// Time index name, used in table constraints. @@ -90,58 +91,6 @@ pub struct CreateTable { pub partitions: Option, } -impl CreateTable { - fn format_constraints(&self) -> String { - self.constraints - .iter() - .map(|c| { - if is_time_index(c) { - let TableConstraint::Unique { columns, .. } = c else { - unreachable!() - }; - - format_indent!("{}TIME INDEX ({})", format_list_comma!(columns)) - } else { - format_indent!(c) - } - }) - .join(LINE_SEP) - } - - #[inline] - fn format_partitions(&self) -> String { - if let Some(partitions) = &self.partitions { - format!("{}\n", partitions) - } else { - String::default() - } - } - - #[inline] - fn format_if_not_exists(&self) -> &str { - if self.if_not_exists { - "IF NOT EXISTS" - } else { - "" - } - } - - #[inline] - fn format_options(&self) -> String { - if self.options.is_empty() { - String::default() - } else { - let options: Vec<&SqlOption> = self.options.iter().sorted().collect(); - let options = format_list_indent!(options); - format!( - r#"WITH( -{options} -)"# - ) - } - } -} - #[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut)] pub struct Partitions { pub column_list: Vec, @@ -182,36 +131,37 @@ impl Display for Partitions { "PARTITION ON COLUMNS ({}) (\n{}\n)", format_list_comma!(self.column_list), format_list_indent!(self.exprs), - ) - } else { - write!(f, "") + )?; } + Ok(()) } } impl Display for CreateTable { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let if_not_exists = self.format_if_not_exists(); - let name = &self.name; - let columns = format_list_indent!(self.columns); - let constraints = self.format_constraints(); - let partitions = self.format_partitions(); - let engine = &self.engine; - let options = self.format_options(); - let maybe_external = if self.engine == FILE_ENGINE { - "EXTERNAL " - } else { - "" - }; - write!( - f, - r#"CREATE {maybe_external}TABLE {if_not_exists} {name} ( -{columns}, -{constraints} -) -{partitions}ENGINE={engine} -{options}"# - ) + write!(f, "CREATE ")?; + if self.engine == FILE_ENGINE { + write!(f, "EXTERNAL ")?; + } + write!(f, "TABLE ")?; + if self.if_not_exists { + write!(f, "IF NOT EXISTS ")?; + } + writeln!(f, "{} (", &self.name)?; + writeln!(f, "{},", format_list_indent!(self.columns))?; + writeln!(f, "{}", format_table_constraint(&self.constraints))?; + writeln!(f, ")")?; + if let Some(partitions) = &self.partitions { + writeln!(f, "{partitions}")?; + } + writeln!(f, "ENGINE={}", &self.engine)?; + if !self.options.is_empty() { + writeln!(f, "WITH(")?; + let options: Vec<&SqlOption> = self.options.iter().sorted().collect(); + writeln!(f, "{}", format_list_indent!(options))?; + write!(f, ")")?; + } + Ok(()) } } @@ -234,12 +184,11 @@ impl CreateDatabase { impl Display for CreateDatabase { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str("CREATE DATABASE")?; + write!(f, "CREATE DATABASE ")?; if self.if_not_exists { - f.write_str(" IF NOT EXISTS")?; + write!(f, "IF NOT EXISTS ")?; } - let name = &self.name; - write!(f, r#" {name}"#) + write!(f, "{}", &self.name) } } @@ -256,56 +205,24 @@ pub struct CreateExternalTable { pub engine: String, } -impl CreateExternalTable { - fn format_constraints(&self) -> String { - self.constraints - .iter() - .map(|c| { - if is_time_index(c) { - let TableConstraint::Unique { columns, .. } = c else { - unreachable!() - }; - - format_indent!("{}TIME INDEX ({})", format_list_comma!(columns)) - } else { - format_indent!(c) - } - }) - .join(LINE_SEP) - } -} - impl Display for CreateExternalTable { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let if_not_exists = match self.if_not_exists { - true => "IF NOT EXISTS", - false => "", - }; - - let options = if self.options.map.is_empty() { - String::default() - } else { - let options = format_sorted_hashmap!(&self.options.map); - format!( - r#"WITH( -{options} -)"# - ) - }; - - let name = &self.name; - let columns = format_list_indent!(self.columns); - let constraints = self.format_constraints(); - let engine = &self.engine; - write!( - f, - r#"CREATE EXTERNAL TABLE {if_not_exists} {name} ( -{columns}, -{constraints} -) -ENGINE={engine} -{options}"# - ) + write!(f, "CREATE EXTERNAL TABLE ")?; + if self.if_not_exists { + write!(f, "IF NOT EXISTS ")?; + } + writeln!(f, "{} (", &self.name)?; + writeln!(f, "{},", format_list_indent!(self.columns))?; + writeln!(f, "{}", format_table_constraint(&self.constraints))?; + writeln!(f, ")")?; + writeln!(f, "ENGINE={}", &self.engine)?; + if !self.options.map.is_empty() { + let options = redact_and_sort_options(&self.options); + writeln!(f, "WITH(")?; + writeln!(f, "{}", format_list_indent!(options))?; + write!(f, ")")?; + } + Ok(()) } } @@ -571,7 +488,7 @@ CREATE TABLE t2 LIKE t1"#, let new_sql = format!("\n{}", create); assert_eq!( r#" -CREATE EXTERNAL TABLE city ( +CREATE EXTERNAL TABLE city ( host STRING, ts TIMESTAMP, cpu FLOAT64 DEFAULT 0, @@ -581,7 +498,8 @@ CREATE EXTERNAL TABLE city ( ) ENGINE=file WITH( -format = csv,location = /var/data/city.csv, + format = csv, + location = /var/data/city.csv )"#, &new_sql ); diff --git a/src/sql/src/util.rs b/src/sql/src/util.rs index 0b1daec01208..c9c135225280 100644 --- a/src/sql/src/util.rs +++ b/src/sql/src/util.rs @@ -85,16 +85,6 @@ pub fn redact_sql_secrets(sql: &str) -> String { s } -/// Use regex to match and replace common seen secret values in SQL. -pub fn redact_option_secret(key: &str, val: &str) -> String { - let pattern = Regex::new(r#"(?i)(access_key_id|secret_access_key)"#).unwrap(); - if pattern.is_match(key) { - format!("{} = ******,", key) - } else { - format!("{} = {},", key, val) - } -} - #[cfg(test)] mod test { use super::*; From dc4b2e4c121c6c309f36247afde05bfa6cd36e4c Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 24 Apr 2024 10:06:18 +0800 Subject: [PATCH 15/17] fixup Signed-off-by: tison --- src/sql/src/statements/transform/type_alias.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sql/src/statements/transform/type_alias.rs b/src/sql/src/statements/transform/type_alias.rs index 353c19f68c67..464d0ca0c01a 100644 --- a/src/sql/src/statements/transform/type_alias.rs +++ b/src/sql/src/statements/transform/type_alias.rs @@ -365,7 +365,7 @@ CREATE TABLE data_types ( match &stmts[0] { Statement::CreateTable(c) => { - let expected = r#"CREATE TABLE data_types ( + let expected = r#"CREATE TABLE data_types ( s STRING, tt TEXT, mt TEXT, From 3e36273f44bb632c496a681afc648f39cb7121f6 Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 24 Apr 2024 14:18:11 +0800 Subject: [PATCH 16/17] address comments Signed-off-by: tison --- src/sql/src/statements.rs | 11 +++--- src/sql/src/statements/tql.rs | 65 ++++++++++++++++++++++------------- 2 files changed, 48 insertions(+), 28 deletions(-) diff --git a/src/sql/src/statements.rs b/src/sql/src/statements.rs index b7c6807ba53a..27a0c9327a24 100644 --- a/src/sql/src/statements.rs +++ b/src/sql/src/statements.rs @@ -61,18 +61,21 @@ use crate::error::{ const REDACTED_OPTIONS: [&str; 2] = ["access_key_id", "secret_access_key"]; +/// Convert the options into redacted and sorted key-value string. Options with key in +/// [REDACTED_OPTIONS] will be converted into ` = '******'`. fn redact_and_sort_options(options: &OptionMap) -> Vec { - let mut result = vec![]; - let keys = options.as_ref().keys().sorted(); + let options = options.as_ref(); + let mut result = Vec::with_capacity(options.len()); + let keys = options.keys().sorted(); for key in keys { if let Some(val) = options.get(key) { let redacted = REDACTED_OPTIONS .iter() .any(|opt| opt.eq_ignore_ascii_case(key)); if redacted { - result.push(format!("{key} = ******")); + result.push(format!("{key} = '******'")); } else { - result.push(format!("{key} = {val}")); + result.push(format!("{key} = '{}'", val.escape_default())); } } } diff --git a/src/sql/src/statements/tql.rs b/src/sql/src/statements/tql.rs index b9a6e0e4feb6..07f8cb8876ec 100644 --- a/src/sql/src/statements/tql.rs +++ b/src/sql/src/statements/tql.rs @@ -33,6 +33,22 @@ impl Display for Tql { } } +// TODO: encapsulate shard TQL args into a struct and implement Display for it. +fn format_tql( + f: &mut std::fmt::Formatter<'_>, + start: &str, + end: &str, + step: &str, + lookback: Option<&str>, + query: &str, +) -> std::fmt::Result { + write!(f, "({start}, {end}, {step}")?; + if let Some(lookback) = lookback { + write!(f, ", {lookback}")?; + } + write!(f, ") {query}") +} + /// TQL EVAL (, , , [lookback]) #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)] pub struct TqlEval { @@ -45,14 +61,15 @@ pub struct TqlEval { impl Display for TqlEval { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "TQL EVAL(")?; - write!(f, "{}, ", &self.start)?; - write!(f, "{}, ", &self.end)?; - write!(f, "{}", &self.step)?; - if let Some(lookback) = &self.lookback { - write!(f, ", {}", lookback)?; - } - write!(f, ") {}", &self.query) + write!(f, "TQL EVAL")?; + format_tql( + f, + &self.start, + &self.end, + &self.step, + self.lookback.as_deref(), + &self.query, + ) } } @@ -74,14 +91,14 @@ impl Display for TqlExplain { if self.is_verbose { write!(f, "VERBOSE ")?; } - write!(f, "(")?; - write!(f, "{}, ", &self.start)?; - write!(f, "{}, ", &self.end)?; - write!(f, "{}", &self.step)?; - if let Some(lookback) = &self.lookback { - write!(f, ", {}", lookback)?; - } - write!(f, ") {}", &self.query) + format_tql( + f, + &self.start, + &self.end, + &self.step, + self.lookback.as_deref(), + &self.query, + ) } } @@ -103,14 +120,14 @@ impl Display for TqlAnalyze { if self.is_verbose { write!(f, "VERBOSE ")?; } - write!(f, "(")?; - write!(f, "{}, ", &self.start)?; - write!(f, "{}, ", &self.end)?; - write!(f, "{}", &self.step)?; - if let Some(lookback) = &self.lookback { - write!(f, ", {}", lookback)?; - } - write!(f, ") {}", &self.query) + format_tql( + f, + &self.start, + &self.end, + &self.step, + self.lookback.as_deref(), + &self.query, + ) } } From a7592a7881379e59a1a898bc82e9a5840f4eb0fa Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 24 Apr 2024 14:55:09 +0800 Subject: [PATCH 17/17] fixup quote Signed-off-by: tison --- src/sql/src/statements/copy.rs | 8 ++++---- src/sql/src/statements/create.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sql/src/statements/copy.rs b/src/sql/src/statements/copy.rs index 263ff832f733..c801c3bb62fc 100644 --- a/src/sql/src/statements/copy.rs +++ b/src/sql/src/statements/copy.rs @@ -152,7 +152,7 @@ mod tests { Statement::Copy(copy) => { let new_sql = format!("{}", copy); assert_eq!( - r#"COPY tbl FROM s3://my-bucket/data.parquet WITH (format = parquet, pattern = .*parquet.*) CONNECTION (region = us-west-2, secret_access_key = ******)"#, + r#"COPY tbl FROM s3://my-bucket/data.parquet WITH (format = 'parquet', pattern = '.*parquet.*') CONNECTION (region = 'us-west-2', secret_access_key = '******')"#, &new_sql ); } @@ -177,7 +177,7 @@ mod tests { Statement::Copy(copy) => { let new_sql = format!("{}", copy); assert_eq!( - r#"COPY tbl TO s3://my-bucket/data.parquet WITH (format = parquet, pattern = .*parquet.*) CONNECTION (region = us-west-2, secret_access_key = ******)"#, + r#"COPY tbl TO s3://my-bucket/data.parquet WITH (format = 'parquet', pattern = '.*parquet.*') CONNECTION (region = 'us-west-2', secret_access_key = '******')"#, &new_sql ); } @@ -202,7 +202,7 @@ mod tests { Statement::Copy(copy) => { let new_sql = format!("{}", copy); assert_eq!( - r#"COPY DATABASE db1 FROM s3://my-bucket/data.parquet WITH (format = parquet, pattern = .*parquet.*) CONNECTION (region = us-west-2, secret_access_key = ******)"#, + r#"COPY DATABASE db1 FROM s3://my-bucket/data.parquet WITH (format = 'parquet', pattern = '.*parquet.*') CONNECTION (region = 'us-west-2', secret_access_key = '******')"#, &new_sql ); } @@ -227,7 +227,7 @@ mod tests { Statement::Copy(copy) => { let new_sql = format!("{}", copy); assert_eq!( - r#"COPY DATABASE db1 TO s3://my-bucket/data.parquet WITH (format = parquet, pattern = .*parquet.*) CONNECTION (region = us-west-2, secret_access_key = ******)"#, + r#"COPY DATABASE db1 TO s3://my-bucket/data.parquet WITH (format = 'parquet', pattern = '.*parquet.*') CONNECTION (region = 'us-west-2', secret_access_key = '******')"#, &new_sql ); } diff --git a/src/sql/src/statements/create.rs b/src/sql/src/statements/create.rs index b273ccc55278..eb992b48ef45 100644 --- a/src/sql/src/statements/create.rs +++ b/src/sql/src/statements/create.rs @@ -498,8 +498,8 @@ CREATE EXTERNAL TABLE city ( ) ENGINE=file WITH( - format = csv, - location = /var/data/city.csv + format = 'csv', + location = '/var/data/city.csv' )"#, &new_sql );