| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you 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}; |
| |
| use helpers::attached_token::AttachedToken; |
| #[cfg(feature = "serde")] |
| use serde::{Deserialize, Serialize}; |
| |
| #[cfg(feature = "visitor")] |
| use sqlparser_derive::{Visit, VisitMut}; |
| |
| use crate::{ |
| ast::*, |
| tokenizer::{Token, TokenWithSpan}, |
| }; |
| |
| /// 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))] |
| #[cfg_attr(feature = "visitor", visit(with = "visit_query"))] |
| 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: Option<OrderBy>, |
| /// `LIMIT { <N> | ALL }` |
| pub limit: Option<Expr>, |
| |
| /// `LIMIT { <N> } BY { <expr>,<expr>,... } }` |
| pub limit_by: Vec<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>, |
| /// `FOR XML { RAW | AUTO | EXPLICIT | PATH } [ , ELEMENTS ]` |
| /// `FOR JSON { AUTO | PATH } [ , INCLUDE_NULL_VALUES ]` |
| /// (MSSQL-specific) |
| pub for_clause: Option<ForClause>, |
| /// ClickHouse syntax: `SELECT * FROM t SETTINGS key1 = value1, key2 = value2` |
| /// |
| /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/select#settings-in-select-query) |
| pub settings: Option<Vec<Setting>>, |
| /// `SELECT * FROM t FORMAT JSONCompact` |
| /// |
| /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/select/format) |
| /// (ClickHouse-specific) |
| pub format_clause: Option<FormatClause>, |
| } |
| |
| 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 let Some(ref order_by) = self.order_by { |
| write!(f, " {order_by}")?; |
| } |
| if let Some(ref limit) = self.limit { |
| write!(f, " LIMIT {limit}")?; |
| } |
| if let Some(ref offset) = self.offset { |
| write!(f, " {offset}")?; |
| } |
| if !self.limit_by.is_empty() { |
| write!(f, " BY {}", display_separated(&self.limit_by, ", "))?; |
| } |
| if let Some(ref settings) = self.settings { |
| write!(f, " SETTINGS {}", display_comma_separated(settings))?; |
| } |
| if let Some(ref fetch) = self.fetch { |
| write!(f, " {fetch}")?; |
| } |
| if !self.locks.is_empty() { |
| write!(f, " {}", display_separated(&self.locks, " "))?; |
| } |
| if let Some(ref for_clause) = self.for_clause { |
| write!(f, " {}", for_clause)?; |
| } |
| if let Some(ref format) = self.format_clause { |
| write!(f, " {}", format)?; |
| } |
| Ok(()) |
| } |
| } |
| |
| /// Query syntax for ClickHouse ADD PROJECTION statement. |
| /// Its syntax is similar to SELECT statement, but it is used to add a new projection to a table. |
| /// Syntax is `SELECT <COLUMN LIST EXPR> [GROUP BY] [ORDER BY]` |
| /// |
| /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/projection#add-projection) |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub struct ProjectionSelect { |
| pub projection: Vec<SelectItem>, |
| pub order_by: Option<OrderBy>, |
| pub group_by: Option<GroupByExpr>, |
| } |
| |
| impl fmt::Display for ProjectionSelect { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| write!(f, "SELECT {}", display_comma_separated(&self.projection))?; |
| if let Some(ref group_by) = self.group_by { |
| write!(f, " {group_by}")?; |
| } |
| if let Some(ref order_by) = self.order_by { |
| write!(f, " {order_by}")?; |
| } |
| 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 SetExpr { |
| /// If this `SetExpr` is a `SELECT`, returns the [`Select`]. |
| pub fn as_select(&self) -> Option<&Select> { |
| if let Self::Select(select) = self { |
| Some(&**select) |
| } else { |
| None |
| } |
| } |
| } |
| |
| 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 |
| | SetQuantifier::DistinctByName => 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, |
| DistinctByName, |
| 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::DistinctByName => write!(f, "DISTINCT 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 { |
| /// Token for the `SELECT` keyword |
| pub select_token: AttachedToken, |
| /// `SELECT [DISTINCT] ...` |
| pub distinct: Option<Distinct>, |
| /// MSSQL syntax: `TOP (<N>) [ PERCENT ] [ WITH TIES ]` |
| pub top: Option<Top>, |
| /// Whether the top was located before `ALL`/`DISTINCT` |
| pub top_before_distinct: bool, |
| /// projection expressions |
| pub projection: Vec<SelectItem>, |
| /// INTO |
| pub into: Option<SelectInto>, |
| /// FROM |
| pub from: Vec<TableWithJoins>, |
| /// LATERAL VIEWs |
| pub lateral_views: Vec<LateralView>, |
| /// ClickHouse syntax: `PREWHERE a = 1 WHERE b = 2`, |
| /// and it can be used together with WHERE selection. |
| /// |
| /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/select/prewhere) |
| pub prewhere: Option<Expr>, |
| /// WHERE |
| pub selection: Option<Expr>, |
| /// GROUP BY |
| pub group_by: GroupByExpr, |
| /// 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>, |
| /// The positioning of QUALIFY and WINDOW clauses differ between dialects. |
| /// e.g. BigQuery requires that WINDOW comes after QUALIFY, while DUCKDB accepts |
| /// WINDOW before QUALIFY. |
| /// We accept either positioning and flag the accepted variant. |
| pub window_before_qualify: bool, |
| /// BigQuery syntax: `SELECT AS VALUE | SELECT AS STRUCT` |
| pub value_table_mode: Option<ValueTableMode>, |
| /// STARTING WITH .. CONNECT BY |
| pub connect_by: Option<ConnectBy>, |
| } |
| |
| impl fmt::Display for Select { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| write!(f, "SELECT")?; |
| |
| if let Some(value_table_mode) = self.value_table_mode { |
| write!(f, " {value_table_mode}")?; |
| } |
| |
| if let Some(ref top) = self.top { |
| if self.top_before_distinct { |
| write!(f, " {top}")?; |
| } |
| } |
| if let Some(ref distinct) = self.distinct { |
| write!(f, " {distinct}")?; |
| } |
| if let Some(ref top) = self.top { |
| if !self.top_before_distinct { |
| 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 prewhere) = self.prewhere { |
| write!(f, " PREWHERE {prewhere}")?; |
| } |
| if let Some(ref selection) = self.selection { |
| write!(f, " WHERE {selection}")?; |
| } |
| match &self.group_by { |
| GroupByExpr::All(_) => write!(f, " {}", self.group_by)?, |
| GroupByExpr::Expressions(exprs, _) => { |
| if !exprs.is_empty() { |
| write!(f, " {}", 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.window_before_qualify { |
| 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}")?; |
| } |
| } else { |
| if let Some(ref qualify) = self.qualify { |
| write!(f, " QUALIFY {qualify}")?; |
| } |
| if !self.named_window.is_empty() { |
| write!(f, " WINDOW {}", display_comma_separated(&self.named_window))?; |
| } |
| } |
| if let Some(ref connect_by) = self.connect_by { |
| write!(f, " {connect_by}")?; |
| } |
| 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(()) |
| } |
| } |
| |
| /// An expression used in a named window declaration. |
| /// |
| /// ```sql |
| /// WINDOW mywindow AS [named_window_expr] |
| /// ``` |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub enum NamedWindowExpr { |
| /// A direct reference to another named window definition. |
| /// [BigQuery] |
| /// |
| /// Example: |
| /// ```sql |
| /// WINDOW mywindow AS prev_window |
| /// ``` |
| /// |
| /// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/window-function-calls#ref_named_window |
| NamedWindow(Ident), |
| /// A window expression. |
| /// |
| /// Example: |
| /// ```sql |
| /// WINDOW mywindow AS (ORDER BY 1) |
| /// ``` |
| WindowSpec(WindowSpec), |
| } |
| |
| impl fmt::Display for NamedWindowExpr { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| match self { |
| NamedWindowExpr::NamedWindow(named_window) => { |
| write!(f, "{named_window}")?; |
| } |
| NamedWindowExpr::WindowSpec(window_spec) => { |
| write!(f, "({window_spec})")?; |
| } |
| }; |
| 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 NamedWindowExpr); |
| |
| 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 { |
| /// Token for the "WITH" keyword |
| pub with_token: AttachedToken, |
| 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) |
| ) |
| } |
| } |
| |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub enum CteAsMaterialized { |
| /// The `WITH` statement specifies `AS MATERIALIZED` behavior |
| Materialized, |
| /// The `WITH` statement specifies `AS NOT MATERIALIZED` behavior |
| NotMaterialized, |
| } |
| |
| impl fmt::Display for CteAsMaterialized { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| match *self { |
| CteAsMaterialized::Materialized => { |
| write!(f, "MATERIALIZED")?; |
| } |
| CteAsMaterialized::NotMaterialized => { |
| write!(f, "NOT MATERIALIZED")?; |
| } |
| }; |
| Ok(()) |
| } |
| } |
| |
| /// A single CTE (used after `WITH`): `<alias> [(col1, col2, ...)] AS <materialized> ( <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>, |
| pub materialized: Option<CteAsMaterialized>, |
| /// Token for the closing parenthesis |
| pub closing_paren_token: AttachedToken, |
| } |
| |
| impl fmt::Display for Cte { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| match self.materialized.as_ref() { |
| None => write!(f, "{} AS ({})", self.alias, self.query)?, |
| Some(materialized) => write!(f, "{} AS {materialized} ({})", 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)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub struct WildcardAdditionalOptions { |
| /// The wildcard token `*` |
| pub wildcard_token: AttachedToken, |
| /// `[ILIKE...]`. |
| /// Snowflake syntax: <https://docs.snowflake.com/en/sql-reference/sql/select#parameters> |
| pub opt_ilike: Option<IlikeSelectItem>, |
| /// `[EXCLUDE...]`. |
| pub opt_exclude: Option<ExcludeSelectItem>, |
| /// `[EXCEPT...]`. |
| /// Clickhouse syntax: <https://clickhouse.com/docs/en/sql-reference/statements/select#except> |
| pub opt_except: Option<ExceptSelectItem>, |
| /// `[REPLACE]` |
| /// BigQuery syntax: <https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#select_replace> |
| /// Clickhouse syntax: <https://clickhouse.com/docs/en/sql-reference/statements/select#replace> |
| /// Snowflake syntax: <https://docs.snowflake.com/en/sql-reference/sql/select#parameters> |
| pub opt_replace: Option<ReplaceSelectItem>, |
| /// `[RENAME ...]`. |
| pub opt_rename: Option<RenameSelectItem>, |
| } |
| |
| impl Default for WildcardAdditionalOptions { |
| fn default() -> Self { |
| Self { |
| wildcard_token: TokenWithSpan::wrap(Token::Mul).into(), |
| opt_ilike: None, |
| opt_exclude: None, |
| opt_except: None, |
| opt_replace: None, |
| opt_rename: None, |
| } |
| } |
| } |
| |
| impl fmt::Display for WildcardAdditionalOptions { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| if let Some(ilike) = &self.opt_ilike { |
| write!(f, " {ilike}")?; |
| } |
| if let Some(exclude) = &self.opt_exclude { |
| write!(f, " {exclude}")?; |
| } |
| if let Some(except) = &self.opt_except { |
| write!(f, " {except}")?; |
| } |
| if let Some(replace) = &self.opt_replace { |
| write!(f, " {replace}")?; |
| } |
| if let Some(rename) = &self.opt_rename { |
| write!(f, " {rename}")?; |
| } |
| Ok(()) |
| } |
| } |
| |
| /// Snowflake `ILIKE` information. |
| /// |
| /// # Syntax |
| /// ```plaintext |
| /// ILIKE <value> |
| /// ``` |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub struct IlikeSelectItem { |
| pub pattern: String, |
| } |
| |
| impl fmt::Display for IlikeSelectItem { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| write!( |
| f, |
| "ILIKE '{}'", |
| value::escape_single_quote_string(&self.pattern) |
| )?; |
| 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(()) |
| } |
| } |
| |
| /// Joins a table to itself to process hierarchical data in the table. |
| /// |
| /// See <https://docs.snowflake.com/en/sql-reference/constructs/connect-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 ConnectBy { |
| /// START WITH |
| pub condition: Expr, |
| /// CONNECT BY |
| pub relationships: Vec<Expr>, |
| } |
| |
| impl fmt::Display for ConnectBy { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| write!( |
| f, |
| "START WITH {condition} CONNECT BY {relationships}", |
| condition = self.condition, |
| relationships = display_comma_separated(&self.relationships) |
| ) |
| } |
| } |
| |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub struct Setting { |
| pub key: Ident, |
| pub value: Value, |
| } |
| |
| impl fmt::Display for Setting { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| write!(f, "{} = {}", self.key, self.value) |
| } |
| } |
| |
| /// An expression optionally followed by an alias. |
| /// |
| /// Example: |
| /// ```sql |
| /// 42 AS myint |
| /// ``` |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub struct ExprWithAlias { |
| pub expr: Expr, |
| pub alias: Option<Ident>, |
| } |
| |
| impl fmt::Display for ExprWithAlias { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| let ExprWithAlias { expr, alias } = self; |
| write!(f, "{expr}")?; |
| if let Some(alias) = alias { |
| write!(f, " AS {alias}")?; |
| } |
| Ok(()) |
| } |
| } |
| |
| /// Arguments to a table-valued function |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub struct TableFunctionArgs { |
| pub args: Vec<FunctionArg>, |
| /// ClickHouse-specific SETTINGS clause. |
| /// For example, |
| /// `SELECT * FROM executable('generate_random.py', TabSeparated, 'id UInt32, random String', SETTINGS send_chunk_header = false, pool_size = 16)` |
| /// [`executable` table function](https://clickhouse.com/docs/en/engines/table-functions/executable) |
| pub settings: Option<Vec<Setting>>, |
| } |
| |
| /// 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))] |
| #[cfg_attr(feature = "visitor", visit(with = "visit_table_factor"))] |
| 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<TableFunctionArgs>, |
| /// MSSQL-specific `WITH (...)` hints such as NOLOCK. |
| with_hints: Vec<Expr>, |
| /// Optional version qualifier to facilitate table time-travel, as |
| /// supported by BigQuery and MSSQL. |
| version: Option<TableVersion>, |
| // Optional table function modifier to generate the ordinality for column. |
| /// For example, `SELECT * FROM generate_series(1, 10) WITH ORDINALITY AS t(a, b);` |
| /// [WITH ORDINALITY](https://www.postgresql.org/docs/current/functions-srf.html), supported by Postgres. |
| with_ordinality: bool, |
| /// [Partition selection](https://dev.mysql.com/doc/refman/8.0/en/partitioning-selection.html), supported by MySQL. |
| partitions: Vec<Ident>, |
| /// Optional PartiQL JsonPath: <https://partiql.org/dql/from.html> |
| json_path: Option<JsonPath>, |
| }, |
| Derived { |
| lateral: bool, |
| subquery: Box<Query>, |
| alias: Option<TableAlias>, |
| }, |
| /// `TABLE(<expr>)[ AS <alias> ]` |
| TableFunction { |
| expr: Expr, |
| alias: Option<TableAlias>, |
| }, |
| /// `e.g. LATERAL FLATTEN(<args>)[ AS <alias> ]` |
| Function { |
| lateral: bool, |
| name: ObjectName, |
| args: Vec<FunctionArg>, |
| 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>, |
| with_ordinality: bool, |
| }, |
| /// The `JSON_TABLE` table-valued function. |
| /// Part of the SQL standard, but implemented only by MySQL, Oracle, and DB2. |
| /// |
| /// <https://modern-sql.com/blog/2017-06/whats-new-in-sql-2016#json_table> |
| /// <https://dev.mysql.com/doc/refman/8.0/en/json-table-functions.html#function_json-table> |
| /// |
| /// ```sql |
| /// SELECT * FROM JSON_TABLE( |
| /// '[{"a": 1, "b": 2}, {"a": 3, "b": 4}]', |
| /// '$[*]' COLUMNS( |
| /// a INT PATH '$.a' DEFAULT '0' ON EMPTY, |
| /// b INT PATH '$.b' NULL ON ERROR |
| /// ) |
| /// ) AS jt; |
| /// ```` |
| JsonTable { |
| /// The JSON expression to be evaluated. It must evaluate to a json string |
| json_expr: Expr, |
| /// The path to the array or object to be iterated over. |
| /// It must evaluate to a json array or object. |
| json_path: Value, |
| /// The columns to be extracted from each element of the array or object. |
| /// Each column must have a name and a type. |
| columns: Vec<JsonTableColumn>, |
| /// The alias for the table. |
| alias: Option<TableAlias>, |
| }, |
| /// The MSSQL's `OPENJSON` table-valued function. |
| /// |
| /// ```sql |
| /// OPENJSON( jsonExpression [ , path ] ) [ <with_clause> ] |
| /// |
| /// <with_clause> ::= WITH ( { colName type [ column_path ] [ AS JSON ] } [ ,...n ] ) |
| /// ```` |
| /// |
| /// Reference: <https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16#syntax> |
| OpenJsonTable { |
| /// The JSON expression to be evaluated. It must evaluate to a json string |
| json_expr: Expr, |
| /// The path to the array or object to be iterated over. |
| /// It must evaluate to a json array or object. |
| json_path: Option<Value>, |
| /// The columns to be extracted from each element of the array or object. |
| /// Each column must have a name and a type. |
| columns: Vec<OpenJsonTableColumn>, |
| /// The alias for the table. |
| alias: Option<TableAlias>, |
| }, |
| /// 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'))` |
| /// |
| /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#pivot_operator) |
| /// [Snowflake](https://docs.snowflake.com/en/sql-reference/constructs/pivot) |
| Pivot { |
| table: Box<TableFactor>, |
| aggregate_functions: Vec<ExprWithAlias>, // Function expression |
| value_column: Vec<Ident>, |
| value_source: PivotValueSource, |
| default_on_null: Option<Expr>, |
| alias: Option<TableAlias>, |
| }, |
| /// An UNPIVOT operation on a table. |
| /// |
| /// Syntax: |
| /// ```sql |
| /// table UNPIVOT(value FOR name IN (column1, [ column2, ... ])) [ alias ] |
| /// ``` |
| /// |
| /// See <https://docs.snowflake.com/en/sql-reference/constructs/unpivot>. |
| Unpivot { |
| table: Box<TableFactor>, |
| value: Ident, |
| name: Ident, |
| columns: Vec<Ident>, |
| alias: Option<TableAlias>, |
| }, |
| /// A `MATCH_RECOGNIZE` operation on a table. |
| /// |
| /// See <https://docs.snowflake.com/en/sql-reference/constructs/match_recognize>. |
| MatchRecognize { |
| table: Box<TableFactor>, |
| /// `PARTITION BY <expr> [, ... ]` |
| partition_by: Vec<Expr>, |
| /// `ORDER BY <expr> [, ... ]` |
| order_by: Vec<OrderByExpr>, |
| /// `MEASURES <expr> [AS] <alias> [, ... ]` |
| measures: Vec<Measure>, |
| /// `ONE ROW PER MATCH | ALL ROWS PER MATCH [ <option> ]` |
| rows_per_match: Option<RowsPerMatch>, |
| /// `AFTER MATCH SKIP <option>` |
| after_match_skip: Option<AfterMatchSkip>, |
| /// `PATTERN ( <pattern> )` |
| pattern: MatchRecognizePattern, |
| /// `DEFINE <symbol> AS <expr> [, ... ]` |
| symbols: Vec<SymbolDefinition>, |
| alias: Option<TableAlias>, |
| }, |
| } |
| |
| /// The source of values in a `PIVOT` operation. |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub enum PivotValueSource { |
| /// Pivot on a static list of values. |
| /// |
| /// See <https://docs.snowflake.com/en/sql-reference/constructs/pivot#pivot-on-a-specified-list-of-column-values-for-the-pivot-column>. |
| List(Vec<ExprWithAlias>), |
| /// Pivot on all distinct values of the pivot column. |
| /// |
| /// See <https://docs.snowflake.com/en/sql-reference/constructs/pivot#pivot-on-all-distinct-column-values-automatically-with-dynamic-pivot>. |
| Any(Vec<OrderByExpr>), |
| /// Pivot on all values returned by a subquery. |
| /// |
| /// See <https://docs.snowflake.com/en/sql-reference/constructs/pivot#pivot-on-column-values-using-a-subquery-with-dynamic-pivot>. |
| Subquery(Box<Query>), |
| } |
| |
| impl fmt::Display for PivotValueSource { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| match self { |
| PivotValueSource::List(values) => write!(f, "{}", display_comma_separated(values)), |
| PivotValueSource::Any(order_by) => { |
| write!(f, "ANY")?; |
| if !order_by.is_empty() { |
| write!(f, " ORDER BY {}", display_comma_separated(order_by))?; |
| } |
| Ok(()) |
| } |
| PivotValueSource::Subquery(query) => write!(f, "{query}"), |
| } |
| } |
| } |
| |
| /// An item in the `MEASURES` subclause of a `MATCH_RECOGNIZE` operation. |
| /// |
| /// See <https://docs.snowflake.com/en/sql-reference/constructs/match_recognize#measures-specifying-additional-output-columns>. |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub struct Measure { |
| pub expr: Expr, |
| pub alias: Ident, |
| } |
| |
| impl fmt::Display for Measure { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| write!(f, "{} AS {}", self.expr, self.alias) |
| } |
| } |
| |
| /// The rows per match option in a `MATCH_RECOGNIZE` operation. |
| /// |
| /// See <https://docs.snowflake.com/en/sql-reference/constructs/match_recognize#row-s-per-match-specifying-the-rows-to-return>. |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub enum RowsPerMatch { |
| /// `ONE ROW PER MATCH` |
| OneRow, |
| /// `ALL ROWS PER MATCH <mode>` |
| AllRows(Option<EmptyMatchesMode>), |
| } |
| |
| impl fmt::Display for RowsPerMatch { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| match self { |
| RowsPerMatch::OneRow => write!(f, "ONE ROW PER MATCH"), |
| RowsPerMatch::AllRows(mode) => { |
| write!(f, "ALL ROWS PER MATCH")?; |
| if let Some(mode) = mode { |
| write!(f, " {}", mode)?; |
| } |
| Ok(()) |
| } |
| } |
| } |
| } |
| |
| /// The after match skip option in a `MATCH_RECOGNIZE` operation. |
| /// |
| /// See <https://docs.snowflake.com/en/sql-reference/constructs/match_recognize#after-match-skip-specifying-where-to-continue-after-a-match>. |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub enum AfterMatchSkip { |
| /// `PAST LAST ROW` |
| PastLastRow, |
| /// `TO NEXT ROW` |
| ToNextRow, |
| /// `TO FIRST <symbol>` |
| ToFirst(Ident), |
| /// `TO LAST <symbol>` |
| ToLast(Ident), |
| } |
| |
| impl fmt::Display for AfterMatchSkip { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| write!(f, "AFTER MATCH SKIP ")?; |
| match self { |
| AfterMatchSkip::PastLastRow => write!(f, "PAST LAST ROW"), |
| AfterMatchSkip::ToNextRow => write!(f, " TO NEXT ROW"), |
| AfterMatchSkip::ToFirst(symbol) => write!(f, "TO FIRST {symbol}"), |
| AfterMatchSkip::ToLast(symbol) => write!(f, "TO LAST {symbol}"), |
| } |
| } |
| } |
| |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub enum EmptyMatchesMode { |
| /// `SHOW EMPTY MATCHES` |
| Show, |
| /// `OMIT EMPTY MATCHES` |
| Omit, |
| /// `WITH UNMATCHED ROWS` |
| WithUnmatched, |
| } |
| |
| impl fmt::Display for EmptyMatchesMode { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| match self { |
| EmptyMatchesMode::Show => write!(f, "SHOW EMPTY MATCHES"), |
| EmptyMatchesMode::Omit => write!(f, "OMIT EMPTY MATCHES"), |
| EmptyMatchesMode::WithUnmatched => write!(f, "WITH UNMATCHED ROWS"), |
| } |
| } |
| } |
| |
| /// A symbol defined in a `MATCH_RECOGNIZE` operation. |
| /// |
| /// See <https://docs.snowflake.com/en/sql-reference/constructs/match_recognize#define-defining-symbols>. |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub struct SymbolDefinition { |
| pub symbol: Ident, |
| pub definition: Expr, |
| } |
| |
| impl fmt::Display for SymbolDefinition { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| write!(f, "{} AS {}", self.symbol, self.definition) |
| } |
| } |
| |
| /// A symbol in a `MATCH_RECOGNIZE` pattern. |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub enum MatchRecognizeSymbol { |
| /// A named symbol, e.g. `S1`. |
| Named(Ident), |
| /// A virtual symbol representing the start of the of partition (`^`). |
| Start, |
| /// A virtual symbol representing the end of the partition (`$`). |
| End, |
| } |
| |
| impl fmt::Display for MatchRecognizeSymbol { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| match self { |
| MatchRecognizeSymbol::Named(symbol) => write!(f, "{symbol}"), |
| MatchRecognizeSymbol::Start => write!(f, "^"), |
| MatchRecognizeSymbol::End => write!(f, "$"), |
| } |
| } |
| } |
| |
| /// The pattern in a `MATCH_RECOGNIZE` operation. |
| /// |
| /// See <https://docs.snowflake.com/en/sql-reference/constructs/match_recognize#pattern-specifying-the-pattern-to-match>. |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub enum MatchRecognizePattern { |
| /// A named symbol such as `S1` or a virtual symbol such as `^`. |
| Symbol(MatchRecognizeSymbol), |
| /// {- symbol -} |
| Exclude(MatchRecognizeSymbol), |
| /// PERMUTE(symbol_1, ..., symbol_n) |
| Permute(Vec<MatchRecognizeSymbol>), |
| /// pattern_1 pattern_2 ... pattern_n |
| Concat(Vec<MatchRecognizePattern>), |
| /// ( pattern ) |
| Group(Box<MatchRecognizePattern>), |
| /// pattern_1 | pattern_2 | ... | pattern_n |
| Alternation(Vec<MatchRecognizePattern>), |
| /// e.g. pattern* |
| Repetition(Box<MatchRecognizePattern>, RepetitionQuantifier), |
| } |
| |
| impl fmt::Display for MatchRecognizePattern { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| use MatchRecognizePattern::*; |
| match self { |
| Symbol(symbol) => write!(f, "{}", symbol), |
| Exclude(symbol) => write!(f, "{{- {symbol} -}}"), |
| Permute(symbols) => write!(f, "PERMUTE({})", display_comma_separated(symbols)), |
| Concat(patterns) => write!(f, "{}", display_separated(patterns, " ")), |
| Group(pattern) => write!(f, "( {pattern} )"), |
| Alternation(patterns) => write!(f, "{}", display_separated(patterns, " | ")), |
| Repetition(pattern, op) => write!(f, "{pattern}{op}"), |
| } |
| } |
| } |
| |
| /// Determines the minimum and maximum allowed occurrences of a pattern in a |
| /// `MATCH_RECOGNIZE` operation. |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub enum RepetitionQuantifier { |
| /// `*` |
| ZeroOrMore, |
| /// `+` |
| OneOrMore, |
| /// `?` |
| AtMostOne, |
| /// `{n}` |
| Exactly(u32), |
| /// `{n,}` |
| AtLeast(u32), |
| /// `{,n}` |
| AtMost(u32), |
| /// `{n,m} |
| Range(u32, u32), |
| } |
| |
| impl fmt::Display for RepetitionQuantifier { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| use RepetitionQuantifier::*; |
| match self { |
| ZeroOrMore => write!(f, "*"), |
| OneOrMore => write!(f, "+"), |
| AtMostOne => write!(f, "?"), |
| Exactly(n) => write!(f, "{{{n}}}"), |
| AtLeast(n) => write!(f, "{{{n},}}"), |
| AtMost(n) => write!(f, "{{,{n}}}"), |
| Range(n, m) => write!(f, "{{{n},{m}}}"), |
| } |
| } |
| } |
| |
| impl fmt::Display for TableFactor { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| match self { |
| TableFactor::Table { |
| name, |
| alias, |
| args, |
| with_hints, |
| version, |
| partitions, |
| with_ordinality, |
| json_path, |
| } => { |
| write!(f, "{name}")?; |
| if let Some(json_path) = json_path { |
| write!(f, "{json_path}")?; |
| } |
| if !partitions.is_empty() { |
| write!(f, "PARTITION ({})", display_comma_separated(partitions))?; |
| } |
| if let Some(args) = args { |
| write!(f, "(")?; |
| write!(f, "{}", display_comma_separated(&args.args))?; |
| if let Some(ref settings) = args.settings { |
| if !args.args.is_empty() { |
| write!(f, ", ")?; |
| } |
| write!(f, "SETTINGS {}", display_comma_separated(settings))?; |
| } |
| write!(f, ")")?; |
| } |
| if *with_ordinality { |
| write!(f, " WITH ORDINALITY")?; |
| } |
| if let Some(alias) = alias { |
| write!(f, " AS {alias}")?; |
| } |
| if !with_hints.is_empty() { |
| write!(f, " WITH ({})", display_comma_separated(with_hints))?; |
| } |
| if let Some(version) = version { |
| write!(f, "{version}")?; |
| } |
| 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::Function { |
| lateral, |
| name, |
| args, |
| alias, |
| } => { |
| if *lateral { |
| write!(f, "LATERAL ")?; |
| } |
| write!(f, "{name}")?; |
| write!(f, "({})", display_comma_separated(args))?; |
| 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, |
| with_ordinality, |
| } => { |
| write!(f, "UNNEST({})", display_comma_separated(array_exprs))?; |
| |
| if *with_ordinality { |
| write!(f, " WITH ORDINALITY")?; |
| } |
| |
| 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::JsonTable { |
| json_expr, |
| json_path, |
| columns, |
| alias, |
| } => { |
| write!( |
| f, |
| "JSON_TABLE({json_expr}, {json_path} COLUMNS({columns}))", |
| columns = display_comma_separated(columns) |
| )?; |
| if let Some(alias) = alias { |
| write!(f, " AS {alias}")?; |
| } |
| Ok(()) |
| } |
| TableFactor::OpenJsonTable { |
| json_expr, |
| json_path, |
| columns, |
| alias, |
| } => { |
| write!(f, "OPENJSON({json_expr}")?; |
| if let Some(json_path) = json_path { |
| write!(f, ", {json_path}")?; |
| } |
| write!(f, ")")?; |
| if !columns.is_empty() { |
| write!(f, " WITH ({})", display_comma_separated(columns))?; |
| } |
| if let Some(alias) = 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 { |
| table, |
| aggregate_functions, |
| value_column, |
| value_source, |
| default_on_null, |
| alias, |
| } => { |
| write!( |
| f, |
| "{table} PIVOT({} FOR {} IN ({value_source})", |
| display_comma_separated(aggregate_functions), |
| Expr::CompoundIdentifier(value_column.to_vec()), |
| )?; |
| if let Some(expr) = default_on_null { |
| write!(f, " DEFAULT ON NULL ({expr})")?; |
| } |
| write!(f, ")")?; |
| if alias.is_some() { |
| write!(f, " AS {}", alias.as_ref().unwrap())?; |
| } |
| Ok(()) |
| } |
| TableFactor::Unpivot { |
| table, |
| value, |
| name, |
| columns, |
| alias, |
| } => { |
| write!( |
| f, |
| "{} UNPIVOT({} FOR {} IN ({}))", |
| table, |
| value, |
| name, |
| display_comma_separated(columns) |
| )?; |
| if alias.is_some() { |
| write!(f, " AS {}", alias.as_ref().unwrap())?; |
| } |
| Ok(()) |
| } |
| TableFactor::MatchRecognize { |
| table, |
| partition_by, |
| order_by, |
| measures, |
| rows_per_match, |
| after_match_skip, |
| pattern, |
| symbols, |
| alias, |
| } => { |
| write!(f, "{table} MATCH_RECOGNIZE(")?; |
| if !partition_by.is_empty() { |
| write!(f, "PARTITION BY {} ", display_comma_separated(partition_by))?; |
| } |
| if !order_by.is_empty() { |
| write!(f, "ORDER BY {} ", display_comma_separated(order_by))?; |
| } |
| if !measures.is_empty() { |
| write!(f, "MEASURES {} ", display_comma_separated(measures))?; |
| } |
| if let Some(rows_per_match) = rows_per_match { |
| write!(f, "{rows_per_match} ")?; |
| } |
| if let Some(after_match_skip) = after_match_skip { |
| write!(f, "{after_match_skip} ")?; |
| } |
| write!(f, "PATTERN ({pattern}) ")?; |
| write!(f, "DEFINE {})", display_comma_separated(symbols))?; |
| if alias.is_some() { |
| write!(f, " AS {}", 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<TableAliasColumnDef>, |
| } |
| |
| 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(()) |
| } |
| } |
| |
| /// SQL column definition in a table expression alias. |
| /// Most of the time, the data type is not specified. |
| /// But some table-valued functions do require specifying the data type. |
| /// |
| /// See <https://www.postgresql.org/docs/17/queries-table-expressions.html#QUERIES-TABLEFUNCTIONS> |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub struct TableAliasColumnDef { |
| /// Column name alias |
| pub name: Ident, |
| /// Some table-valued functions require specifying the data type in the alias. |
| pub data_type: Option<DataType>, |
| } |
| |
| impl TableAliasColumnDef { |
| /// Create a new table alias column definition with only a name and no type |
| pub fn from_name<S: Into<String>>(name: S) -> Self { |
| TableAliasColumnDef { |
| name: Ident::new(name), |
| data_type: None, |
| } |
| } |
| } |
| |
| impl fmt::Display for TableAliasColumnDef { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| write!(f, "{}", self.name)?; |
| if let Some(ref data_type) = self.data_type { |
| write!(f, " {}", data_type)?; |
| } |
| Ok(()) |
| } |
| } |
| |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub enum TableVersion { |
| ForSystemTimeAsOf(Expr), |
| } |
| |
| impl Display for TableVersion { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| match self { |
| TableVersion::ForSystemTimeAsOf(e) => write!(f, " FOR SYSTEM_TIME AS OF {e}")?, |
| } |
| 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, |
| /// ClickHouse supports the optional `GLOBAL` keyword before the join operator. |
| /// See [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/select/join) |
| pub global: bool, |
| 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 fmt::Display for Suffix<'_> { |
| 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) |
| } |
| if self.global { |
| write!(f, " GLOBAL")?; |
| } |
| |
| 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::Semi(constraint) => write!( |
| f, |
| " {}SEMI JOIN {}{}", |
| prefix(constraint), |
| self.relation, |
| suffix(constraint) |
| ), |
| 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::Anti(constraint) => write!( |
| f, |
| " {}ANTI 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), |
| JoinOperator::AsOf { |
| match_condition, |
| constraint, |
| } => write!( |
| f, |
| " ASOF JOIN {} MATCH_CONDITION ({match_condition}){}", |
| self.relation, |
| suffix(constraint) |
| ), |
| } |
| } |
| } |
| |
| #[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, |
| /// SEMI (non-standard) |
| Semi(JoinConstraint), |
| /// LEFT SEMI (non-standard) |
| LeftSemi(JoinConstraint), |
| /// RIGHT SEMI (non-standard) |
| RightSemi(JoinConstraint), |
| /// ANTI (non-standard) |
| Anti(JoinConstraint), |
| /// LEFT ANTI (non-standard) |
| LeftAnti(JoinConstraint), |
| /// RIGHT ANTI (non-standard) |
| RightAnti(JoinConstraint), |
| /// CROSS APPLY (non-standard) |
| CrossApply, |
| /// OUTER APPLY (non-standard) |
| OuterApply, |
| /// `ASOF` joins are used for joining tables containing time-series data |
| /// whose timestamp columns do not match exactly. |
| /// |
| /// See <https://docs.snowflake.com/en/sql-reference/constructs/asof-join>. |
| AsOf { |
| match_condition: Expr, |
| constraint: JoinConstraint, |
| }, |
| } |
| |
| #[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, |
| } |
| |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub struct OrderBy { |
| pub exprs: Vec<OrderByExpr>, |
| /// Optional: `INTERPOLATE` |
| /// Supported by [ClickHouse syntax] |
| /// |
| /// [ClickHouse syntax]: <https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier> |
| pub interpolate: Option<Interpolate>, |
| } |
| |
| impl fmt::Display for OrderBy { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| write!(f, "ORDER BY")?; |
| if !self.exprs.is_empty() { |
| write!(f, " {}", display_comma_separated(&self.exprs))?; |
| } |
| if let Some(ref interpolate) = self.interpolate { |
| match &interpolate.exprs { |
| Some(exprs) => write!(f, " INTERPOLATE ({})", display_comma_separated(exprs))?, |
| None => write!(f, " INTERPOLATE")?, |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| /// 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>, |
| /// Optional: `WITH FILL` |
| /// Supported by [ClickHouse syntax]: <https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier> |
| pub with_fill: Option<WithFill>, |
| } |
| |
| 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 => (), |
| } |
| if let Some(ref with_fill) = self.with_fill { |
| write!(f, " {}", with_fill)? |
| } |
| Ok(()) |
| } |
| } |
| |
| /// ClickHouse `WITH FILL` modifier for `ORDER BY` clause. |
| /// Supported by [ClickHouse syntax] |
| /// |
| /// [ClickHouse syntax]: <https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier> |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub struct WithFill { |
| pub from: Option<Expr>, |
| pub to: Option<Expr>, |
| pub step: Option<Expr>, |
| } |
| |
| impl fmt::Display for WithFill { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| write!(f, "WITH FILL")?; |
| if let Some(ref from) = self.from { |
| write!(f, " FROM {}", from)?; |
| } |
| if let Some(ref to) = self.to { |
| write!(f, " TO {}", to)?; |
| } |
| if let Some(ref step) = self.step { |
| write!(f, " STEP {}", step)?; |
| } |
| Ok(()) |
| } |
| } |
| |
| /// ClickHouse `INTERPOLATE` clause for use in `ORDER BY` clause when using `WITH FILL` modifier. |
| /// Supported by [ClickHouse syntax] |
| /// |
| /// [ClickHouse syntax]: <https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier> |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub struct InterpolateExpr { |
| pub column: Ident, |
| pub expr: Option<Expr>, |
| } |
| |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub struct Interpolate { |
| pub exprs: Option<Vec<InterpolateExpr>>, |
| } |
| |
| impl fmt::Display for InterpolateExpr { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| write!(f, "{}", self.column)?; |
| if let Some(ref expr) = self.expr { |
| write!(f, " AS {}", expr)?; |
| } |
| 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. |
| /// MSSQL only. |
| pub with_ties: bool, |
| /// MSSQL only. |
| pub percent: bool, |
| pub quantity: Option<TopQuantity>, |
| } |
| |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub enum TopQuantity { |
| // A parenthesized expression. MSSQL only. |
| Expr(Expr), |
| // An unparenthesized integer constant. |
| Constant(u64), |
| } |
| |
| 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 { "" }; |
| match quantity { |
| TopQuantity::Expr(quantity) => write!(f, "TOP ({quantity}){percent}{extension}"), |
| TopQuantity::Constant(quantity) => { |
| 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) |
| } |
| } |
| |
| /// ClickHouse supports GROUP BY WITH modifiers(includes ROLLUP|CUBE|TOTALS). |
| /// e.g. GROUP BY year WITH ROLLUP WITH TOTALS |
| /// |
| /// [ClickHouse]: <https://clickhouse.com/docs/en/sql-reference/statements/select/group-by#rollup-modifier> |
| #[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 GroupByWithModifier { |
| Rollup, |
| Cube, |
| Totals, |
| } |
| |
| impl fmt::Display for GroupByWithModifier { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| match self { |
| GroupByWithModifier::Rollup => write!(f, "WITH ROLLUP"), |
| GroupByWithModifier::Cube => write!(f, "WITH CUBE"), |
| GroupByWithModifier::Totals => write!(f, "WITH TOTALS"), |
| } |
| } |
| } |
| |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub enum GroupByExpr { |
| /// ALL syntax of [Snowflake], [DuckDB] and [ClickHouse]. |
| /// |
| /// [Snowflake]: <https://docs.snowflake.com/en/sql-reference/constructs/group-by#label-group-by-all-columns> |
| /// [DuckDB]: <https://duckdb.org/docs/sql/query_syntax/groupby.html> |
| /// [ClickHouse]: <https://clickhouse.com/docs/en/sql-reference/statements/select/group-by#group-by-all> |
| /// |
| /// ClickHouse also supports WITH modifiers after GROUP BY ALL and expressions. |
| /// |
| /// [ClickHouse]: <https://clickhouse.com/docs/en/sql-reference/statements/select/group-by#rollup-modifier> |
| All(Vec<GroupByWithModifier>), |
| |
| /// Expressions |
| Expressions(Vec<Expr>, Vec<GroupByWithModifier>), |
| } |
| |
| impl fmt::Display for GroupByExpr { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| match self { |
| GroupByExpr::All(modifiers) => { |
| write!(f, "GROUP BY ALL")?; |
| if !modifiers.is_empty() { |
| write!(f, " {}", display_separated(modifiers, " "))?; |
| } |
| Ok(()) |
| } |
| GroupByExpr::Expressions(col_names, modifiers) => { |
| let col_names = display_comma_separated(col_names); |
| write!(f, "GROUP BY {col_names}")?; |
| if !modifiers.is_empty() { |
| write!(f, " {}", display_separated(modifiers, " "))?; |
| } |
| Ok(()) |
| } |
| } |
| } |
| } |
| |
| /// FORMAT identifier or FORMAT NULL clause, specific to ClickHouse. |
| /// |
| /// [ClickHouse]: <https://clickhouse.com/docs/en/sql-reference/statements/select/format> |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub enum FormatClause { |
| Identifier(Ident), |
| Null, |
| } |
| |
| impl fmt::Display for FormatClause { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| match self { |
| FormatClause::Identifier(ident) => write!(f, "FORMAT {}", ident), |
| FormatClause::Null => write!(f, "FORMAT NULL"), |
| } |
| } |
| } |
| |
| /// FOR XML or FOR JSON clause, specific to MSSQL |
| /// (formats the output of a query as XML or JSON) |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub enum ForClause { |
| Browse, |
| Json { |
| for_json: ForJson, |
| root: Option<String>, |
| include_null_values: bool, |
| without_array_wrapper: bool, |
| }, |
| Xml { |
| for_xml: ForXml, |
| elements: bool, |
| binary_base64: bool, |
| root: Option<String>, |
| r#type: bool, |
| }, |
| } |
| |
| impl fmt::Display for ForClause { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| match self { |
| ForClause::Browse => write!(f, "FOR BROWSE"), |
| ForClause::Json { |
| for_json, |
| root, |
| include_null_values, |
| without_array_wrapper, |
| } => { |
| write!(f, "FOR JSON ")?; |
| write!(f, "{}", for_json)?; |
| if let Some(root) = root { |
| write!(f, ", ROOT('{}')", root)?; |
| } |
| if *include_null_values { |
| write!(f, ", INCLUDE_NULL_VALUES")?; |
| } |
| if *without_array_wrapper { |
| write!(f, ", WITHOUT_ARRAY_WRAPPER")?; |
| } |
| Ok(()) |
| } |
| ForClause::Xml { |
| for_xml, |
| elements, |
| binary_base64, |
| root, |
| r#type, |
| } => { |
| write!(f, "FOR XML ")?; |
| write!(f, "{}", for_xml)?; |
| if *binary_base64 { |
| write!(f, ", BINARY BASE64")?; |
| } |
| if *r#type { |
| write!(f, ", TYPE")?; |
| } |
| if let Some(root) = root { |
| write!(f, ", ROOT('{}')", root)?; |
| } |
| if *elements { |
| write!(f, ", ELEMENTS")?; |
| } |
| Ok(()) |
| } |
| } |
| } |
| } |
| |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| pub enum ForXml { |
| Raw(Option<String>), |
| Auto, |
| Explicit, |
| Path(Option<String>), |
| } |
| |
| impl fmt::Display for ForXml { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| match self { |
| ForXml::Raw(root) => { |
| write!(f, "RAW")?; |
| if let Some(root) = root { |
| write!(f, "('{}')", root)?; |
| } |
| Ok(()) |
| } |
| ForXml::Auto => write!(f, "AUTO"), |
| ForXml::Explicit => write!(f, "EXPLICIT"), |
| ForXml::Path(root) => { |
| write!(f, "PATH")?; |
| if let Some(root) = root { |
| write!(f, "('{}')", root)?; |
| } |
| Ok(()) |
| } |
| } |
| } |
| } |
| |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| pub enum ForJson { |
| Auto, |
| Path, |
| } |
| |
| impl fmt::Display for ForJson { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| match self { |
| ForJson::Auto => write!(f, "AUTO"), |
| ForJson::Path => write!(f, "PATH"), |
| } |
| } |
| } |
| |
| /// A single column definition in MySQL's `JSON_TABLE` table valued function. |
| /// |
| /// See |
| /// - [MySQL's JSON_TABLE documentation](https://dev.mysql.com/doc/refman/8.0/en/json-table-functions.html#function_json-table) |
| /// - [Oracle's JSON_TABLE documentation](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/JSON_TABLE.html) |
| /// - [MariaDB's JSON_TABLE documentation](https://mariadb.com/kb/en/json_table/) |
| /// |
| /// ```sql |
| /// SELECT * |
| /// FROM JSON_TABLE( |
| /// '["a", "b"]', |
| /// '$[*]' COLUMNS ( |
| /// name FOR ORDINALITY, |
| /// value VARCHAR(20) PATH '$', |
| /// NESTED PATH '$[*]' COLUMNS ( |
| /// value VARCHAR(20) PATH '$' |
| /// ) |
| /// ) |
| /// ) AS jt; |
| /// ``` |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| pub enum JsonTableColumn { |
| /// A named column with a JSON path |
| Named(JsonTableNamedColumn), |
| /// The FOR ORDINALITY column, which is a special column that returns the index of the current row in a JSON array. |
| ForOrdinality(Ident), |
| /// A set of nested columns, which extracts data from a nested JSON array. |
| Nested(JsonTableNestedColumn), |
| } |
| |
| impl fmt::Display for JsonTableColumn { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| match self { |
| JsonTableColumn::Named(json_table_named_column) => { |
| write!(f, "{json_table_named_column}") |
| } |
| JsonTableColumn::ForOrdinality(ident) => write!(f, "{} FOR ORDINALITY", ident), |
| JsonTableColumn::Nested(json_table_nested_column) => { |
| write!(f, "{json_table_nested_column}") |
| } |
| } |
| } |
| } |
| |
| /// A nested column in a JSON_TABLE column list |
| /// |
| /// See <https://mariadb.com/kb/en/json_table/#nested-paths> |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| pub struct JsonTableNestedColumn { |
| pub path: Value, |
| pub columns: Vec<JsonTableColumn>, |
| } |
| |
| impl fmt::Display for JsonTableNestedColumn { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| write!( |
| f, |
| "NESTED PATH {} COLUMNS ({})", |
| self.path, |
| display_comma_separated(&self.columns) |
| ) |
| } |
| } |
| |
| /// A single column definition in MySQL's `JSON_TABLE` table valued function. |
| /// |
| /// See <https://mariadb.com/kb/en/json_table/#path-columns> |
| /// |
| /// ```sql |
| /// value VARCHAR(20) PATH '$' |
| /// ``` |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| pub struct JsonTableNamedColumn { |
| /// The name of the column to be extracted. |
| pub name: Ident, |
| /// The type of the column to be extracted. |
| pub r#type: DataType, |
| /// The path to the column to be extracted. Must be a literal string. |
| pub path: Value, |
| /// true if the column is a boolean set to true if the given path exists |
| pub exists: bool, |
| /// The empty handling clause of the column |
| pub on_empty: Option<JsonTableColumnErrorHandling>, |
| /// The error handling clause of the column |
| pub on_error: Option<JsonTableColumnErrorHandling>, |
| } |
| |
| impl fmt::Display for JsonTableNamedColumn { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| write!( |
| f, |
| "{} {}{} PATH {}", |
| self.name, |
| self.r#type, |
| if self.exists { " EXISTS" } else { "" }, |
| self.path |
| )?; |
| if let Some(on_empty) = &self.on_empty { |
| write!(f, " {} ON EMPTY", on_empty)?; |
| } |
| if let Some(on_error) = &self.on_error { |
| write!(f, " {} ON ERROR", on_error)?; |
| } |
| Ok(()) |
| } |
| } |
| |
| /// Stores the error handling clause of a `JSON_TABLE` table valued function: |
| /// {NULL | DEFAULT json_string | ERROR} ON {ERROR | EMPTY } |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| pub enum JsonTableColumnErrorHandling { |
| Null, |
| Default(Value), |
| Error, |
| } |
| |
| impl fmt::Display for JsonTableColumnErrorHandling { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| match self { |
| JsonTableColumnErrorHandling::Null => write!(f, "NULL"), |
| JsonTableColumnErrorHandling::Default(json_string) => { |
| write!(f, "DEFAULT {}", json_string) |
| } |
| JsonTableColumnErrorHandling::Error => write!(f, "ERROR"), |
| } |
| } |
| } |
| |
| /// A single column definition in MSSQL's `OPENJSON WITH` clause. |
| /// |
| /// ```sql |
| /// colName type [ column_path ] [ AS JSON ] |
| /// ``` |
| /// |
| /// Reference: <https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16#syntax> |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| pub struct OpenJsonTableColumn { |
| /// The name of the column to be extracted. |
| pub name: Ident, |
| /// The type of the column to be extracted. |
| pub r#type: DataType, |
| /// The path to the column to be extracted. Must be a literal string. |
| pub path: Option<String>, |
| /// The `AS JSON` option. |
| pub as_json: bool, |
| } |
| |
| impl fmt::Display for OpenJsonTableColumn { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| write!(f, "{} {}", self.name, self.r#type)?; |
| if let Some(path) = &self.path { |
| write!(f, " '{}'", value::escape_single_quote_string(path))?; |
| } |
| if self.as_json { |
| write!(f, " AS JSON")?; |
| } |
| Ok(()) |
| } |
| } |
| |
| /// BigQuery supports ValueTables which have 2 modes: |
| /// `SELECT AS STRUCT` |
| /// `SELECT AS VALUE` |
| /// <https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#value_tables> |
| #[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 ValueTableMode { |
| AsStruct, |
| AsValue, |
| } |
| |
| impl fmt::Display for ValueTableMode { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| match self { |
| ValueTableMode::AsStruct => write!(f, "AS STRUCT"), |
| ValueTableMode::AsValue => write!(f, "AS VALUE"), |
| } |
| } |
| } |