mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2025-12-22 22:20:02 +00:00
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:
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }`
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user