blob: 4fcdefae00ee13c0fab1199de5384991067f7c72 [file] [log] [blame]
// 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
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! SQL Abstract Syntax Tree (AST) types
mod data_type;
mod ddl;
mod operator;
mod query;
mod value;
use std::fmt;
pub use self::data_type::DataType;
pub use self::ddl::{
AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef, TableConstraint,
};
pub use self::operator::{BinaryOperator, UnaryOperator};
pub use self::query::{
Cte, Fetch, Join, JoinConstraint, JoinOperator, OrderByExpr, Query, Select, SelectItem,
SetExpr, SetOperator, TableAlias, TableFactor, TableWithJoins, Values,
};
pub use self::value::{DateTimeField, Value};
struct DisplaySeparated<'a, T>
where
T: fmt::Display,
{
slice: &'a [T],
sep: &'static str,
}
impl<'a, T> fmt::Display for DisplaySeparated<'a, T>
where
T: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut delim = "";
for t in self.slice {
write!(f, "{}", delim)?;
delim = self.sep;
write!(f, "{}", t)?;
}
Ok(())
}
}
fn display_separated<'a, T>(slice: &'a [T], sep: &'static str) -> DisplaySeparated<'a, T>
where
T: fmt::Display,
{
DisplaySeparated { slice, sep }
}
fn display_comma_separated<T>(slice: &[T]) -> DisplaySeparated<'_, T>
where
T: fmt::Display,
{
DisplaySeparated { slice, sep: ", " }
}
/// Identifier name, in the originally quoted form (e.g. `"id"`)
pub type Ident = String;
/// A name of a table, view, custom type, etc., possibly multi-part, i.e. db.schema.obj
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ObjectName(pub Vec<Ident>);
impl fmt::Display for ObjectName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", display_separated(&self.0, "."))
}
}
/// An SQL expression of any type.
///
/// The parser does not distinguish between expressions of different types
/// (e.g. boolean vs string), so the caller must handle expressions of
/// inappropriate type, like `WHERE 1` or `SELECT 1=1`, as necessary.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Expr {
/// Identifier e.g. table name or column name
Identifier(Ident),
/// Unqualified wildcard (`*`). SQL allows this in limited contexts, such as:
/// - right after `SELECT` (which is represented as a [SelectItem::Wildcard] instead)
/// - or as part of an aggregate function, e.g. `COUNT(*)`,
///
/// ...but we currently also accept it in contexts where it doesn't make
/// sense, such as `* + *`
Wildcard,
/// Qualified wildcard, e.g. `alias.*` or `schema.table.*`.
/// (Same caveats apply to `QualifiedWildcard` as to `Wildcard`.)
QualifiedWildcard(Vec<Ident>),
/// Multi-part identifier, e.g. `table_alias.column` or `schema.table.col`
CompoundIdentifier(Vec<Ident>),
/// `IS NULL` expression
IsNull(Box<Expr>),
/// `IS NOT NULL` expression
IsNotNull(Box<Expr>),
/// `[ NOT ] IN (val1, val2, ...)`
InList {
expr: Box<Expr>,
list: Vec<Expr>,
negated: bool,
},
/// `[ NOT ] IN (SELECT ...)`
InSubquery {
expr: Box<Expr>,
subquery: Box<Query>,
negated: bool,
},
/// `<expr> [ NOT ] BETWEEN <low> AND <high>`
Between {
expr: Box<Expr>,
negated: bool,
low: Box<Expr>,
high: Box<Expr>,
},
/// Binary operation e.g. `1 + 1` or `foo > bar`
BinaryOp {
left: Box<Expr>,
op: BinaryOperator,
right: Box<Expr>,
},
/// Unary operation e.g. `NOT foo`
UnaryOp { op: UnaryOperator, expr: Box<Expr> },
/// CAST an expression to a different data type e.g. `CAST(foo AS VARCHAR(123))`
Cast {
expr: Box<Expr>,
data_type: DataType,
},
Extract {
field: DateTimeField,
expr: Box<Expr>,
},
/// `expr COLLATE collation`
Collate {
expr: Box<Expr>,
collation: ObjectName,
},
/// Nested expression e.g. `(foo > bar)` or `(1)`
Nested(Box<Expr>),
/// A literal value, such as string, number, date or NULL
Value(Value),
/// Scalar function call e.g. `LEFT(foo, 5)`
Function(Function),
/// `CASE [<operand>] WHEN <condition> THEN <result> ... [ELSE <result>] END`
///
/// Note we only recognize a complete single expression as `<condition>`,
/// not `< 0` nor `1, 2, 3` as allowed in a `<simple when clause>` per
/// <https://jakewheat.github.io/sql-overview/sql-2011-foundation-grammar.html#simple-when-clause>
Case {
operand: Option<Box<Expr>>,
conditions: Vec<Expr>,
results: Vec<Expr>,
else_result: Option<Box<Expr>>,
},
/// An exists expression `EXISTS(SELECT ...)`, used in expressions like
/// `WHERE EXISTS (SELECT ...)`.
Exists(Box<Query>),
/// A parenthesized subquery `(SELECT ...)`, used in expression like
/// `SELECT (subquery) AS x` or `WHERE (subquery) = x`
Subquery(Box<Query>),
}
impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Expr::Identifier(s) => f.write_str(s),
Expr::Wildcard => f.write_str("*"),
Expr::QualifiedWildcard(q) => write!(f, "{}.*", display_separated(q, ".")),
Expr::CompoundIdentifier(s) => write!(f, "{}", display_separated(s, ".")),
Expr::IsNull(ast) => write!(f, "{} IS NULL", ast),
Expr::IsNotNull(ast) => write!(f, "{} IS NOT NULL", ast),
Expr::InList {
expr,
list,
negated,
} => write!(
f,
"{} {}IN ({})",
expr,
if *negated { "NOT " } else { "" },
display_comma_separated(list)
),
Expr::InSubquery {
expr,
subquery,
negated,
} => write!(
f,
"{} {}IN ({})",
expr,
if *negated { "NOT " } else { "" },
subquery
),
Expr::Between {
expr,
negated,
low,
high,
} => write!(
f,
"{} {}BETWEEN {} AND {}",
expr,
if *negated { "NOT " } else { "" },
low,
high
),
Expr::BinaryOp { left, op, right } => write!(f, "{} {} {}", left, op, right),
Expr::UnaryOp { op, expr } => write!(f, "{} {}", op, expr),
Expr::Cast { expr, data_type } => write!(f, "CAST({} AS {})", expr, data_type),
Expr::Extract { field, expr } => write!(f, "EXTRACT({} FROM {})", field, expr),
Expr::Collate { expr, collation } => write!(f, "{} COLLATE {}", expr, collation),
Expr::Nested(ast) => write!(f, "({})", ast),
Expr::Value(v) => write!(f, "{}", v),
Expr::Function(fun) => write!(f, "{}", fun),
Expr::Case {
operand,
conditions,
results,
else_result,
} => {
f.write_str("CASE")?;
if let Some(operand) = operand {
write!(f, " {}", operand)?;
}
for (c, r) in conditions.iter().zip(results) {
write!(f, " WHEN {} THEN {}", c, r)?;
}
if let Some(else_result) = else_result {
write!(f, " ELSE {}", else_result)?;
}
f.write_str(" END")
}
Expr::Exists(s) => write!(f, "EXISTS ({})", s),
Expr::Subquery(s) => write!(f, "({})", s),
}
}
}
/// A window specification (i.e. `OVER (PARTITION BY .. ORDER BY .. etc.)`)
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct WindowSpec {
pub partition_by: Vec<Expr>,
pub order_by: Vec<OrderByExpr>,
pub window_frame: Option<WindowFrame>,
}
impl fmt::Display for WindowSpec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut delim = "";
if !self.partition_by.is_empty() {
delim = " ";
write!(
f,
"PARTITION BY {}",
display_comma_separated(&self.partition_by)
)?;
}
if !self.order_by.is_empty() {
f.write_str(delim)?;
delim = " ";
write!(f, "ORDER BY {}", display_comma_separated(&self.order_by))?;
}
if let Some(window_frame) = &self.window_frame {
if let Some(end_bound) = &window_frame.end_bound {
f.write_str(delim)?;
write!(
f,
"{} BETWEEN {} AND {}",
window_frame.units, window_frame.start_bound, end_bound
)?;
} else {
f.write_str(delim)?;
write!(f, "{} {}", window_frame.units, window_frame.start_bound)?;
}
}
Ok(())
}
}
/// Specifies the data processed by a window function, e.g.
/// `RANGE UNBOUNDED PRECEDING` or `ROWS BETWEEN 5 PRECEDING AND CURRENT ROW`.
///
/// Note: The parser does not validate the specified bounds; the caller should
/// reject invalid bounds like `ROWS UNBOUNDED FOLLOWING` before execution.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct WindowFrame {
pub units: WindowFrameUnits,
pub start_bound: WindowFrameBound,
/// The right bound of the `BETWEEN .. AND` clause. The end bound of `None`
/// indicates the shorthand form (e.g. `ROWS 1 PRECEDING`), which must
/// behave the same as `end_bound = WindowFrameBound::CurrentRow`.
pub end_bound: Option<WindowFrameBound>,
// TBD: EXCLUDE
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum WindowFrameUnits {
Rows,
Range,
Groups,
}
impl fmt::Display for WindowFrameUnits {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self {
WindowFrameUnits::Rows => "ROWS",
WindowFrameUnits::Range => "RANGE",
WindowFrameUnits::Groups => "GROUPS",
})
}
}
impl FromStr for WindowFrameUnits {
type Err = ParserError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ROWS" => Ok(WindowFrameUnits::Rows),
"RANGE" => Ok(WindowFrameUnits::Range),
"GROUPS" => Ok(WindowFrameUnits::Groups),
_ => Err(ParserError::ParserError(format!(
"Expected ROWS, RANGE, or GROUPS, found: {}",
s
))),
}
}
}
/// Specifies [WindowFrame]'s `start_bound` and `end_bound`
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum WindowFrameBound {
/// `CURRENT ROW`
CurrentRow,
/// `<N> PRECEDING` or `UNBOUNDED PRECEDING`
Preceding(Option<u64>),
/// `<N> FOLLOWING` or `UNBOUNDED FOLLOWING`.
Following(Option<u64>),
}
impl fmt::Display for WindowFrameBound {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
WindowFrameBound::CurrentRow => f.write_str("CURRENT ROW"),
WindowFrameBound::Preceding(None) => f.write_str("UNBOUNDED PRECEDING"),
WindowFrameBound::Following(None) => f.write_str("UNBOUNDED FOLLOWING"),
WindowFrameBound::Preceding(Some(n)) => write!(f, "{} PRECEDING", n),
WindowFrameBound::Following(Some(n)) => write!(f, "{} FOLLOWING", n),
}
}
}
/// A top-level statement (SELECT, INSERT, CREATE, etc.)
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Statement {
/// SELECT
Query(Box<Query>),
/// INSERT
Insert {
/// TABLE
table_name: ObjectName,
/// COLUMNS
columns: Vec<Ident>,
/// A SQL query that specifies what to insert
source: Box<Query>,
},
Copy {
/// TABLE
table_name: ObjectName,
/// COLUMNS
columns: Vec<Ident>,
/// VALUES a vector of values to be copied
values: Vec<Option<String>>,
},
/// UPDATE
Update {
/// TABLE
table_name: ObjectName,
/// Column assignments
assignments: Vec<Assignment>,
/// WHERE
selection: Option<Expr>,
},
/// DELETE
Delete {
/// FROM
table_name: ObjectName,
/// WHERE
selection: Option<Expr>,
},
/// CREATE VIEW
CreateView {
/// View name
name: ObjectName,
columns: Vec<Ident>,
query: Box<Query>,
materialized: bool,
with_options: Vec<SqlOption>,
},
/// CREATE TABLE
CreateTable {
/// Table name
name: ObjectName,
/// Optional schema
columns: Vec<ColumnDef>,
constraints: Vec<TableConstraint>,
with_options: Vec<SqlOption>,
external: bool,
file_format: Option<FileFormat>,
location: Option<String>,
},
/// ALTER TABLE
AlterTable {
/// Table name
name: ObjectName,
operation: AlterTableOperation,
},
/// DROP
Drop {
/// The type of the object to drop: TABLE, VIEW, etc.
object_type: ObjectType,
/// An optional `IF EXISTS` clause. (Non-standard.)
if_exists: bool,
/// One or more objects to drop. (ANSI SQL requires exactly one.)
names: Vec<ObjectName>,
/// Whether `CASCADE` was specified. This will be `false` when
/// `RESTRICT` or no drop behavior at all was specified.
cascade: bool,
},
/// SET <variable>
///
/// Note: this is not a standard SQL statement, but it is supported by at
/// least MySQL and PostgreSQL. Not all MySQL-specific syntatic forms are
/// supported yet.
SetVariable {
local: bool,
variable: Ident,
value: SetVariableValue,
},
/// SHOW <variable>
///
/// Note: this is a PostgreSQL-specific statement.
ShowVariable { variable: Ident },
/// SHOW COLUMNS
///
/// Note: this is a MySQL-specific statement.
ShowColumns {
extended: bool,
full: bool,
table_name: ObjectName,
filter: Option<ShowStatementFilter>,
},
/// `{ BEGIN [ TRANSACTION | WORK ] | START TRANSACTION } ...`
StartTransaction { modes: Vec<TransactionMode> },
/// `SET TRANSACTION ...`
SetTransaction { modes: Vec<TransactionMode> },
/// `COMMIT [ TRANSACTION | WORK ] [ AND [ NO ] CHAIN ]`
Commit { chain: bool },
/// `ROLLBACK [ TRANSACTION | WORK ] [ AND [ NO ] CHAIN ]`
Rollback { chain: bool },
}
impl fmt::Display for Statement {
// Clippy thinks this function is too complicated, but it is painful to
// split up without extracting structs for each `Statement` variant.
#[allow(clippy::cognitive_complexity)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Statement::Query(s) => write!(f, "{}", s),
Statement::Insert {
table_name,
columns,
source,
} => {
write!(f, "INSERT INTO {} ", table_name)?;
if !columns.is_empty() {
write!(f, "({}) ", display_comma_separated(columns))?;
}
write!(f, "{}", source)
}
Statement::Copy {
table_name,
columns,
values,
} => {
write!(f, "COPY {}", table_name)?;
if !columns.is_empty() {
write!(f, " ({})", display_comma_separated(columns))?;
}
write!(f, " FROM stdin; ")?;
if !values.is_empty() {
writeln!(f)?;
let mut delim = "";
for v in values {
write!(f, "{}", delim)?;
delim = "\t";
if let Some(v) = v {
write!(f, "{}", v)?;
} else {
write!(f, "\\N")?;
}
}
}
write!(f, "\n\\.")
}
Statement::Update {
table_name,
assignments,
selection,
} => {
write!(f, "UPDATE {}", table_name)?;
if !assignments.is_empty() {
write!(f, " SET ")?;
write!(f, "{}", display_comma_separated(assignments))?;
}
if let Some(selection) = selection {
write!(f, " WHERE {}", selection)?;
}
Ok(())
}
Statement::Delete {
table_name,
selection,
} => {
write!(f, "DELETE FROM {}", table_name)?;
if let Some(selection) = selection {
write!(f, " WHERE {}", selection)?;
}
Ok(())
}
Statement::CreateView {
name,
columns,
query,
materialized,
with_options,
} => {
write!(f, "CREATE")?;
if *materialized {
write!(f, " MATERIALIZED")?;
}
write!(f, " VIEW {}", name)?;
if !with_options.is_empty() {
write!(f, " WITH ({})", display_comma_separated(with_options))?;
}
if !columns.is_empty() {
write!(f, " ({})", display_comma_separated(columns))?;
}
write!(f, " AS {}", query)
}
Statement::CreateTable {
name,
columns,
constraints,
with_options,
external,
file_format,
location,
} => {
write!(
f,
"CREATE {}TABLE {} ({}",
if *external { "EXTERNAL " } else { "" },
name,
display_comma_separated(columns)
)?;
if !constraints.is_empty() {
write!(f, ", {}", display_comma_separated(constraints))?;
}
write!(f, ")")?;
if *external {
write!(
f,
" STORED AS {} LOCATION '{}'",
file_format.as_ref().unwrap(),
location.as_ref().unwrap()
)?;
}
if !with_options.is_empty() {
write!(f, " WITH ({})", display_comma_separated(with_options))?;
}
Ok(())
}
Statement::AlterTable { name, operation } => {
write!(f, "ALTER TABLE {} {}", name, operation)
}
Statement::Drop {
object_type,
if_exists,
names,
cascade,
} => write!(
f,
"DROP {}{} {}{}",
object_type,
if *if_exists { " IF EXISTS" } else { "" },
display_comma_separated(names),
if *cascade { " CASCADE" } else { "" },
),
Statement::SetVariable {
local,
variable,
value,
} => {
f.write_str("SET ")?;
if *local {
f.write_str("LOCAL ")?;
}
write!(f, "{} = {}", variable, value)
}
Statement::ShowVariable { variable } => write!(f, "SHOW {}", variable),
Statement::ShowColumns {
extended,
full,
table_name,
filter,
} => {
f.write_str("SHOW ")?;
if *extended {
f.write_str("EXTENDED ")?;
}
if *full {
f.write_str("FULL ")?;
}
write!(f, "COLUMNS FROM {}", table_name)?;
if let Some(filter) = filter {
write!(f, " {}", filter)?;
}
Ok(())
}
Statement::StartTransaction { modes } => {
write!(f, "START TRANSACTION")?;
if !modes.is_empty() {
write!(f, " {}", display_comma_separated(modes))?;
}
Ok(())
}
Statement::SetTransaction { modes } => {
write!(f, "SET TRANSACTION")?;
if !modes.is_empty() {
write!(f, " {}", display_comma_separated(modes))?;
}
Ok(())
}
Statement::Commit { chain } => {
write!(f, "COMMIT{}", if *chain { " AND CHAIN" } else { "" },)
}
Statement::Rollback { chain } => {
write!(f, "ROLLBACK{}", if *chain { " AND CHAIN" } else { "" },)
}
}
}
}
/// SQL assignment `foo = expr` as used in SQLUpdate
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Assignment {
pub id: Ident,
pub value: Expr,
}
impl fmt::Display for Assignment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} = {}", self.id, self.value)
}
}
/// A function call
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Function {
pub name: ObjectName,
pub args: Vec<Expr>,
pub over: Option<WindowSpec>,
// aggregate functions may specify eg `COUNT(DISTINCT x)`
pub distinct: bool,
}
impl fmt::Display for Function {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}({}{})",
self.name,
if self.distinct { "DISTINCT " } else { "" },
display_comma_separated(&self.args),
)?;
if let Some(o) = &self.over {
write!(f, " OVER ({})", o)?;
}
Ok(())
}
}
/// External table's available file format
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum FileFormat {
TEXTFILE,
SEQUENCEFILE,
ORC,
PARQUET,
AVRO,
RCFILE,
JSONFILE,
}
impl fmt::Display for FileFormat {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::FileFormat::*;
f.write_str(match self {
TEXTFILE => "TEXTFILE",
SEQUENCEFILE => "SEQUENCEFILE",
ORC => "ORC",
PARQUET => "PARQUET",
AVRO => "AVRO",
RCFILE => "RCFILE",
JSONFILE => "TEXTFILE",
})
}
}
use crate::parser::ParserError;
use std::str::FromStr;
impl FromStr for FileFormat {
type Err = ParserError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use self::FileFormat::*;
match s {
"TEXTFILE" => Ok(TEXTFILE),
"SEQUENCEFILE" => Ok(SEQUENCEFILE),
"ORC" => Ok(ORC),
"PARQUET" => Ok(PARQUET),
"AVRO" => Ok(AVRO),
"RCFILE" => Ok(RCFILE),
"JSONFILE" => Ok(JSONFILE),
_ => Err(ParserError::ParserError(format!(
"Unexpected file format: {}",
s
))),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ObjectType {
Table,
View,
}
impl fmt::Display for ObjectType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self {
ObjectType::Table => "TABLE",
ObjectType::View => "VIEW",
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SqlOption {
pub name: Ident,
pub value: Value,
}
impl fmt::Display for SqlOption {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} = {}", self.name, self.value)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TransactionMode {
AccessMode(TransactionAccessMode),
IsolationLevel(TransactionIsolationLevel),
}
impl fmt::Display for TransactionMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use TransactionMode::*;
match self {
AccessMode(access_mode) => write!(f, "{}", access_mode),
IsolationLevel(iso_level) => write!(f, "ISOLATION LEVEL {}", iso_level),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TransactionAccessMode {
ReadOnly,
ReadWrite,
}
impl fmt::Display for TransactionAccessMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use TransactionAccessMode::*;
f.write_str(match self {
ReadOnly => "READ ONLY",
ReadWrite => "READ WRITE",
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TransactionIsolationLevel {
ReadUncommitted,
ReadCommitted,
RepeatableRead,
Serializable,
}
impl fmt::Display for TransactionIsolationLevel {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use TransactionIsolationLevel::*;
f.write_str(match self {
ReadUncommitted => "READ UNCOMMITTED",
ReadCommitted => "READ COMMITTED",
RepeatableRead => "REPEATABLE READ",
Serializable => "SERIALIZABLE",
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ShowStatementFilter {
Like(String),
Where(Expr),
}
impl fmt::Display for ShowStatementFilter {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use ShowStatementFilter::*;
match self {
Like(pattern) => write!(f, "LIKE '{}'", value::escape_single_quote_string(pattern)),
Where(expr) => write!(f, "WHERE {}", expr),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SetVariableValue {
Ident(Ident),
Literal(Value),
}
impl fmt::Display for SetVariableValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use SetVariableValue::*;
match self {
Ident(ident) => f.write_str(ident),
Literal(literal) => write!(f, "{}", literal),
}
}
}