feat: impl Display for Statement (#3744)

* feat: impl Display for Statement

* fix: add license header

* fix: inline function manually

* fix: redacte options

* fix: check secret key and replace value

* test: add test for statement display

* fix: fix check

* fix: inline method

* fix: inline methods

* fix: format

* showcase how to write Display impl

Signed-off-by: tison <wander4096@gmail.com>

* for others

Signed-off-by: tison <wander4096@gmail.com>

* create and copy

Signed-off-by: tison <wander4096@gmail.com>

* create rest

Signed-off-by: tison <wander4096@gmail.com>

* fixup

Signed-off-by: tison <wander4096@gmail.com>

* address comments

Signed-off-by: tison <wander4096@gmail.com>

* fixup quote

Signed-off-by: tison <wander4096@gmail.com>

---------

Signed-off-by: tison <wander4096@gmail.com>
Co-authored-by: tison <wander4096@gmail.com>
This commit is contained in:
irenjj
2024-04-24 15:09:06 +08:00
committed by GitHub
parent 8d229dda98
commit 62037ee4c8
13 changed files with 1087 additions and 82 deletions

View File

@@ -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 {

View File

@@ -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,29 @@ use crate::error::{
SerializeColumnDefaultConstraintSnafu, TimestampOverflowSnafu, UnsupportedDefaultValueSnafu,
};
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 `<key> = '******'`.
fn redact_and_sort_options(options: &OptionMap) -> Vec<String> {
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} = '******'"));
} else {
result.push(format!("{key} = '{}'", val.escape_default()));
}
}
}
result
}
fn parse_string_to_value(
column_name: &str,
s: String,

View File

@@ -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 <table_constraint>`
@@ -53,3 +63,100 @@ pub enum AlterTableOperation {
/// `RENAME <new_table_name>`
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 {column_def} {location}"#)
} else {
write!(f, r#"ADD COLUMN {column_def}"#)
}
}
AlterTableOperation::DropColumn { name } => write!(f, r#"DROP COLUMN {name}"#),
AlterTableOperation::RenameTable { new_table_name } => {
write!(f, r#"RENAME {new_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_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!();
}
}
}
}

View File

@@ -12,10 +12,12 @@
// 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;
use crate::statements::{redact_and_sort_options, OptionMap};
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)]
pub enum Copy {
@@ -23,18 +25,77 @@ pub enum Copy {
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 {
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(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(())
}
}
#[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 {
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(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(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)]
pub struct CopyDatabaseArgument {
pub database_name: ObjectName,
@@ -67,3 +128,112 @@ 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!("{}", 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!("{}", 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!("{}", 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!("{}", 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!();
}
}
}
}

View File

@@ -20,7 +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::statements::{redact_and_sort_options, OptionMap};
const LINE_SEP: &str = ",\n";
const COMMA_SEP: &str = ", ";
@@ -47,6 +47,23 @@ macro_rules! format_list_comma {
};
}
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)
}
})
.join(LINE_SEP)
}
/// Time index name, used in table constraints.
pub const TIME_INDEX: &str = "__time_index";
@@ -74,58 +91,6 @@ pub struct CreateTable {
pub partitions: Option<Partitions>,
}
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<Ident>,
@@ -166,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(())
}
}
@@ -216,6 +182,16 @@ impl CreateDatabase {
}
}
impl Display for CreateDatabase {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "CREATE DATABASE ")?;
if self.if_not_exists {
write!(f, "IF NOT EXISTS ")?;
}
write!(f, "{}", &self.name)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut)]
pub struct CreateExternalTable {
/// Table name
@@ -229,6 +205,27 @@ pub struct CreateExternalTable {
pub engine: String,
}
impl Display for CreateExternalTable {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
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(())
}
}
#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut)]
pub struct CreateTableLike {
/// Table name
@@ -237,6 +234,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;
@@ -392,4 +397,116 @@ 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!();
}
}
}
}

View File

@@ -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#"DESCRIBE TABLE {name}"#)
}
}
#[cfg(test)]
mod tests {
use std::assert_matches::assert_matches;
@@ -108,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!();
}
}
}
}

View File

@@ -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};
@@ -41,6 +43,17 @@ impl DropTable {
}
}
impl Display for DropTable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("DROP TABLE")?;
if self.drop_if_exists() {
f.write_str(" IF EXISTS")?;
}
let table_name = self.table_name();
write!(f, r#" {table_name}"#)
}
}
/// DROP DATABASE statement.
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)]
pub struct DropDatabase {
@@ -66,3 +79,113 @@ impl DropDatabase {
self.drop_if_exists
}
}
impl Display for DropDatabase {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("DROP DATABASE")?;
if self.drop_if_exists() {
f.write_str(" IF EXISTS")?;
}
let name = self.name();
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!();
}
}
}
}

View File

@@ -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,50 @@ pub struct SetVariables {
pub variable: ObjectName,
pub value: Vec<Expr>,
}
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::<Vec<_>>()
.join(", ");
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!();
}
}
}
}

View File

@@ -12,7 +12,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};
@@ -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"),
@@ -51,6 +51,20 @@ pub struct ShowColumns {
pub full: bool,
}
impl Display for ShowColumns {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SHOW ")?;
if self.full {
write!(f, "FULL ")?;
}
write!(f, "COLUMNS IN {} ", &self.table)?;
if let Some(database) = &self.database {
write!(f, "IN {database} ")?;
}
write!(f, "{}", &self.kind)
}
}
/// The SQL `SHOW INDEX` statement
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)]
pub struct ShowIndex {
@@ -59,6 +73,16 @@ pub struct ShowIndex {
pub database: Option<String>,
}
impl Display for ShowIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SHOW INDEX IN {} ", &self.table)?;
if let Some(database) = &self.database {
write!(f, "IN {database} ")?;
}
write!(f, "{}", &self.kind)
}
}
impl ShowDatabases {
/// Creates a statement for `SHOW DATABASES`
pub fn new(kind: ShowKind) -> Self {
@@ -66,6 +90,13 @@ impl ShowDatabases {
}
}
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`.
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)]
pub struct ShowTables {
@@ -74,18 +105,46 @@ pub struct ShowTables {
pub full: bool,
}
impl Display for ShowTables {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SHOW ")?;
if self.full {
write!(f, "FULL ")?;
}
write!(f, "TABLES ")?;
if let Some(database) = &self.database {
write!(f, "IN {database} ")?;
}
write!(f, "{}", &self.kind)
}
}
/// SQL structure for `SHOW CREATE TABLE`.
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)]
pub struct ShowCreateTable {
pub table_name: ObjectName,
}
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 {table_name}"#)
}
}
/// SQL structure for `SHOW VARIABLES xxx`.
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)]
pub struct ShowVariables {
pub variable: ObjectName,
}
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;
@@ -171,4 +230,162 @@ mod tests {
)
.is_err());
}
#[test]
fn test_display_show_variables() {
let sql = r"show variables v1;";
let stmts: Vec<Statement> =
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<Statement> =
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<Statement> =
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<Statement> =
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<Statement> =
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!();
}
}
let sql = r"show full tables;";
let stmts: Vec<Statement> =
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]
fn test_display_show_databases() {
let sql = r"show databases;";
let stmts: Vec<Statement> =
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!();
}
}
}
}

View File

@@ -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};
@@ -89,6 +91,41 @@ 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.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(kind) => {
write!(f, "SHOW CHARSET {kind}")
}
Statement::ShowCollation(kind) => {
write!(f, "SHOW COLLATION {kind}")
}
}
}
}
/// Comment hints from SQL.
/// It'll be enabled when using `--comment` in mysql client.
/// Eg: `SELECT * FROM system.number LIMIT 1; -- { ErrorCode 25 }`

View File

@@ -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,32 @@ 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),
}
}
}
// 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 (<start>, <end>, <step>, [lookback]) <promql>
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)]
pub struct TqlEval {
@@ -31,6 +59,20 @@ pub struct TqlEval {
pub query: String,
}
impl Display for TqlEval {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "TQL EVAL")?;
format_tql(
f,
&self.start,
&self.end,
&self.step,
self.lookback.as_deref(),
&self.query,
)
}
}
/// TQL EXPLAIN [VERBOSE] [<start>, <end>, <step>, [lookback]] <promql>
/// 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 +85,23 @@ pub struct TqlExplain {
pub is_verbose: bool,
}
impl Display for TqlExplain {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "TQL EXPLAIN ")?;
if self.is_verbose {
write!(f, "VERBOSE ")?;
}
format_tql(
f,
&self.start,
&self.end,
&self.step,
self.lookback.as_deref(),
&self.query,
)
}
}
/// TQL ANALYZE [VERBOSE] (<start>, <end>, <step>, [lookback]) <promql>
/// executes the plan and tells the detailed per-step execution time (similar to SQL ANALYZE).
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)]
@@ -55,6 +114,23 @@ pub struct TqlAnalyze {
pub is_verbose: bool,
}
impl Display for TqlAnalyze {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "TQL ANALYZE ")?;
if self.is_verbose {
write!(f, "VERBOSE ")?;
}
format_tql(
f,
&self.start,
&self.end,
&self.step,
self.lookback.as_deref(),
&self.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`.

View File

@@ -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,

View File

@@ -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,42 @@ 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}"#)
}
}
#[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<Statement> =
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!();
}
}
}
}