Skip to content

Commit

Permalink
Merge remote-tracking branch 'fork/main' into zdenko/support-snowflak…
Browse files Browse the repository at this point in the history
…e-trim
  • Loading branch information
zdenal committed Oct 5, 2023
2 parents 09a3009 + 5263da6 commit 7d28a81
Show file tree
Hide file tree
Showing 17 changed files with 743 additions and 70 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ $ cargo run --features json_example --example cli FILENAME.sql [--dialectname]
## Users

This parser is currently being used by the [DataFusion] query engine,
[LocustDB], [Ballista], [GlueSQL], and [Opteryx].
[LocustDB], [Ballista], [GlueSQL], [Opteryx], and [JumpWire].

If your project is using sqlparser-rs feel free to make a PR to add it
to this list.
Expand Down Expand Up @@ -179,6 +179,7 @@ licensed as above, without any additional terms or conditions.
[Ballista]: https://github.com/apache/arrow-ballista
[GlueSQL]: https://github.com/gluesql/gluesql
[Opteryx]: https://github.com/mabel-dev/opteryx
[JumpWire]: https://github.com/extragoodlabs/jumpwire
[Pratt Parser]: https://tdop.github.io/
[sql-2016-grammar]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html
[sql-standard]: https://en.wikipedia.org/wiki/ISO/IEC_9075
55 changes: 50 additions & 5 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ impl fmt::Display for Ident {
let escaped = value::escape_quoted_string(&self.value, q);
write!(f, "{q}{escaped}{q}")
}
Some(q) if q == '[' => write!(f, "[{}]", self.value),
Some('[') => write!(f, "[{}]", self.value),
None => f.write_str(&self.value),
_ => panic!("unexpected quote style"),
}
Expand Down Expand Up @@ -579,7 +579,7 @@ pub enum Expr {
///
/// Syntax:
/// ```sql
/// MARCH (<col>, <col>, ...) AGAINST (<expr> [<search modifier>])
/// MATCH (<col>, <col>, ...) AGAINST (<expr> [<search modifier>])
///
/// <col> = CompoundIdentifier
/// <expr> = String literal
Expand Down Expand Up @@ -1307,6 +1307,10 @@ pub enum Statement {
selection: Option<Expr>,
/// RETURNING
returning: Option<Vec<SelectItem>>,
/// ORDER BY (MySQL)
order_by: Vec<OrderByExpr>,
/// LIMIT (MySQL)
limit: Option<Expr>,
},
/// CREATE VIEW
CreateView {
Expand All @@ -1318,6 +1322,12 @@ pub enum Statement {
query: Box<Query>,
with_options: Vec<SqlOption>,
cluster_by: Vec<Ident>,
/// if true, has RedShift [`WITH NO SCHEMA BINDING`] clause <https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_VIEW.html>
with_no_schema_binding: bool,
/// if true, has SQLite `IF NOT EXISTS` clause <https://www.sqlite.org/lang_createview.html>
if_not_exists: bool,
/// if true, has SQLite `TEMP` or `TEMPORARY` clause <https://www.sqlite.org/lang_createview.html>
temporary: bool,
},
/// CREATE TABLE
CreateTable {
Expand Down Expand Up @@ -1435,6 +1445,16 @@ pub enum Statement {
name: Ident,
operation: AlterRoleOperation,
},
/// ATTACH DATABASE 'path/to/file' AS alias
/// (SQLite-specific)
AttachDatabase {
/// The name to bind to the newly attached database
schema_name: Ident,
/// An expression that indicates the path to the database file
database_file_name: Expr,
/// true if the syntax is 'ATTACH DATABASE', false if it's just 'ATTACH'
database: bool,
},
/// DROP
Drop {
/// The type of the object to drop: TABLE, VIEW, etc.
Expand Down Expand Up @@ -1975,6 +1995,14 @@ impl fmt::Display for Statement {
}
Ok(())
}
Statement::AttachDatabase {
schema_name,
database_file_name,
database,
} => {
let keyword = if *database { "DATABASE " } else { "" };
write!(f, "ATTACH {keyword}{database_file_name} AS {schema_name}")
}
Statement::Analyze {
table_name,
partitions,
Expand Down Expand Up @@ -2129,6 +2157,8 @@ impl fmt::Display for Statement {
using,
selection,
returning,
order_by,
limit,
} => {
write!(f, "DELETE ")?;
if !tables.is_empty() {
Expand All @@ -2144,6 +2174,12 @@ impl fmt::Display for Statement {
if let Some(returning) = returning {
write!(f, " RETURNING {}", display_comma_separated(returning))?;
}
if !order_by.is_empty() {
write!(f, " ORDER BY {}", display_comma_separated(order_by))?;
}
if let Some(limit) = limit {
write!(f, " LIMIT {limit}")?;
}
Ok(())
}
Statement::Close { cursor } => {
Expand Down Expand Up @@ -2247,13 +2283,18 @@ impl fmt::Display for Statement {
materialized,
with_options,
cluster_by,
with_no_schema_binding,
if_not_exists,
temporary,
} => {
write!(
f,
"CREATE {or_replace}{materialized}VIEW {name}",
"CREATE {or_replace}{materialized}{temporary}VIEW {if_not_exists}{name}",
or_replace = if *or_replace { "OR REPLACE " } else { "" },
materialized = if *materialized { "MATERIALIZED " } else { "" },
name = name
name = name,
temporary = if *temporary { "TEMPORARY " } else { "" },
if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" }
)?;
if !with_options.is_empty() {
write!(f, " WITH ({})", display_comma_separated(with_options))?;
Expand All @@ -2264,7 +2305,11 @@ impl fmt::Display for Statement {
if !cluster_by.is_empty() {
write!(f, " CLUSTER BY ({})", display_comma_separated(cluster_by))?;
}
write!(f, " AS {query}")
write!(f, " AS {query}")?;
if *with_no_schema_binding {
write!(f, " WITH NO SCHEMA BINDING")?;
}
Ok(())
}
Statement::CreateTable {
name,
Expand Down
74 changes: 53 additions & 21 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ pub struct Query {
pub order_by: Vec<OrderByExpr>,
/// `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 }`
Expand All @@ -58,6 +62,9 @@ impl fmt::Display for Query {
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 fetch) = self.fetch {
write!(f, " {fetch}")?;
}
Expand Down Expand Up @@ -713,13 +720,28 @@ pub enum TableFactor {
/// 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>,
#[cfg_attr(feature = "visitor", visit(with = "visit_table_factor"))]
table: Box<TableFactor>,
aggregate_function: Expr, // Function expression
value_column: Vec<Ident>,
pivot_values: Vec<Value>,
pivot_alias: Option<TableAlias>,
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 {
#[cfg_attr(feature = "visitor", visit(with = "visit_table_factor"))]
table: Box<TableFactor>,
value: Ident,
name: Ident,
columns: Vec<Ident>,
alias: Option<TableAlias>,
},
}

Expand Down Expand Up @@ -803,32 +825,42 @@ impl fmt::Display for TableFactor {
Ok(())
}
TableFactor::Pivot {
name,
table_alias,
table,
aggregate_function,
value_column,
pivot_values,
pivot_alias,
alias,
} => {
write!(f, "{}", name)?;
if table_alias.is_some() {
write!(f, " AS {}", table_alias.as_ref().unwrap())?;
}
write!(
f,
" PIVOT({} FOR {} IN (",
"{} PIVOT({} FOR {} IN ({}))",
table,
aggregate_function,
Expr::CompoundIdentifier(value_column.to_vec())
Expr::CompoundIdentifier(value_column.to_vec()),
display_comma_separated(pivot_values)
)?;
for value in pivot_values {
write!(f, "{}", value)?;
if !value.eq(pivot_values.last().unwrap()) {
write!(f, ", ")?;
}
if alias.is_some() {
write!(f, " AS {}", alias.as_ref().unwrap())?;
}
write!(f, "))")?;
if pivot_alias.is_some() {
write!(f, " AS {}", pivot_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(())
}
Expand Down
8 changes: 8 additions & 0 deletions src/ast/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ pub enum DateTimeField {
Month,
Week,
Day,
DayOfWeek,
DayOfYear,
Date,
Hour,
Minute,
Expand All @@ -127,6 +129,7 @@ pub enum DateTimeField {
Doy,
Epoch,
Isodow,
IsoWeek,
Isoyear,
Julian,
Microsecond,
Expand All @@ -138,6 +141,7 @@ pub enum DateTimeField {
Nanosecond,
Nanoseconds,
Quarter,
Time,
Timezone,
TimezoneHour,
TimezoneMinute,
Expand All @@ -151,6 +155,8 @@ impl fmt::Display for DateTimeField {
DateTimeField::Month => "MONTH",
DateTimeField::Week => "WEEK",
DateTimeField::Day => "DAY",
DateTimeField::DayOfWeek => "DAYOFWEEK",
DateTimeField::DayOfYear => "DAYOFYEAR",
DateTimeField::Date => "DATE",
DateTimeField::Hour => "HOUR",
DateTimeField::Minute => "MINUTE",
Expand All @@ -162,6 +168,7 @@ impl fmt::Display for DateTimeField {
DateTimeField::Epoch => "EPOCH",
DateTimeField::Isodow => "ISODOW",
DateTimeField::Isoyear => "ISOYEAR",
DateTimeField::IsoWeek => "ISOWEEK",
DateTimeField::Julian => "JULIAN",
DateTimeField::Microsecond => "MICROSECOND",
DateTimeField::Microseconds => "MICROSECONDS",
Expand All @@ -172,6 +179,7 @@ impl fmt::Display for DateTimeField {
DateTimeField::Nanosecond => "NANOSECOND",
DateTimeField::Nanoseconds => "NANOSECONDS",
DateTimeField::Quarter => "QUARTER",
DateTimeField::Time => "TIME",
DateTimeField::Timezone => "TIMEZONE",
DateTimeField::TimezoneHour => "TIMEZONE_HOUR",
DateTimeField::TimezoneMinute => "TIMEZONE_MINUTE",
Expand Down
2 changes: 1 addition & 1 deletion src/ast/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ where
///
/// This demonstrates how to effectively replace an expression with another more complicated one
/// that references the original. This example avoids unnecessary allocations by using the
/// [`std::mem`](std::mem) family of functions.
/// [`std::mem`] family of functions.
///
/// ```
/// # use sqlparser::parser::Parser;
Expand Down
7 changes: 7 additions & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ define_keywords!(
ASYMMETRIC,
AT,
ATOMIC,
ATTACH,
AUTHORIZATION,
AUTOINCREMENT,
AUTO_INCREMENT,
Expand All @@ -109,6 +110,7 @@ define_keywords!(
BIGINT,
BIGNUMERIC,
BINARY,
BINDING,
BLOB,
BLOOMFILTER,
BOOL,
Expand Down Expand Up @@ -195,6 +197,8 @@ define_keywords!(
DATE,
DATETIME,
DAY,
DAYOFWEEK,
DAYOFYEAR,
DEALLOCATE,
DEC,
DECADE,
Expand Down Expand Up @@ -333,6 +337,7 @@ define_keywords!(
IS,
ISODOW,
ISOLATION,
ISOWEEK,
ISOYEAR,
JAR,
JOIN,
Expand Down Expand Up @@ -631,6 +636,7 @@ define_keywords!(
UNKNOWN,
UNLOGGED,
UNNEST,
UNPIVOT,
UNSIGNED,
UNTIL,
UPDATE,
Expand Down Expand Up @@ -689,6 +695,7 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[
Keyword::HAVING,
Keyword::ORDER,
Keyword::PIVOT,
Keyword::UNPIVOT,
Keyword::TOP,
Keyword::LATERAL,
Keyword::VIEW,
Expand Down
Loading

0 comments on commit 7d28a81

Please sign in to comment.