mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-07-16 04:54:59 +00:00
1196 lines
38 KiB
Rust
1196 lines
38 KiB
Rust
// 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.
|
|
|
|
#[cfg(not(feature = "std"))]
|
|
use alloc::{boxed::Box, vec::Vec};
|
|
|
|
#[cfg(feature = "serde")]
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[cfg(feature = "visitor")]
|
|
use sqlparser_derive::{Visit, VisitMut};
|
|
|
|
use crate::ast::*;
|
|
|
|
/// The most complete variant of a `SELECT` query expression, optionally
|
|
/// including `WITH`, `UNION` / other set operations, and `ORDER BY`.
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct Query {
|
|
/// WITH (common table expressions, or CTEs)
|
|
pub with: Option<With>,
|
|
/// SELECT or UNION / EXCEPT / INTERSECT
|
|
pub body: Box<SetExpr>,
|
|
/// ORDER BY
|
|
pub order_by: Vec<OrderByExpr>,
|
|
/// `LIMIT { <N> | ALL }`
|
|
pub limit: Option<Expr>,
|
|
/// `OFFSET <N> [ { ROW | ROWS } ]`
|
|
pub offset: Option<Offset>,
|
|
/// `FETCH { FIRST | NEXT } <N> [ PERCENT ] { ROW | ROWS } | { ONLY | WITH TIES }`
|
|
pub fetch: Option<Fetch>,
|
|
/// `FOR { UPDATE | SHARE } [ OF table_name ] [ SKIP LOCKED | NOWAIT ]`
|
|
pub locks: Vec<LockClause>,
|
|
}
|
|
|
|
impl fmt::Display for Query {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
if let Some(ref with) = self.with {
|
|
write!(f, "{with} ")?;
|
|
}
|
|
write!(f, "{}", self.body)?;
|
|
if !self.order_by.is_empty() {
|
|
write!(f, " ORDER BY {}", display_comma_separated(&self.order_by))?;
|
|
}
|
|
if let Some(ref limit) = self.limit {
|
|
write!(f, " LIMIT {limit}")?;
|
|
}
|
|
if let Some(ref offset) = self.offset {
|
|
write!(f, " {offset}")?;
|
|
}
|
|
if let Some(ref fetch) = self.fetch {
|
|
write!(f, " {fetch}")?;
|
|
}
|
|
if !self.locks.is_empty() {
|
|
write!(f, " {}", display_separated(&self.locks, " "))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// A node in a tree, representing a "query body" expression, roughly:
|
|
/// `SELECT ... [ {UNION|EXCEPT|INTERSECT} SELECT ...]`
|
|
#[allow(clippy::large_enum_variant)]
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub enum SetExpr {
|
|
/// Restricted SELECT .. FROM .. HAVING (no ORDER BY or set operations)
|
|
Select(Box<Select>),
|
|
/// Parenthesized SELECT subquery, which may include more set operations
|
|
/// in its body and an optional ORDER BY / LIMIT.
|
|
Query(Box<Query>),
|
|
/// UNION/EXCEPT/INTERSECT of two queries
|
|
SetOperation {
|
|
op: SetOperator,
|
|
set_quantifier: SetQuantifier,
|
|
left: Box<SetExpr>,
|
|
right: Box<SetExpr>,
|
|
},
|
|
Values(Values),
|
|
Insert(Statement),
|
|
Update(Statement),
|
|
Table(Box<Table>),
|
|
}
|
|
|
|
impl fmt::Display for SetExpr {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
SetExpr::Select(s) => write!(f, "{s}"),
|
|
SetExpr::Query(q) => write!(f, "({q})"),
|
|
SetExpr::Values(v) => write!(f, "{v}"),
|
|
SetExpr::Insert(v) => write!(f, "{v}"),
|
|
SetExpr::Update(v) => write!(f, "{v}"),
|
|
SetExpr::Table(t) => write!(f, "{t}"),
|
|
SetExpr::SetOperation {
|
|
left,
|
|
right,
|
|
op,
|
|
set_quantifier,
|
|
} => {
|
|
write!(f, "{left} {op}")?;
|
|
match set_quantifier {
|
|
SetQuantifier::All
|
|
| SetQuantifier::Distinct
|
|
| SetQuantifier::ByName
|
|
| SetQuantifier::AllByName => write!(f, " {set_quantifier}")?,
|
|
SetQuantifier::None => write!(f, "{set_quantifier}")?,
|
|
}
|
|
write!(f, " {right}")?;
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub enum SetOperator {
|
|
Union,
|
|
Except,
|
|
Intersect,
|
|
}
|
|
|
|
impl fmt::Display for SetOperator {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
f.write_str(match self {
|
|
SetOperator::Union => "UNION",
|
|
SetOperator::Except => "EXCEPT",
|
|
SetOperator::Intersect => "INTERSECT",
|
|
})
|
|
}
|
|
}
|
|
|
|
/// A quantifier for [SetOperator].
|
|
// TODO: Restrict parsing specific SetQuantifier in some specific dialects.
|
|
// For example, BigQuery does not support `DISTINCT` for `EXCEPT` and `INTERSECT`
|
|
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub enum SetQuantifier {
|
|
All,
|
|
Distinct,
|
|
ByName,
|
|
AllByName,
|
|
None,
|
|
}
|
|
|
|
impl fmt::Display for SetQuantifier {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
SetQuantifier::All => write!(f, "ALL"),
|
|
SetQuantifier::Distinct => write!(f, "DISTINCT"),
|
|
SetQuantifier::ByName => write!(f, "BY NAME"),
|
|
SetQuantifier::AllByName => write!(f, "ALL BY NAME"),
|
|
SetQuantifier::None => write!(f, ""),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
/// A [`TABLE` command]( https://www.postgresql.org/docs/current/sql-select.html#SQL-TABLE)
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct Table {
|
|
pub table_name: Option<String>,
|
|
pub schema_name: Option<String>,
|
|
}
|
|
|
|
impl fmt::Display for Table {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
if let Some(ref schema_name) = self.schema_name {
|
|
write!(
|
|
f,
|
|
"TABLE {}.{}",
|
|
schema_name,
|
|
self.table_name.as_ref().unwrap(),
|
|
)?;
|
|
} else {
|
|
write!(f, "TABLE {}", self.table_name.as_ref().unwrap(),)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// A restricted variant of `SELECT` (without CTEs/`ORDER BY`), which may
|
|
/// appear either as the only body item of a `Query`, or as an operand
|
|
/// to a set operation like `UNION`.
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct Select {
|
|
pub distinct: Option<Distinct>,
|
|
/// MSSQL syntax: `TOP (<N>) [ PERCENT ] [ WITH TIES ]`
|
|
pub top: Option<Top>,
|
|
/// projection expressions
|
|
pub projection: Vec<SelectItem>,
|
|
/// INTO
|
|
pub into: Option<SelectInto>,
|
|
/// FROM
|
|
pub from: Vec<TableWithJoins>,
|
|
/// LATERAL VIEWs
|
|
pub lateral_views: Vec<LateralView>,
|
|
/// WHERE
|
|
pub selection: Option<Expr>,
|
|
/// GROUP BY
|
|
pub group_by: Vec<Expr>,
|
|
/// CLUSTER BY (Hive)
|
|
pub cluster_by: Vec<Expr>,
|
|
/// DISTRIBUTE BY (Hive)
|
|
pub distribute_by: Vec<Expr>,
|
|
/// SORT BY (Hive)
|
|
pub sort_by: Vec<Expr>,
|
|
/// HAVING
|
|
pub having: Option<Expr>,
|
|
/// WINDOW AS
|
|
pub named_window: Vec<NamedWindowDefinition>,
|
|
/// QUALIFY (Snowflake)
|
|
pub qualify: Option<Expr>,
|
|
}
|
|
|
|
impl fmt::Display for Select {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "SELECT")?;
|
|
if let Some(ref distinct) = self.distinct {
|
|
write!(f, " {distinct}")?;
|
|
}
|
|
if let Some(ref top) = self.top {
|
|
write!(f, " {top}")?;
|
|
}
|
|
write!(f, " {}", display_comma_separated(&self.projection))?;
|
|
|
|
if let Some(ref into) = self.into {
|
|
write!(f, " {into}")?;
|
|
}
|
|
|
|
if !self.from.is_empty() {
|
|
write!(f, " FROM {}", display_comma_separated(&self.from))?;
|
|
}
|
|
if !self.lateral_views.is_empty() {
|
|
for lv in &self.lateral_views {
|
|
write!(f, "{lv}")?;
|
|
}
|
|
}
|
|
if let Some(ref selection) = self.selection {
|
|
write!(f, " WHERE {selection}")?;
|
|
}
|
|
if !self.group_by.is_empty() {
|
|
write!(f, " GROUP BY {}", display_comma_separated(&self.group_by))?;
|
|
}
|
|
if !self.cluster_by.is_empty() {
|
|
write!(
|
|
f,
|
|
" CLUSTER BY {}",
|
|
display_comma_separated(&self.cluster_by)
|
|
)?;
|
|
}
|
|
if !self.distribute_by.is_empty() {
|
|
write!(
|
|
f,
|
|
" DISTRIBUTE BY {}",
|
|
display_comma_separated(&self.distribute_by)
|
|
)?;
|
|
}
|
|
if !self.sort_by.is_empty() {
|
|
write!(f, " SORT BY {}", display_comma_separated(&self.sort_by))?;
|
|
}
|
|
if let Some(ref having) = self.having {
|
|
write!(f, " HAVING {having}")?;
|
|
}
|
|
if !self.named_window.is_empty() {
|
|
write!(f, " WINDOW {}", display_comma_separated(&self.named_window))?;
|
|
}
|
|
if let Some(ref qualify) = self.qualify {
|
|
write!(f, " QUALIFY {qualify}")?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// A hive LATERAL VIEW with potential column aliases
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct LateralView {
|
|
/// LATERAL VIEW
|
|
pub lateral_view: Expr,
|
|
/// LATERAL VIEW table name
|
|
pub lateral_view_name: ObjectName,
|
|
/// LATERAL VIEW optional column aliases
|
|
pub lateral_col_alias: Vec<Ident>,
|
|
/// LATERAL VIEW OUTER
|
|
pub outer: bool,
|
|
}
|
|
|
|
impl fmt::Display for LateralView {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(
|
|
f,
|
|
" LATERAL VIEW{outer} {} {}",
|
|
self.lateral_view,
|
|
self.lateral_view_name,
|
|
outer = if self.outer { " OUTER" } else { "" }
|
|
)?;
|
|
if !self.lateral_col_alias.is_empty() {
|
|
write!(
|
|
f,
|
|
" AS {}",
|
|
display_comma_separated(&self.lateral_col_alias)
|
|
)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct NamedWindowDefinition(pub Ident, pub WindowSpec);
|
|
|
|
impl fmt::Display for NamedWindowDefinition {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "{} AS ({})", self.0, self.1)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct With {
|
|
pub recursive: bool,
|
|
pub cte_tables: Vec<Cte>,
|
|
}
|
|
|
|
impl fmt::Display for With {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(
|
|
f,
|
|
"WITH {}{}",
|
|
if self.recursive { "RECURSIVE " } else { "" },
|
|
display_comma_separated(&self.cte_tables)
|
|
)
|
|
}
|
|
}
|
|
|
|
/// A single CTE (used after `WITH`): `alias [(col1, col2, ...)] AS ( query )`
|
|
/// The names in the column list before `AS`, when specified, replace the names
|
|
/// of the columns returned by the query. The parser does not validate that the
|
|
/// number of columns in the query matches the number of columns in the query.
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct Cte {
|
|
pub alias: TableAlias,
|
|
pub query: Box<Query>,
|
|
pub from: Option<Ident>,
|
|
}
|
|
|
|
impl fmt::Display for Cte {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "{} AS ({})", self.alias, self.query)?;
|
|
if let Some(ref fr) = self.from {
|
|
write!(f, " FROM {fr}")?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// One item of the comma-separated list following `SELECT`
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub enum SelectItem {
|
|
/// Any expression, not followed by `[ AS ] alias`
|
|
UnnamedExpr(Expr),
|
|
/// An expression, followed by `[ AS ] alias`
|
|
ExprWithAlias { expr: Expr, alias: Ident },
|
|
/// `alias.*` or even `schema.table.*`
|
|
QualifiedWildcard(ObjectName, WildcardAdditionalOptions),
|
|
/// An unqualified `*`
|
|
Wildcard(WildcardAdditionalOptions),
|
|
}
|
|
|
|
/// Single aliased identifier
|
|
///
|
|
/// # Syntax
|
|
/// ```plaintext
|
|
/// <ident> AS <alias>
|
|
/// ```
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct IdentWithAlias {
|
|
pub ident: Ident,
|
|
pub alias: Ident,
|
|
}
|
|
|
|
impl fmt::Display for IdentWithAlias {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "{} AS {}", self.ident, self.alias)
|
|
}
|
|
}
|
|
|
|
/// Additional options for wildcards, e.g. Snowflake `EXCLUDE`/`RENAME` and Bigquery `EXCEPT`.
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Default)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct WildcardAdditionalOptions {
|
|
/// `[EXCLUDE...]`.
|
|
pub opt_exclude: Option<ExcludeSelectItem>,
|
|
/// `[EXCEPT...]`.
|
|
pub opt_except: Option<ExceptSelectItem>,
|
|
/// `[RENAME ...]`.
|
|
pub opt_rename: Option<RenameSelectItem>,
|
|
/// `[REPLACE]`
|
|
/// BigQuery syntax: <https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#select_replace>
|
|
pub opt_replace: Option<ReplaceSelectItem>,
|
|
}
|
|
|
|
impl fmt::Display for WildcardAdditionalOptions {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
if let Some(exclude) = &self.opt_exclude {
|
|
write!(f, " {exclude}")?;
|
|
}
|
|
if let Some(except) = &self.opt_except {
|
|
write!(f, " {except}")?;
|
|
}
|
|
if let Some(rename) = &self.opt_rename {
|
|
write!(f, " {rename}")?;
|
|
}
|
|
if let Some(replace) = &self.opt_replace {
|
|
write!(f, " {replace}")?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Snowflake `EXCLUDE` information.
|
|
///
|
|
/// # Syntax
|
|
/// ```plaintext
|
|
/// <col_name>
|
|
/// | (<col_name>, <col_name>, ...)
|
|
/// ```
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub enum ExcludeSelectItem {
|
|
/// Single column name without parenthesis.
|
|
///
|
|
/// # Syntax
|
|
/// ```plaintext
|
|
/// <col_name>
|
|
/// ```
|
|
Single(Ident),
|
|
/// Multiple column names inside parenthesis.
|
|
/// # Syntax
|
|
/// ```plaintext
|
|
/// (<col_name>, <col_name>, ...)
|
|
/// ```
|
|
Multiple(Vec<Ident>),
|
|
}
|
|
|
|
impl fmt::Display for ExcludeSelectItem {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "EXCLUDE")?;
|
|
match self {
|
|
Self::Single(column) => {
|
|
write!(f, " {column}")?;
|
|
}
|
|
Self::Multiple(columns) => {
|
|
write!(f, " ({})", display_comma_separated(columns))?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Snowflake `RENAME` information.
|
|
///
|
|
/// # Syntax
|
|
/// ```plaintext
|
|
/// <col_name> AS <col_alias>
|
|
/// | (<col_name> AS <col_alias>, <col_name> AS <col_alias>, ...)
|
|
/// ```
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub enum RenameSelectItem {
|
|
/// Single column name with alias without parenthesis.
|
|
///
|
|
/// # Syntax
|
|
/// ```plaintext
|
|
/// <col_name> AS <col_alias>
|
|
/// ```
|
|
Single(IdentWithAlias),
|
|
/// Multiple column names with aliases inside parenthesis.
|
|
/// # Syntax
|
|
/// ```plaintext
|
|
/// (<col_name> AS <col_alias>, <col_name> AS <col_alias>, ...)
|
|
/// ```
|
|
Multiple(Vec<IdentWithAlias>),
|
|
}
|
|
|
|
impl fmt::Display for RenameSelectItem {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "RENAME")?;
|
|
match self {
|
|
Self::Single(column) => {
|
|
write!(f, " {column}")?;
|
|
}
|
|
Self::Multiple(columns) => {
|
|
write!(f, " ({})", display_comma_separated(columns))?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Bigquery `EXCEPT` information, with at least one column.
|
|
///
|
|
/// # Syntax
|
|
/// ```plaintext
|
|
/// EXCEPT (<col_name> [, ...])
|
|
/// ```
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct ExceptSelectItem {
|
|
/// First guaranteed column.
|
|
pub first_element: Ident,
|
|
/// Additional columns. This list can be empty.
|
|
pub additional_elements: Vec<Ident>,
|
|
}
|
|
|
|
impl fmt::Display for ExceptSelectItem {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "EXCEPT ")?;
|
|
if self.additional_elements.is_empty() {
|
|
write!(f, "({})", self.first_element)?;
|
|
} else {
|
|
write!(
|
|
f,
|
|
"({}, {})",
|
|
self.first_element,
|
|
display_comma_separated(&self.additional_elements)
|
|
)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Bigquery `REPLACE` information.
|
|
///
|
|
/// # Syntax
|
|
/// ```plaintext
|
|
/// REPLACE (<new_expr> [AS] <col_name>)
|
|
/// REPLACE (<col_name> [AS] <col_alias>, <col_name> [AS] <col_alias>, ...)
|
|
/// ```
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct ReplaceSelectItem {
|
|
pub items: Vec<Box<ReplaceSelectElement>>,
|
|
}
|
|
|
|
impl fmt::Display for ReplaceSelectItem {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "REPLACE")?;
|
|
write!(f, " ({})", display_comma_separated(&self.items))?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// # Syntax
|
|
/// ```plaintext
|
|
/// <expr> [AS] <column_name>
|
|
/// ```
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct ReplaceSelectElement {
|
|
pub expr: Expr,
|
|
pub column_name: Ident,
|
|
pub as_keyword: bool,
|
|
}
|
|
|
|
impl fmt::Display for ReplaceSelectElement {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
if self.as_keyword {
|
|
write!(f, "{} AS {}", self.expr, self.column_name)
|
|
} else {
|
|
write!(f, "{} {}", self.expr, self.column_name)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for SelectItem {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match &self {
|
|
SelectItem::UnnamedExpr(expr) => write!(f, "{expr}"),
|
|
SelectItem::ExprWithAlias { expr, alias } => write!(f, "{expr} AS {alias}"),
|
|
SelectItem::QualifiedWildcard(prefix, additional_options) => {
|
|
write!(f, "{prefix}.*")?;
|
|
write!(f, "{additional_options}")?;
|
|
Ok(())
|
|
}
|
|
SelectItem::Wildcard(additional_options) => {
|
|
write!(f, "*")?;
|
|
write!(f, "{additional_options}")?;
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct TableWithJoins {
|
|
pub relation: TableFactor,
|
|
pub joins: Vec<Join>,
|
|
}
|
|
|
|
impl fmt::Display for TableWithJoins {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "{}", self.relation)?;
|
|
for join in &self.joins {
|
|
write!(f, "{join}")?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// A table name or a parenthesized subquery with an optional alias
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub enum TableFactor {
|
|
Table {
|
|
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
|
|
name: ObjectName,
|
|
alias: Option<TableAlias>,
|
|
/// Arguments of a table-valued function, as supported by Postgres
|
|
/// and MSSQL. Note that deprecated MSSQL `FROM foo (NOLOCK)` syntax
|
|
/// will also be parsed as `args`.
|
|
///
|
|
/// This field's value is `Some(v)`, where `v` is a (possibly empty)
|
|
/// vector of arguments, in the case of a table-valued function call,
|
|
/// whereas it's `None` in the case of a regular table name.
|
|
args: Option<Vec<FunctionArg>>,
|
|
/// MSSQL-specific `WITH (...)` hints such as NOLOCK.
|
|
with_hints: Vec<Expr>,
|
|
},
|
|
Derived {
|
|
lateral: bool,
|
|
subquery: Box<Query>,
|
|
alias: Option<TableAlias>,
|
|
},
|
|
/// `TABLE(<expr>)[ AS <alias> ]`
|
|
TableFunction {
|
|
expr: Expr,
|
|
alias: Option<TableAlias>,
|
|
},
|
|
/// ```sql
|
|
/// SELECT * FROM UNNEST ([10,20,30]) as numbers WITH OFFSET;
|
|
/// +---------+--------+
|
|
/// | numbers | offset |
|
|
/// +---------+--------+
|
|
/// | 10 | 0 |
|
|
/// | 20 | 1 |
|
|
/// | 30 | 2 |
|
|
/// +---------+--------+
|
|
/// ```
|
|
UNNEST {
|
|
alias: Option<TableAlias>,
|
|
array_exprs: Vec<Expr>,
|
|
with_offset: bool,
|
|
with_offset_alias: Option<Ident>,
|
|
},
|
|
/// Represents a parenthesized table factor. The SQL spec only allows a
|
|
/// join expression (`(foo <JOIN> bar [ <JOIN> baz ... ])`) to be nested,
|
|
/// possibly several times.
|
|
///
|
|
/// The parser may also accept non-standard nesting of bare tables for some
|
|
/// dialects, but the information about such nesting is stripped from AST.
|
|
NestedJoin {
|
|
table_with_joins: Box<TableWithJoins>,
|
|
alias: Option<TableAlias>,
|
|
},
|
|
/// Represents PIVOT operation on a table.
|
|
/// For example `FROM monthly_sales PIVOT(sum(amount) FOR MONTH IN ('JAN', 'FEB'))`
|
|
/// See <https://docs.snowflake.com/en/sql-reference/constructs/pivot>
|
|
Pivot {
|
|
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
|
|
name: ObjectName,
|
|
table_alias: Option<TableAlias>,
|
|
aggregate_function: Expr, // Function expression
|
|
value_column: Vec<Ident>,
|
|
pivot_values: Vec<Value>,
|
|
pivot_alias: Option<TableAlias>,
|
|
},
|
|
}
|
|
|
|
impl fmt::Display for TableFactor {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
TableFactor::Table {
|
|
name,
|
|
alias,
|
|
args,
|
|
with_hints,
|
|
} => {
|
|
write!(f, "{name}")?;
|
|
if let Some(args) = args {
|
|
write!(f, "({})", display_comma_separated(args))?;
|
|
}
|
|
if let Some(alias) = alias {
|
|
write!(f, " AS {alias}")?;
|
|
}
|
|
if !with_hints.is_empty() {
|
|
write!(f, " WITH ({})", display_comma_separated(with_hints))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
TableFactor::Derived {
|
|
lateral,
|
|
subquery,
|
|
alias,
|
|
} => {
|
|
if *lateral {
|
|
write!(f, "LATERAL ")?;
|
|
}
|
|
write!(f, "({subquery})")?;
|
|
if let Some(alias) = alias {
|
|
write!(f, " AS {alias}")?;
|
|
}
|
|
Ok(())
|
|
}
|
|
TableFactor::TableFunction { expr, alias } => {
|
|
write!(f, "TABLE({expr})")?;
|
|
if let Some(alias) = alias {
|
|
write!(f, " AS {alias}")?;
|
|
}
|
|
Ok(())
|
|
}
|
|
TableFactor::UNNEST {
|
|
alias,
|
|
array_exprs,
|
|
with_offset,
|
|
with_offset_alias,
|
|
} => {
|
|
write!(f, "UNNEST({})", display_comma_separated(array_exprs))?;
|
|
|
|
if let Some(alias) = alias {
|
|
write!(f, " AS {alias}")?;
|
|
}
|
|
if *with_offset {
|
|
write!(f, " WITH OFFSET")?;
|
|
}
|
|
if let Some(alias) = with_offset_alias {
|
|
write!(f, " AS {alias}")?;
|
|
}
|
|
Ok(())
|
|
}
|
|
TableFactor::NestedJoin {
|
|
table_with_joins,
|
|
alias,
|
|
} => {
|
|
write!(f, "({table_with_joins})")?;
|
|
if let Some(alias) = alias {
|
|
write!(f, " AS {alias}")?;
|
|
}
|
|
Ok(())
|
|
}
|
|
TableFactor::Pivot {
|
|
name,
|
|
table_alias,
|
|
aggregate_function,
|
|
value_column,
|
|
pivot_values,
|
|
pivot_alias,
|
|
} => {
|
|
write!(f, "{}", name)?;
|
|
if table_alias.is_some() {
|
|
write!(f, " AS {}", table_alias.as_ref().unwrap())?;
|
|
}
|
|
write!(
|
|
f,
|
|
" PIVOT({} FOR {} IN (",
|
|
aggregate_function,
|
|
Expr::CompoundIdentifier(value_column.to_vec())
|
|
)?;
|
|
for value in pivot_values {
|
|
write!(f, "{}", value)?;
|
|
if !value.eq(pivot_values.last().unwrap()) {
|
|
write!(f, ", ")?;
|
|
}
|
|
}
|
|
write!(f, "))")?;
|
|
if pivot_alias.is_some() {
|
|
write!(f, " AS {}", pivot_alias.as_ref().unwrap())?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct TableAlias {
|
|
pub name: Ident,
|
|
pub columns: Vec<Ident>,
|
|
}
|
|
|
|
impl fmt::Display for TableAlias {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "{}", self.name)?;
|
|
if !self.columns.is_empty() {
|
|
write!(f, " ({})", display_comma_separated(&self.columns))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct Join {
|
|
pub relation: TableFactor,
|
|
pub join_operator: JoinOperator,
|
|
}
|
|
|
|
impl fmt::Display for Join {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
fn prefix(constraint: &JoinConstraint) -> &'static str {
|
|
match constraint {
|
|
JoinConstraint::Natural => "NATURAL ",
|
|
_ => "",
|
|
}
|
|
}
|
|
fn suffix(constraint: &'_ JoinConstraint) -> impl fmt::Display + '_ {
|
|
struct Suffix<'a>(&'a JoinConstraint);
|
|
impl<'a> fmt::Display for Suffix<'a> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self.0 {
|
|
JoinConstraint::On(expr) => write!(f, " ON {expr}"),
|
|
JoinConstraint::Using(attrs) => {
|
|
write!(f, " USING({})", display_comma_separated(attrs))
|
|
}
|
|
_ => Ok(()),
|
|
}
|
|
}
|
|
}
|
|
Suffix(constraint)
|
|
}
|
|
match &self.join_operator {
|
|
JoinOperator::Inner(constraint) => write!(
|
|
f,
|
|
" {}JOIN {}{}",
|
|
prefix(constraint),
|
|
self.relation,
|
|
suffix(constraint)
|
|
),
|
|
JoinOperator::LeftOuter(constraint) => write!(
|
|
f,
|
|
" {}LEFT JOIN {}{}",
|
|
prefix(constraint),
|
|
self.relation,
|
|
suffix(constraint)
|
|
),
|
|
JoinOperator::RightOuter(constraint) => write!(
|
|
f,
|
|
" {}RIGHT JOIN {}{}",
|
|
prefix(constraint),
|
|
self.relation,
|
|
suffix(constraint)
|
|
),
|
|
JoinOperator::FullOuter(constraint) => write!(
|
|
f,
|
|
" {}FULL JOIN {}{}",
|
|
prefix(constraint),
|
|
self.relation,
|
|
suffix(constraint)
|
|
),
|
|
JoinOperator::CrossJoin => write!(f, " CROSS JOIN {}", self.relation),
|
|
JoinOperator::LeftSemi(constraint) => write!(
|
|
f,
|
|
" {}LEFT SEMI JOIN {}{}",
|
|
prefix(constraint),
|
|
self.relation,
|
|
suffix(constraint)
|
|
),
|
|
JoinOperator::RightSemi(constraint) => write!(
|
|
f,
|
|
" {}RIGHT SEMI JOIN {}{}",
|
|
prefix(constraint),
|
|
self.relation,
|
|
suffix(constraint)
|
|
),
|
|
JoinOperator::LeftAnti(constraint) => write!(
|
|
f,
|
|
" {}LEFT ANTI JOIN {}{}",
|
|
prefix(constraint),
|
|
self.relation,
|
|
suffix(constraint)
|
|
),
|
|
JoinOperator::RightAnti(constraint) => write!(
|
|
f,
|
|
" {}RIGHT ANTI JOIN {}{}",
|
|
prefix(constraint),
|
|
self.relation,
|
|
suffix(constraint)
|
|
),
|
|
JoinOperator::CrossApply => write!(f, " CROSS APPLY {}", self.relation),
|
|
JoinOperator::OuterApply => write!(f, " OUTER APPLY {}", self.relation),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub enum JoinOperator {
|
|
Inner(JoinConstraint),
|
|
LeftOuter(JoinConstraint),
|
|
RightOuter(JoinConstraint),
|
|
FullOuter(JoinConstraint),
|
|
CrossJoin,
|
|
/// LEFT SEMI (non-standard)
|
|
LeftSemi(JoinConstraint),
|
|
/// RIGHT SEMI (non-standard)
|
|
RightSemi(JoinConstraint),
|
|
/// LEFT ANTI (non-standard)
|
|
LeftAnti(JoinConstraint),
|
|
/// RIGHT ANTI (non-standard)
|
|
RightAnti(JoinConstraint),
|
|
/// CROSS APPLY (non-standard)
|
|
CrossApply,
|
|
/// OUTER APPLY (non-standard)
|
|
OuterApply,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub enum JoinConstraint {
|
|
On(Expr),
|
|
Using(Vec<Ident>),
|
|
Natural,
|
|
None,
|
|
}
|
|
|
|
/// An `ORDER BY` expression
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct OrderByExpr {
|
|
pub expr: Expr,
|
|
/// Optional `ASC` or `DESC`
|
|
pub asc: Option<bool>,
|
|
/// Optional `NULLS FIRST` or `NULLS LAST`
|
|
pub nulls_first: Option<bool>,
|
|
}
|
|
|
|
impl fmt::Display for OrderByExpr {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "{}", self.expr)?;
|
|
match self.asc {
|
|
Some(true) => write!(f, " ASC")?,
|
|
Some(false) => write!(f, " DESC")?,
|
|
None => (),
|
|
}
|
|
match self.nulls_first {
|
|
Some(true) => write!(f, " NULLS FIRST")?,
|
|
Some(false) => write!(f, " NULLS LAST")?,
|
|
None => (),
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct Offset {
|
|
pub value: Expr,
|
|
pub rows: OffsetRows,
|
|
}
|
|
|
|
impl fmt::Display for Offset {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "OFFSET {}{}", self.value, self.rows)
|
|
}
|
|
}
|
|
|
|
/// Stores the keyword after `OFFSET <number>`
|
|
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub enum OffsetRows {
|
|
/// Omitting ROW/ROWS is non-standard MySQL quirk.
|
|
None,
|
|
Row,
|
|
Rows,
|
|
}
|
|
|
|
impl fmt::Display for OffsetRows {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
OffsetRows::None => Ok(()),
|
|
OffsetRows::Row => write!(f, " ROW"),
|
|
OffsetRows::Rows => write!(f, " ROWS"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct Fetch {
|
|
pub with_ties: bool,
|
|
pub percent: bool,
|
|
pub quantity: Option<Expr>,
|
|
}
|
|
|
|
impl fmt::Display for Fetch {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let extension = if self.with_ties { "WITH TIES" } else { "ONLY" };
|
|
if let Some(ref quantity) = self.quantity {
|
|
let percent = if self.percent { " PERCENT" } else { "" };
|
|
write!(f, "FETCH FIRST {quantity}{percent} ROWS {extension}")
|
|
} else {
|
|
write!(f, "FETCH FIRST ROWS {extension}")
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct LockClause {
|
|
pub lock_type: LockType,
|
|
pub of: Option<ObjectName>,
|
|
pub nonblock: Option<NonBlock>,
|
|
}
|
|
|
|
impl fmt::Display for LockClause {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "FOR {}", &self.lock_type)?;
|
|
if let Some(ref of) = self.of {
|
|
write!(f, " OF {of}")?;
|
|
}
|
|
if let Some(ref nb) = self.nonblock {
|
|
write!(f, " {nb}")?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub enum LockType {
|
|
Share,
|
|
Update,
|
|
}
|
|
|
|
impl fmt::Display for LockType {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let select_lock = match self {
|
|
LockType::Share => "SHARE",
|
|
LockType::Update => "UPDATE",
|
|
};
|
|
write!(f, "{select_lock}")
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub enum NonBlock {
|
|
Nowait,
|
|
SkipLocked,
|
|
}
|
|
|
|
impl fmt::Display for NonBlock {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let nonblock = match self {
|
|
NonBlock::Nowait => "NOWAIT",
|
|
NonBlock::SkipLocked => "SKIP LOCKED",
|
|
};
|
|
write!(f, "{nonblock}")
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub enum Distinct {
|
|
/// DISTINCT
|
|
Distinct,
|
|
|
|
/// DISTINCT ON({column names})
|
|
On(Vec<Expr>),
|
|
}
|
|
|
|
impl fmt::Display for Distinct {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
Distinct::Distinct => write!(f, "DISTINCT"),
|
|
Distinct::On(col_names) => {
|
|
let col_names = display_comma_separated(col_names);
|
|
write!(f, "DISTINCT ON ({col_names})")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct Top {
|
|
/// SQL semantic equivalent of LIMIT but with same structure as FETCH.
|
|
pub with_ties: bool,
|
|
pub percent: bool,
|
|
pub quantity: Option<Expr>,
|
|
}
|
|
|
|
impl fmt::Display for Top {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let extension = if self.with_ties { " WITH TIES" } else { "" };
|
|
if let Some(ref quantity) = self.quantity {
|
|
let percent = if self.percent { " PERCENT" } else { "" };
|
|
write!(f, "TOP ({quantity}){percent}{extension}")
|
|
} else {
|
|
write!(f, "TOP{extension}")
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct Values {
|
|
/// Was there an explicit ROWs keyword (MySQL)?
|
|
/// <https://dev.mysql.com/doc/refman/8.0/en/values.html>
|
|
pub explicit_row: bool,
|
|
pub rows: Vec<Vec<Expr>>,
|
|
}
|
|
|
|
impl fmt::Display for Values {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "VALUES ")?;
|
|
let prefix = if self.explicit_row { "ROW" } else { "" };
|
|
let mut delim = "";
|
|
for row in &self.rows {
|
|
write!(f, "{delim}")?;
|
|
delim = ", ";
|
|
write!(f, "{prefix}({})", display_comma_separated(row))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
|
pub struct SelectInto {
|
|
pub temporary: bool,
|
|
pub unlogged: bool,
|
|
pub table: bool,
|
|
pub name: ObjectName,
|
|
}
|
|
|
|
impl fmt::Display for SelectInto {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let temporary = if self.temporary { " TEMPORARY" } else { "" };
|
|
let unlogged = if self.unlogged { " UNLOGGED" } else { "" };
|
|
let table = if self.table { " TABLE" } else { "" };
|
|
|
|
write!(f, "INTO{}{}{} {}", temporary, unlogged, table, self.name)
|
|
}
|
|
}
|