mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-07-07 17:04:59 +00:00
Merge remote-tracking branch 'upstream/main' into eper/tag-and-policy-object-name
This commit is contained in:
commit
9dcc348a4d
34 changed files with 2612 additions and 565 deletions
|
@ -63,7 +63,7 @@ $ cargo run --example cli - [--dialectname]
|
|||
};
|
||||
|
||||
let contents = if filename == "-" {
|
||||
println!("Parsing from stdin using {:?}", dialect);
|
||||
println!("Parsing from stdin using {dialect:?}");
|
||||
let mut buf = Vec::new();
|
||||
stdin()
|
||||
.read_to_end(&mut buf)
|
||||
|
|
|
@ -45,25 +45,24 @@ fn basic_queries(c: &mut Criterion) {
|
|||
|
||||
let large_statement = {
|
||||
let expressions = (0..1000)
|
||||
.map(|n| format!("FN_{}(COL_{})", n, n))
|
||||
.map(|n| format!("FN_{n}(COL_{n})"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let tables = (0..1000)
|
||||
.map(|n| format!("TABLE_{}", n))
|
||||
.map(|n| format!("TABLE_{n}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" JOIN ");
|
||||
let where_condition = (0..1000)
|
||||
.map(|n| format!("COL_{} = {}", n, n))
|
||||
.map(|n| format!("COL_{n} = {n}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" OR ");
|
||||
let order_condition = (0..1000)
|
||||
.map(|n| format!("COL_{} DESC", n))
|
||||
.map(|n| format!("COL_{n} DESC"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
format!(
|
||||
"SELECT {} FROM {} WHERE {} ORDER BY {}",
|
||||
expressions, tables, where_condition, order_condition
|
||||
"SELECT {expressions} FROM {tables} WHERE {where_condition} ORDER BY {order_condition}"
|
||||
)
|
||||
};
|
||||
|
||||
|
|
|
@ -446,6 +446,14 @@ pub enum DataType {
|
|||
///
|
||||
/// [PostgreSQL]: https://www.postgresql.org/docs/9.5/functions-geometry.html
|
||||
GeometricType(GeometricTypeKind),
|
||||
/// PostgreSQL text search vectors, see [PostgreSQL].
|
||||
///
|
||||
/// [PostgreSQL]: https://www.postgresql.org/docs/17/datatype-textsearch.html
|
||||
TsVector,
|
||||
/// PostgreSQL text search query, see [PostgreSQL].
|
||||
///
|
||||
/// [PostgreSQL]: https://www.postgresql.org/docs/17/datatype-textsearch.html
|
||||
TsQuery,
|
||||
}
|
||||
|
||||
impl fmt::Display for DataType {
|
||||
|
@ -658,7 +666,7 @@ impl fmt::Display for DataType {
|
|||
}
|
||||
DataType::Enum(vals, bits) => {
|
||||
match bits {
|
||||
Some(bits) => write!(f, "ENUM{}", bits),
|
||||
Some(bits) => write!(f, "ENUM{bits}"),
|
||||
None => write!(f, "ENUM"),
|
||||
}?;
|
||||
write!(f, "(")?;
|
||||
|
@ -706,16 +714,16 @@ impl fmt::Display for DataType {
|
|||
}
|
||||
// ClickHouse
|
||||
DataType::Nullable(data_type) => {
|
||||
write!(f, "Nullable({})", data_type)
|
||||
write!(f, "Nullable({data_type})")
|
||||
}
|
||||
DataType::FixedString(character_length) => {
|
||||
write!(f, "FixedString({})", character_length)
|
||||
write!(f, "FixedString({character_length})")
|
||||
}
|
||||
DataType::LowCardinality(data_type) => {
|
||||
write!(f, "LowCardinality({})", data_type)
|
||||
write!(f, "LowCardinality({data_type})")
|
||||
}
|
||||
DataType::Map(key_data_type, value_data_type) => {
|
||||
write!(f, "Map({}, {})", key_data_type, value_data_type)
|
||||
write!(f, "Map({key_data_type}, {value_data_type})")
|
||||
}
|
||||
DataType::Tuple(fields) => {
|
||||
write!(f, "Tuple({})", display_comma_separated(fields))
|
||||
|
@ -737,7 +745,9 @@ impl fmt::Display for DataType {
|
|||
DataType::NamedTable { name, columns } => {
|
||||
write!(f, "{} TABLE ({})", name, display_comma_separated(columns))
|
||||
}
|
||||
DataType::GeometricType(kind) => write!(f, "{}", kind),
|
||||
DataType::GeometricType(kind) => write!(f, "{kind}"),
|
||||
DataType::TsVector => write!(f, "TSVECTOR"),
|
||||
DataType::TsQuery => write!(f, "TSQUERY"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -932,7 +942,7 @@ impl fmt::Display for CharacterLength {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
CharacterLength::IntegerLength { length, unit } => {
|
||||
write!(f, "{}", length)?;
|
||||
write!(f, "{length}")?;
|
||||
if let Some(unit) = unit {
|
||||
write!(f, " {unit}")?;
|
||||
}
|
||||
|
@ -987,7 +997,7 @@ impl fmt::Display for BinaryLength {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
BinaryLength::IntegerLength { length } => {
|
||||
write!(f, "{}", length)?;
|
||||
write!(f, "{length}")?;
|
||||
}
|
||||
BinaryLength::Max => {
|
||||
write!(f, "MAX")?;
|
||||
|
|
|
@ -173,7 +173,7 @@ impl fmt::Display for AlterRoleOperation {
|
|||
in_database,
|
||||
} => {
|
||||
if let Some(database_name) = in_database {
|
||||
write!(f, "IN DATABASE {} ", database_name)?;
|
||||
write!(f, "IN DATABASE {database_name} ")?;
|
||||
}
|
||||
|
||||
match config_value {
|
||||
|
@ -187,7 +187,7 @@ impl fmt::Display for AlterRoleOperation {
|
|||
in_database,
|
||||
} => {
|
||||
if let Some(database_name) = in_database {
|
||||
write!(f, "IN DATABASE {} ", database_name)?;
|
||||
write!(f, "IN DATABASE {database_name} ")?;
|
||||
}
|
||||
|
||||
match config_name {
|
||||
|
@ -218,15 +218,15 @@ impl fmt::Display for Use {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str("USE ")?;
|
||||
match self {
|
||||
Use::Catalog(name) => write!(f, "CATALOG {}", name),
|
||||
Use::Schema(name) => write!(f, "SCHEMA {}", name),
|
||||
Use::Database(name) => write!(f, "DATABASE {}", name),
|
||||
Use::Warehouse(name) => write!(f, "WAREHOUSE {}", name),
|
||||
Use::Role(name) => write!(f, "ROLE {}", name),
|
||||
Use::Catalog(name) => write!(f, "CATALOG {name}"),
|
||||
Use::Schema(name) => write!(f, "SCHEMA {name}"),
|
||||
Use::Database(name) => write!(f, "DATABASE {name}"),
|
||||
Use::Warehouse(name) => write!(f, "WAREHOUSE {name}"),
|
||||
Use::Role(name) => write!(f, "ROLE {name}"),
|
||||
Use::SecondaryRoles(secondary_roles) => {
|
||||
write!(f, "SECONDARY ROLES {}", secondary_roles)
|
||||
write!(f, "SECONDARY ROLES {secondary_roles}")
|
||||
}
|
||||
Use::Object(name) => write!(f, "{}", name),
|
||||
Use::Object(name) => write!(f, "{name}"),
|
||||
Use::Default => write!(f, "DEFAULT"),
|
||||
}
|
||||
}
|
||||
|
|
124
src/ast/ddl.rs
124
src/ast/ddl.rs
|
@ -30,11 +30,11 @@ use sqlparser_derive::{Visit, VisitMut};
|
|||
|
||||
use crate::ast::value::escape_single_quote_string;
|
||||
use crate::ast::{
|
||||
display_comma_separated, display_separated, CommentDef, CreateFunctionBody,
|
||||
display_comma_separated, display_separated, ArgMode, CommentDef, CreateFunctionBody,
|
||||
CreateFunctionUsing, DataType, Expr, FunctionBehavior, FunctionCalledOnNull,
|
||||
FunctionDeterminismSpecifier, FunctionParallel, Ident, MySQLColumnPosition, ObjectName,
|
||||
OperateFunctionArg, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Tag, Value,
|
||||
ValueWithSpan,
|
||||
FunctionDeterminismSpecifier, FunctionParallel, Ident, IndexColumn, MySQLColumnPosition,
|
||||
ObjectName, OperateFunctionArg, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Tag,
|
||||
Value, ValueWithSpan,
|
||||
};
|
||||
use crate::keywords::Keyword;
|
||||
use crate::tokenizer::Token;
|
||||
|
@ -57,7 +57,7 @@ impl fmt::Display for ReplicaIdentity {
|
|||
ReplicaIdentity::None => f.write_str("NONE"),
|
||||
ReplicaIdentity::Full => f.write_str("FULL"),
|
||||
ReplicaIdentity::Default => f.write_str("DEFAULT"),
|
||||
ReplicaIdentity::Index(idx) => write!(f, "USING INDEX {}", idx),
|
||||
ReplicaIdentity::Index(idx) => write!(f, "USING INDEX {idx}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,8 +67,11 @@ impl fmt::Display for ReplicaIdentity {
|
|||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub enum AlterTableOperation {
|
||||
/// `ADD <table_constraint>`
|
||||
AddConstraint(TableConstraint),
|
||||
/// `ADD <table_constraint> [NOT VALID]`
|
||||
AddConstraint {
|
||||
constraint: TableConstraint,
|
||||
not_valid: bool,
|
||||
},
|
||||
/// `ADD [COLUMN] [IF NOT EXISTS] <column_def>`
|
||||
AddColumn {
|
||||
/// `[COLUMN]`.
|
||||
|
@ -344,6 +347,10 @@ pub enum AlterTableOperation {
|
|||
equals: bool,
|
||||
value: ValueWithSpan,
|
||||
},
|
||||
/// `VALIDATE CONSTRAINT <name>`
|
||||
ValidateConstraint {
|
||||
name: Ident,
|
||||
},
|
||||
}
|
||||
|
||||
/// An `ALTER Policy` (`Statement::AlterPolicy`) operation
|
||||
|
@ -450,7 +457,7 @@ pub enum Owner {
|
|||
impl fmt::Display for Owner {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Owner::Ident(ident) => write!(f, "{}", ident),
|
||||
Owner::Ident(ident) => write!(f, "{ident}"),
|
||||
Owner::CurrentRole => write!(f, "CURRENT_ROLE"),
|
||||
Owner::CurrentUser => write!(f, "CURRENT_USER"),
|
||||
Owner::SessionUser => write!(f, "SESSION_USER"),
|
||||
|
@ -494,7 +501,16 @@ impl fmt::Display for AlterTableOperation {
|
|||
display_separated(new_partitions, " "),
|
||||
ine = if *if_not_exists { " IF NOT EXISTS" } else { "" }
|
||||
),
|
||||
AlterTableOperation::AddConstraint(c) => write!(f, "ADD {c}"),
|
||||
AlterTableOperation::AddConstraint {
|
||||
not_valid,
|
||||
constraint,
|
||||
} => {
|
||||
write!(f, "ADD {constraint}")?;
|
||||
if *not_valid {
|
||||
write!(f, " NOT VALID")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
AlterTableOperation::AddColumn {
|
||||
column_keyword,
|
||||
if_not_exists,
|
||||
|
@ -525,7 +541,7 @@ impl fmt::Display for AlterTableOperation {
|
|||
if *if_not_exists {
|
||||
write!(f, " IF NOT EXISTS")?;
|
||||
}
|
||||
write!(f, " {} ({})", name, query)
|
||||
write!(f, " {name} ({query})")
|
||||
}
|
||||
AlterTableOperation::Algorithm { equals, algorithm } => {
|
||||
write!(
|
||||
|
@ -540,7 +556,7 @@ impl fmt::Display for AlterTableOperation {
|
|||
if *if_exists {
|
||||
write!(f, " IF EXISTS")?;
|
||||
}
|
||||
write!(f, " {}", name)
|
||||
write!(f, " {name}")
|
||||
}
|
||||
AlterTableOperation::MaterializeProjection {
|
||||
if_exists,
|
||||
|
@ -551,9 +567,9 @@ impl fmt::Display for AlterTableOperation {
|
|||
if *if_exists {
|
||||
write!(f, " IF EXISTS")?;
|
||||
}
|
||||
write!(f, " {}", name)?;
|
||||
write!(f, " {name}")?;
|
||||
if let Some(partition) = partition {
|
||||
write!(f, " IN PARTITION {}", partition)?;
|
||||
write!(f, " IN PARTITION {partition}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -566,9 +582,9 @@ impl fmt::Display for AlterTableOperation {
|
|||
if *if_exists {
|
||||
write!(f, " IF EXISTS")?;
|
||||
}
|
||||
write!(f, " {}", name)?;
|
||||
write!(f, " {name}")?;
|
||||
if let Some(partition) = partition {
|
||||
write!(f, " IN PARTITION {}", partition)?;
|
||||
write!(f, " IN PARTITION {partition}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -772,6 +788,9 @@ impl fmt::Display for AlterTableOperation {
|
|||
AlterTableOperation::ReplicaIdentity { identity } => {
|
||||
write!(f, "REPLICA IDENTITY {identity}")
|
||||
}
|
||||
AlterTableOperation::ValidateConstraint { name } => {
|
||||
write!(f, "VALIDATE CONSTRAINT {name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -893,7 +912,10 @@ pub enum AlterColumnOperation {
|
|||
data_type: DataType,
|
||||
/// PostgreSQL specific
|
||||
using: Option<Expr>,
|
||||
/// Set to true if the statement includes the `SET DATA TYPE` keywords
|
||||
had_set: bool,
|
||||
},
|
||||
|
||||
/// `ADD GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( sequence_options ) ]`
|
||||
///
|
||||
/// Note: this is a PostgreSQL-specific operation.
|
||||
|
@ -914,12 +936,19 @@ impl fmt::Display for AlterColumnOperation {
|
|||
AlterColumnOperation::DropDefault => {
|
||||
write!(f, "DROP DEFAULT")
|
||||
}
|
||||
AlterColumnOperation::SetDataType { data_type, using } => {
|
||||
if let Some(expr) = using {
|
||||
write!(f, "SET DATA TYPE {data_type} USING {expr}")
|
||||
} else {
|
||||
write!(f, "SET DATA TYPE {data_type}")
|
||||
AlterColumnOperation::SetDataType {
|
||||
data_type,
|
||||
using,
|
||||
had_set,
|
||||
} => {
|
||||
if *had_set {
|
||||
write!(f, "SET DATA ")?;
|
||||
}
|
||||
write!(f, "TYPE {data_type}")?;
|
||||
if let Some(expr) = using {
|
||||
write!(f, " USING {expr}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
AlterColumnOperation::AddGenerated {
|
||||
generated_as,
|
||||
|
@ -979,7 +1008,7 @@ pub enum TableConstraint {
|
|||
/// [1]: IndexType
|
||||
index_type: Option<IndexType>,
|
||||
/// Identifiers of the columns that are unique.
|
||||
columns: Vec<Ident>,
|
||||
columns: Vec<IndexColumn>,
|
||||
index_options: Vec<IndexOption>,
|
||||
characteristics: Option<ConstraintCharacteristics>,
|
||||
/// Optional Postgres nulls handling: `[ NULLS [ NOT ] DISTINCT ]`
|
||||
|
@ -1015,7 +1044,7 @@ pub enum TableConstraint {
|
|||
/// [1]: IndexType
|
||||
index_type: Option<IndexType>,
|
||||
/// Identifiers of the columns that form the primary key.
|
||||
columns: Vec<Ident>,
|
||||
columns: Vec<IndexColumn>,
|
||||
index_options: Vec<IndexOption>,
|
||||
characteristics: Option<ConstraintCharacteristics>,
|
||||
},
|
||||
|
@ -1060,7 +1089,7 @@ pub enum TableConstraint {
|
|||
/// [1]: IndexType
|
||||
index_type: Option<IndexType>,
|
||||
/// Referred column identifier list.
|
||||
columns: Vec<Ident>,
|
||||
columns: Vec<IndexColumn>,
|
||||
},
|
||||
/// MySQLs [fulltext][1] definition. Since the [`SPATIAL`][2] definition is exactly the same,
|
||||
/// and MySQL displays both the same way, it is part of this definition as well.
|
||||
|
@ -1083,7 +1112,7 @@ pub enum TableConstraint {
|
|||
/// Optional index name.
|
||||
opt_index_name: Option<Ident>,
|
||||
/// Referred column identifier list.
|
||||
columns: Vec<Ident>,
|
||||
columns: Vec<IndexColumn>,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1168,7 +1197,7 @@ impl fmt::Display for TableConstraint {
|
|||
write!(f, " ON UPDATE {action}")?;
|
||||
}
|
||||
if let Some(characteristics) = characteristics {
|
||||
write!(f, " {}", characteristics)?;
|
||||
write!(f, " {characteristics}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1308,7 +1337,7 @@ impl fmt::Display for IndexType {
|
|||
Self::SPGiST => write!(f, "SPGIST"),
|
||||
Self::BRIN => write!(f, "BRIN"),
|
||||
Self::Bloom => write!(f, "BLOOM"),
|
||||
Self::Custom(name) => write!(f, "{}", name),
|
||||
Self::Custom(name) => write!(f, "{name}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1367,11 +1396,16 @@ impl fmt::Display for NullsDistinctOption {
|
|||
pub struct ProcedureParam {
|
||||
pub name: Ident,
|
||||
pub data_type: DataType,
|
||||
pub mode: Option<ArgMode>,
|
||||
}
|
||||
|
||||
impl fmt::Display for ProcedureParam {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{} {}", self.name, self.data_type)
|
||||
if let Some(mode) = &self.mode {
|
||||
write!(f, "{mode} {} {}", self.name, self.data_type)
|
||||
} else {
|
||||
write!(f, "{} {}", self.name, self.data_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1421,17 +1455,41 @@ impl fmt::Display for ColumnDef {
|
|||
pub struct ViewColumnDef {
|
||||
pub name: Ident,
|
||||
pub data_type: Option<DataType>,
|
||||
pub options: Option<Vec<ColumnOption>>,
|
||||
pub options: Option<ColumnOptions>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub enum ColumnOptions {
|
||||
CommaSeparated(Vec<ColumnOption>),
|
||||
SpaceSeparated(Vec<ColumnOption>),
|
||||
}
|
||||
|
||||
impl ColumnOptions {
|
||||
pub fn as_slice(&self) -> &[ColumnOption] {
|
||||
match self {
|
||||
ColumnOptions::CommaSeparated(options) => options.as_slice(),
|
||||
ColumnOptions::SpaceSeparated(options) => options.as_slice(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ViewColumnDef {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.name)?;
|
||||
if let Some(data_type) = self.data_type.as_ref() {
|
||||
write!(f, " {}", data_type)?;
|
||||
write!(f, " {data_type}")?;
|
||||
}
|
||||
if let Some(options) = self.options.as_ref() {
|
||||
write!(f, " {}", display_comma_separated(options.as_slice()))?;
|
||||
match options {
|
||||
ColumnOptions::CommaSeparated(column_options) => {
|
||||
write!(f, " {}", display_comma_separated(column_options.as_slice()))?;
|
||||
}
|
||||
ColumnOptions::SpaceSeparated(column_options) => {
|
||||
write!(f, " {}", display_separated(column_options.as_slice(), " "))?
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1816,7 +1874,7 @@ impl fmt::Display for ColumnOption {
|
|||
} => {
|
||||
write!(f, "{}", if *is_primary { "PRIMARY KEY" } else { "UNIQUE" })?;
|
||||
if let Some(characteristics) = characteristics {
|
||||
write!(f, " {}", characteristics)?;
|
||||
write!(f, " {characteristics}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1838,7 +1896,7 @@ impl fmt::Display for ColumnOption {
|
|||
write!(f, " ON UPDATE {action}")?;
|
||||
}
|
||||
if let Some(characteristics) = characteristics {
|
||||
write!(f, " {}", characteristics)?;
|
||||
write!(f, " {characteristics}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1898,7 +1956,7 @@ impl fmt::Display for ColumnOption {
|
|||
write!(f, "{parameters}")
|
||||
}
|
||||
OnConflict(keyword) => {
|
||||
write!(f, "ON CONFLICT {:?}", keyword)?;
|
||||
write!(f, "ON CONFLICT {keyword:?}")?;
|
||||
Ok(())
|
||||
}
|
||||
Policy(parameters) => {
|
||||
|
|
|
@ -55,7 +55,7 @@ impl Display for IndexColumn {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.column)?;
|
||||
if let Some(operator_class) = &self.operator_class {
|
||||
write!(f, " {}", operator_class)?;
|
||||
write!(f, " {operator_class}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -266,7 +266,7 @@ impl Display for CreateTable {
|
|||
name = self.name,
|
||||
)?;
|
||||
if let Some(on_cluster) = &self.on_cluster {
|
||||
write!(f, " ON CLUSTER {}", on_cluster)?;
|
||||
write!(f, " ON CLUSTER {on_cluster}")?;
|
||||
}
|
||||
if !self.columns.is_empty() || !self.constraints.is_empty() {
|
||||
f.write_str(" (")?;
|
||||
|
@ -383,15 +383,15 @@ impl Display for CreateTable {
|
|||
match &self.table_options {
|
||||
options @ CreateTableOptions::With(_)
|
||||
| options @ CreateTableOptions::Plain(_)
|
||||
| options @ CreateTableOptions::TableProperties(_) => write!(f, " {}", options)?,
|
||||
| options @ CreateTableOptions::TableProperties(_) => write!(f, " {options}")?,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if let Some(primary_key) = &self.primary_key {
|
||||
write!(f, " PRIMARY KEY {}", primary_key)?;
|
||||
write!(f, " PRIMARY KEY {primary_key}")?;
|
||||
}
|
||||
if let Some(order_by) = &self.order_by {
|
||||
write!(f, " ORDER BY {}", order_by)?;
|
||||
write!(f, " ORDER BY {order_by}")?;
|
||||
}
|
||||
if let Some(inherits) = &self.inherits {
|
||||
write!(f, " INHERITS ({})", display_comma_separated(inherits))?;
|
||||
|
@ -403,7 +403,7 @@ impl Display for CreateTable {
|
|||
write!(f, " CLUSTER BY {cluster_by}")?;
|
||||
}
|
||||
if let options @ CreateTableOptions::Options(_) = &self.table_options {
|
||||
write!(f, " {}", options)?;
|
||||
write!(f, " {options}")?;
|
||||
}
|
||||
if let Some(external_volume) = self.external_volume.as_ref() {
|
||||
write!(f, " EXTERNAL_VOLUME = '{external_volume}'")?;
|
||||
|
|
|
@ -67,7 +67,7 @@ impl fmt::Display for KeyValueOptions {
|
|||
} else {
|
||||
f.write_str(" ")?;
|
||||
}
|
||||
write!(f, "{}", option)?;
|
||||
write!(f, "{option}")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
349
src/ast/mod.rs
349
src/ast/mod.rs
|
@ -28,6 +28,7 @@ use helpers::{
|
|||
stmt_data_loading::{FileStagingCommand, StageLoadSelectItemKind},
|
||||
};
|
||||
|
||||
use core::cmp::Ordering;
|
||||
use core::ops::Deref;
|
||||
use core::{
|
||||
fmt::{self, Display},
|
||||
|
@ -60,13 +61,14 @@ pub use self::ddl::{
|
|||
AlterColumnOperation, AlterConnectorOwner, AlterIndexOperation, AlterPolicyOperation,
|
||||
AlterTableAlgorithm, AlterTableLock, AlterTableOperation, AlterType, AlterTypeAddValue,
|
||||
AlterTypeAddValuePosition, AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue,
|
||||
ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnPolicy, ColumnPolicyProperty,
|
||||
ConstraintCharacteristics, CreateConnector, CreateDomain, CreateFunction, Deduplicate,
|
||||
DeferrableInitial, DropBehavior, GeneratedAs, GeneratedExpressionMode, IdentityParameters,
|
||||
IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder,
|
||||
IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, Owner, Partition,
|
||||
ProcedureParam, ReferentialAction, ReplicaIdentity, TableConstraint, TagsColumnOption,
|
||||
UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef,
|
||||
ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnOptions, ColumnPolicy,
|
||||
ColumnPolicyProperty, ConstraintCharacteristics, CreateConnector, CreateDomain, CreateFunction,
|
||||
Deduplicate, DeferrableInitial, DropBehavior, GeneratedAs, GeneratedExpressionMode,
|
||||
IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind,
|
||||
IdentityPropertyOrder, IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, Owner,
|
||||
Partition, ProcedureParam, ReferentialAction, ReplicaIdentity, TableConstraint,
|
||||
TagsColumnOption, UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation,
|
||||
ViewColumnDef,
|
||||
};
|
||||
pub use self::dml::{CreateIndex, CreateTable, Delete, IndexColumn, Insert};
|
||||
pub use self::operator::{BinaryOperator, UnaryOperator};
|
||||
|
@ -172,7 +174,7 @@ fn format_statement_list(f: &mut fmt::Formatter, statements: &[Statement]) -> fm
|
|||
}
|
||||
|
||||
/// An identifier, decomposed into its value or character data and the quote style.
|
||||
#[derive(Debug, Clone, PartialOrd, Ord)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub struct Ident {
|
||||
|
@ -214,6 +216,35 @@ impl core::hash::Hash for Ident {
|
|||
|
||||
impl Eq for Ident {}
|
||||
|
||||
impl PartialOrd for Ident {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Ident {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let Ident {
|
||||
value,
|
||||
quote_style,
|
||||
// exhaustiveness check; we ignore spans in ordering
|
||||
span: _,
|
||||
} = self;
|
||||
|
||||
let Ident {
|
||||
value: other_value,
|
||||
quote_style: other_quote_style,
|
||||
// exhaustiveness check; we ignore spans in ordering
|
||||
span: _,
|
||||
} = other;
|
||||
|
||||
// First compare by value, then by quote_style
|
||||
value
|
||||
.cmp(other_value)
|
||||
.then_with(|| quote_style.cmp(other_quote_style))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ident {
|
||||
/// Create a new identifier with the given value and no quotes and an empty span.
|
||||
pub fn new<S>(value: S) -> Self
|
||||
|
@ -326,7 +357,7 @@ impl ObjectNamePart {
|
|||
impl fmt::Display for ObjectNamePart {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ObjectNamePart::Identifier(ident) => write!(f, "{}", ident),
|
||||
ObjectNamePart::Identifier(ident) => write!(f, "{ident}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -748,7 +779,7 @@ pub enum Expr {
|
|||
/// `[ NOT ] IN (SELECT ...)`
|
||||
InSubquery {
|
||||
expr: Box<Expr>,
|
||||
subquery: Box<SetExpr>,
|
||||
subquery: Box<Query>,
|
||||
negated: bool,
|
||||
},
|
||||
/// `[ NOT ] IN UNNEST(array_expression)`
|
||||
|
@ -965,7 +996,7 @@ pub enum Expr {
|
|||
data_type: DataType,
|
||||
/// The value of the constant.
|
||||
/// Hint: you can unwrap the string value using `value.into_string()`.
|
||||
value: Value,
|
||||
value: ValueWithSpan,
|
||||
},
|
||||
/// Scalar function call e.g. `LEFT(foo, 5)`
|
||||
Function(Function),
|
||||
|
@ -1093,6 +1124,8 @@ pub enum Expr {
|
|||
/// [Databricks](https://docs.databricks.com/en/sql/language-manual/sql-ref-lambda-functions.html)
|
||||
/// [DuckDb](https://duckdb.org/docs/sql/functions/lambda.html)
|
||||
Lambda(LambdaFunction),
|
||||
/// Checks membership of a value in a JSON array
|
||||
MemberOf(MemberOf),
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
|
@ -1179,8 +1212,8 @@ pub enum AccessExpr {
|
|||
impl fmt::Display for AccessExpr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
AccessExpr::Dot(expr) => write!(f, ".{}", expr),
|
||||
AccessExpr::Subscript(subscript) => write!(f, "[{}]", subscript),
|
||||
AccessExpr::Dot(expr) => write!(f, ".{expr}"),
|
||||
AccessExpr::Subscript(subscript) => write!(f, "[{subscript}]"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1382,12 +1415,12 @@ impl fmt::Display for Expr {
|
|||
match self {
|
||||
Expr::Identifier(s) => write!(f, "{s}"),
|
||||
Expr::Wildcard(_) => f.write_str("*"),
|
||||
Expr::QualifiedWildcard(prefix, _) => write!(f, "{}.*", prefix),
|
||||
Expr::QualifiedWildcard(prefix, _) => write!(f, "{prefix}.*"),
|
||||
Expr::CompoundIdentifier(s) => write!(f, "{}", display_separated(s, ".")),
|
||||
Expr::CompoundFieldAccess { root, access_chain } => {
|
||||
write!(f, "{}", root)?;
|
||||
write!(f, "{root}")?;
|
||||
for field in access_chain {
|
||||
write!(f, "{}", field)?;
|
||||
write!(f, "{field}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1516,7 +1549,7 @@ impl fmt::Display for Expr {
|
|||
} => {
|
||||
let not_ = if *negated { "NOT " } else { "" };
|
||||
if form.is_none() {
|
||||
write!(f, "{} IS {}NORMALIZED", expr, not_)
|
||||
write!(f, "{expr} IS {not_}NORMALIZED")
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
|
@ -1838,7 +1871,7 @@ impl fmt::Display for Expr {
|
|||
}
|
||||
}
|
||||
Expr::Named { expr, name } => {
|
||||
write!(f, "{} AS {}", expr, name)
|
||||
write!(f, "{expr} AS {name}")
|
||||
}
|
||||
Expr::Dictionary(fields) => {
|
||||
write!(f, "{{{}}}", display_comma_separated(fields))
|
||||
|
@ -1881,6 +1914,7 @@ impl fmt::Display for Expr {
|
|||
}
|
||||
Expr::Prior(expr) => write!(f, "PRIOR {expr}"),
|
||||
Expr::Lambda(lambda) => write!(f, "{lambda}"),
|
||||
Expr::MemberOf(member_of) => write!(f, "{member_of}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2394,7 +2428,7 @@ impl fmt::Display for ConditionalStatements {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
ConditionalStatements::BeginEnd(bes) => write!(f, "{}", bes),
|
||||
ConditionalStatements::BeginEnd(bes) => write!(f, "{bes}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2914,9 +2948,7 @@ impl Display for Set {
|
|||
write!(
|
||||
f,
|
||||
"SET {modifier}ROLE {role_name}",
|
||||
modifier = context_modifier
|
||||
.map(|m| format!("{}", m))
|
||||
.unwrap_or_default()
|
||||
modifier = context_modifier.map(|m| format!("{m}")).unwrap_or_default()
|
||||
)
|
||||
}
|
||||
Self::SetSessionParam(kind) => write!(f, "SET {kind}"),
|
||||
|
@ -2949,7 +2981,7 @@ impl Display for Set {
|
|||
charset_name,
|
||||
collation_name,
|
||||
} => {
|
||||
write!(f, "SET NAMES {}", charset_name)?;
|
||||
write!(f, "SET NAMES {charset_name}")?;
|
||||
|
||||
if let Some(collation) = collation_name {
|
||||
f.write_str(" COLLATE ")?;
|
||||
|
@ -2972,7 +3004,7 @@ impl Display for Set {
|
|||
write!(
|
||||
f,
|
||||
"SET {}{}{} = {}",
|
||||
scope.map(|s| format!("{}", s)).unwrap_or_default(),
|
||||
scope.map(|s| format!("{s}")).unwrap_or_default(),
|
||||
if *hivevar { "HIVEVAR:" } else { "" },
|
||||
variable,
|
||||
display_comma_separated(values)
|
||||
|
@ -2990,6 +3022,36 @@ impl From<Set> for Statement {
|
|||
}
|
||||
}
|
||||
|
||||
/// A representation of a `WHEN` arm with all the identifiers catched and the statements to execute
|
||||
/// for the arm.
|
||||
///
|
||||
/// Snowflake: <https://docs.snowflake.com/en/sql-reference/snowflake-scripting/exception>
|
||||
/// BigQuery: <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#beginexceptionend>
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub struct ExceptionWhen {
|
||||
pub idents: Vec<Ident>,
|
||||
pub statements: Vec<Statement>,
|
||||
}
|
||||
|
||||
impl Display for ExceptionWhen {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"WHEN {idents} THEN",
|
||||
idents = display_separated(&self.idents, " OR ")
|
||||
)?;
|
||||
|
||||
if !self.statements.is_empty() {
|
||||
write!(f, " ")?;
|
||||
format_statement_list(f, &self.statements)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A top-level statement (SELECT, INSERT, CREATE, etc.)
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
|
@ -3256,6 +3318,8 @@ pub enum Statement {
|
|||
secret_type: Ident,
|
||||
options: Vec<SecretOption>,
|
||||
},
|
||||
/// A `CREATE SERVER` statement.
|
||||
CreateServer(CreateServerStatement),
|
||||
/// ```sql
|
||||
/// CREATE POLICY
|
||||
/// ```
|
||||
|
@ -3678,17 +3742,20 @@ pub enum Statement {
|
|||
/// END;
|
||||
/// ```
|
||||
statements: Vec<Statement>,
|
||||
/// Statements of an exception clause.
|
||||
/// Exception handling with exception clauses.
|
||||
/// Example:
|
||||
/// ```sql
|
||||
/// BEGIN
|
||||
/// SELECT 1;
|
||||
/// EXCEPTION WHEN ERROR THEN
|
||||
/// SELECT 2;
|
||||
/// SELECT 3;
|
||||
/// END;
|
||||
/// EXCEPTION
|
||||
/// WHEN EXCEPTION_1 THEN
|
||||
/// SELECT 2;
|
||||
/// WHEN EXCEPTION_2 OR EXCEPTION_3 THEN
|
||||
/// SELECT 3;
|
||||
/// WHEN OTHER THEN
|
||||
/// SELECT 4;
|
||||
/// ```
|
||||
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#beginexceptionend>
|
||||
exception_statements: Option<Vec<Statement>>,
|
||||
/// <https://docs.snowflake.com/en/sql-reference/snowflake-scripting/exception>
|
||||
exception: Option<Vec<ExceptionWhen>>,
|
||||
/// TRUE if the statement has an `END` keyword.
|
||||
has_end_keyword: bool,
|
||||
},
|
||||
|
@ -3881,6 +3948,7 @@ pub enum Statement {
|
|||
or_alter: bool,
|
||||
name: ObjectName,
|
||||
params: Option<Vec<ProcedureParam>>,
|
||||
language: Option<Ident>,
|
||||
body: ConditionalStatements,
|
||||
},
|
||||
/// ```sql
|
||||
|
@ -4181,7 +4249,7 @@ pub enum Statement {
|
|||
/// ```sql
|
||||
/// NOTIFY channel [ , payload ]
|
||||
/// ```
|
||||
/// send a notification event together with an optional “payload” string to channel
|
||||
/// send a notification event together with an optional "payload" string to channel
|
||||
///
|
||||
/// See Postgres <https://www.postgresql.org/docs/current/sql-notify.html>
|
||||
NOTIFY {
|
||||
|
@ -4340,7 +4408,7 @@ impl fmt::Display for Statement {
|
|||
write!(f, "{describe_alias} ")?;
|
||||
|
||||
if let Some(format) = hive_format {
|
||||
write!(f, "{} ", format)?;
|
||||
write!(f, "{format} ")?;
|
||||
}
|
||||
if *has_table_keyword {
|
||||
write!(f, "TABLE ")?;
|
||||
|
@ -4784,6 +4852,7 @@ impl fmt::Display for Statement {
|
|||
name,
|
||||
or_alter,
|
||||
params,
|
||||
language,
|
||||
body,
|
||||
} => {
|
||||
write!(
|
||||
|
@ -4799,6 +4868,10 @@ impl fmt::Display for Statement {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(language) = language {
|
||||
write!(f, " LANGUAGE {language}")?;
|
||||
}
|
||||
|
||||
write!(f, " AS {body}")
|
||||
}
|
||||
Statement::CreateMacro {
|
||||
|
@ -5107,6 +5180,9 @@ impl fmt::Display for Statement {
|
|||
write!(f, " )")?;
|
||||
Ok(())
|
||||
}
|
||||
Statement::CreateServer(stmt) => {
|
||||
write!(f, "{stmt}")
|
||||
}
|
||||
Statement::CreatePolicy {
|
||||
name,
|
||||
table_name,
|
||||
|
@ -5171,7 +5247,7 @@ impl fmt::Display for Statement {
|
|||
if *only {
|
||||
write!(f, "ONLY ")?;
|
||||
}
|
||||
write!(f, "{name} ", name = name)?;
|
||||
write!(f, "{name} ")?;
|
||||
if let Some(cluster) = on_cluster {
|
||||
write!(f, "ON CLUSTER {cluster} ")?;
|
||||
}
|
||||
|
@ -5249,7 +5325,7 @@ impl fmt::Display for Statement {
|
|||
)?;
|
||||
if !session_params.options.is_empty() {
|
||||
if *set {
|
||||
write!(f, " {}", session_params)?;
|
||||
write!(f, " {session_params}")?;
|
||||
} else {
|
||||
let options = session_params
|
||||
.options
|
||||
|
@ -5283,7 +5359,7 @@ impl fmt::Display for Statement {
|
|||
if *purge { " PURGE" } else { "" },
|
||||
)?;
|
||||
if let Some(table_name) = table.as_ref() {
|
||||
write!(f, " ON {}", table_name)?;
|
||||
write!(f, " ON {table_name}")?;
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
@ -5533,12 +5609,12 @@ impl fmt::Display for Statement {
|
|||
transaction,
|
||||
modifier,
|
||||
statements,
|
||||
exception_statements,
|
||||
exception,
|
||||
has_end_keyword,
|
||||
} => {
|
||||
if *syntax_begin {
|
||||
if let Some(modifier) = *modifier {
|
||||
write!(f, "BEGIN {}", modifier)?;
|
||||
write!(f, "BEGIN {modifier}")?;
|
||||
} else {
|
||||
write!(f, "BEGIN")?;
|
||||
}
|
||||
|
@ -5555,11 +5631,10 @@ impl fmt::Display for Statement {
|
|||
write!(f, " ")?;
|
||||
format_statement_list(f, statements)?;
|
||||
}
|
||||
if let Some(exception_statements) = exception_statements {
|
||||
write!(f, " EXCEPTION WHEN ERROR THEN")?;
|
||||
if !exception_statements.is_empty() {
|
||||
write!(f, " ")?;
|
||||
format_statement_list(f, exception_statements)?;
|
||||
if let Some(exception_when) = exception {
|
||||
write!(f, " EXCEPTION")?;
|
||||
for when in exception_when {
|
||||
write!(f, " {when}")?;
|
||||
}
|
||||
}
|
||||
if *has_end_keyword {
|
||||
|
@ -5575,7 +5650,7 @@ impl fmt::Display for Statement {
|
|||
if *end_syntax {
|
||||
write!(f, "END")?;
|
||||
if let Some(modifier) = *modifier {
|
||||
write!(f, " {}", modifier)?;
|
||||
write!(f, " {modifier}")?;
|
||||
}
|
||||
if *chain {
|
||||
write!(f, " AND CHAIN")?;
|
||||
|
@ -5674,7 +5749,7 @@ impl fmt::Display for Statement {
|
|||
write!(f, " GRANTED BY {grantor}")?;
|
||||
}
|
||||
if let Some(cascade) = cascade {
|
||||
write!(f, " {}", cascade)?;
|
||||
write!(f, " {cascade}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -5853,13 +5928,13 @@ impl fmt::Display for Statement {
|
|||
if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" },
|
||||
)?;
|
||||
if !directory_table_params.options.is_empty() {
|
||||
write!(f, " DIRECTORY=({})", directory_table_params)?;
|
||||
write!(f, " DIRECTORY=({directory_table_params})")?;
|
||||
}
|
||||
if !file_format.options.is_empty() {
|
||||
write!(f, " FILE_FORMAT=({})", file_format)?;
|
||||
write!(f, " FILE_FORMAT=({file_format})")?;
|
||||
}
|
||||
if !copy_options.options.is_empty() {
|
||||
write!(f, " COPY_OPTIONS=({})", copy_options)?;
|
||||
write!(f, " COPY_OPTIONS=({copy_options})")?;
|
||||
}
|
||||
if comment.is_some() {
|
||||
write!(f, " COMMENT='{}'", comment.as_ref().unwrap())?;
|
||||
|
@ -5882,7 +5957,7 @@ impl fmt::Display for Statement {
|
|||
validation_mode,
|
||||
partition,
|
||||
} => {
|
||||
write!(f, "COPY INTO {}", into)?;
|
||||
write!(f, "COPY INTO {into}")?;
|
||||
if let Some(into_columns) = into_columns {
|
||||
write!(f, " ({})", display_comma_separated(into_columns))?;
|
||||
}
|
||||
|
@ -5898,12 +5973,12 @@ impl fmt::Display for Statement {
|
|||
)?;
|
||||
}
|
||||
if let Some(from_obj_alias) = from_obj_alias {
|
||||
write!(f, " AS {}", from_obj_alias)?;
|
||||
write!(f, " AS {from_obj_alias}")?;
|
||||
}
|
||||
write!(f, ")")?;
|
||||
} else if let Some(from_obj) = from_obj {
|
||||
// Standard data load
|
||||
write!(f, " FROM {}{}", from_obj, stage_params)?;
|
||||
write!(f, " FROM {from_obj}{stage_params}")?;
|
||||
if let Some(from_obj_alias) = from_obj_alias {
|
||||
write!(f, " AS {from_obj_alias}")?;
|
||||
}
|
||||
|
@ -5916,24 +5991,24 @@ impl fmt::Display for Statement {
|
|||
write!(f, " FILES = ('{}')", display_separated(files, "', '"))?;
|
||||
}
|
||||
if let Some(pattern) = pattern {
|
||||
write!(f, " PATTERN = '{}'", pattern)?;
|
||||
write!(f, " PATTERN = '{pattern}'")?;
|
||||
}
|
||||
if let Some(partition) = partition {
|
||||
write!(f, " PARTITION BY {partition}")?;
|
||||
}
|
||||
if !file_format.options.is_empty() {
|
||||
write!(f, " FILE_FORMAT=({})", file_format)?;
|
||||
write!(f, " FILE_FORMAT=({file_format})")?;
|
||||
}
|
||||
if !copy_options.options.is_empty() {
|
||||
match kind {
|
||||
CopyIntoSnowflakeKind::Table => {
|
||||
write!(f, " COPY_OPTIONS=({})", copy_options)?
|
||||
write!(f, " COPY_OPTIONS=({copy_options})")?
|
||||
}
|
||||
CopyIntoSnowflakeKind::Location => write!(f, " {copy_options}")?,
|
||||
}
|
||||
}
|
||||
if let Some(validation_mode) = validation_mode {
|
||||
write!(f, " VALIDATION_MODE = {}", validation_mode)?;
|
||||
write!(f, " VALIDATION_MODE = {validation_mode}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -5979,10 +6054,10 @@ impl fmt::Display for Statement {
|
|||
} => {
|
||||
write!(f, "OPTIMIZE TABLE {name}")?;
|
||||
if let Some(on_cluster) = on_cluster {
|
||||
write!(f, " ON CLUSTER {on_cluster}", on_cluster = on_cluster)?;
|
||||
write!(f, " ON CLUSTER {on_cluster}")?;
|
||||
}
|
||||
if let Some(partition) = partition {
|
||||
write!(f, " {partition}", partition = partition)?;
|
||||
write!(f, " {partition}")?;
|
||||
}
|
||||
if *include_final {
|
||||
write!(f, " FINAL")?;
|
||||
|
@ -6109,7 +6184,7 @@ impl fmt::Display for SetAssignment {
|
|||
write!(
|
||||
f,
|
||||
"{}{} = {}",
|
||||
self.scope.map(|s| format!("{}", s)).unwrap_or_default(),
|
||||
self.scope.map(|s| format!("{s}")).unwrap_or_default(),
|
||||
self.name,
|
||||
self.value
|
||||
)
|
||||
|
@ -6838,7 +6913,7 @@ impl fmt::Display for GranteeName {
|
|||
match self {
|
||||
GranteeName::ObjectName(name) => name.fmt(f),
|
||||
GranteeName::UserHost { user, host } => {
|
||||
write!(f, "{}@{}", user, host)
|
||||
write!(f, "{user}@{host}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6853,6 +6928,12 @@ pub enum GrantObjects {
|
|||
AllSequencesInSchema { schemas: Vec<ObjectName> },
|
||||
/// Grant privileges on `ALL TABLES IN SCHEMA <schema_name> [, ...]`
|
||||
AllTablesInSchema { schemas: Vec<ObjectName> },
|
||||
/// Grant privileges on `FUTURE SCHEMAS IN DATABASE <database_name> [, ...]`
|
||||
FutureSchemasInDatabase { databases: Vec<ObjectName> },
|
||||
/// Grant privileges on `FUTURE TABLES IN SCHEMA <schema_name> [, ...]`
|
||||
FutureTablesInSchema { schemas: Vec<ObjectName> },
|
||||
/// Grant privileges on `FUTURE VIEWS IN SCHEMA <schema_name> [, ...]`
|
||||
FutureViewsInSchema { schemas: Vec<ObjectName> },
|
||||
/// Grant privileges on specific databases
|
||||
Databases(Vec<ObjectName>),
|
||||
/// Grant privileges on specific schemas
|
||||
|
@ -6921,6 +7002,27 @@ impl fmt::Display for GrantObjects {
|
|||
display_comma_separated(schemas)
|
||||
)
|
||||
}
|
||||
GrantObjects::FutureSchemasInDatabase { databases } => {
|
||||
write!(
|
||||
f,
|
||||
"FUTURE SCHEMAS IN DATABASE {}",
|
||||
display_comma_separated(databases)
|
||||
)
|
||||
}
|
||||
GrantObjects::FutureTablesInSchema { schemas } => {
|
||||
write!(
|
||||
f,
|
||||
"FUTURE TABLES IN SCHEMA {}",
|
||||
display_comma_separated(schemas)
|
||||
)
|
||||
}
|
||||
GrantObjects::FutureViewsInSchema { schemas } => {
|
||||
write!(
|
||||
f,
|
||||
"FUTURE VIEWS IN SCHEMA {}",
|
||||
display_comma_separated(schemas)
|
||||
)
|
||||
}
|
||||
GrantObjects::ResourceMonitors(objects) => {
|
||||
write!(f, "RESOURCE MONITOR {}", display_comma_separated(objects))
|
||||
}
|
||||
|
@ -7008,7 +7110,7 @@ pub enum AssignmentTarget {
|
|||
impl fmt::Display for AssignmentTarget {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
AssignmentTarget::ColumnName(column) => write!(f, "{}", column),
|
||||
AssignmentTarget::ColumnName(column) => write!(f, "{column}"),
|
||||
AssignmentTarget::Tuple(columns) => write!(f, "({})", display_comma_separated(columns)),
|
||||
}
|
||||
}
|
||||
|
@ -7253,8 +7355,8 @@ impl fmt::Display for FunctionArguments {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
FunctionArguments::None => Ok(()),
|
||||
FunctionArguments::Subquery(query) => write!(f, "({})", query),
|
||||
FunctionArguments::List(args) => write!(f, "({})", args),
|
||||
FunctionArguments::Subquery(query) => write!(f, "({query})"),
|
||||
FunctionArguments::List(args) => write!(f, "({args})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7275,7 +7377,7 @@ pub struct FunctionArgumentList {
|
|||
impl fmt::Display for FunctionArgumentList {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if let Some(duplicate_treatment) = self.duplicate_treatment {
|
||||
write!(f, "{} ", duplicate_treatment)?;
|
||||
write!(f, "{duplicate_treatment} ")?;
|
||||
}
|
||||
write!(f, "{}", display_comma_separated(&self.args))?;
|
||||
if !self.clauses.is_empty() {
|
||||
|
@ -7335,7 +7437,7 @@ impl fmt::Display for FunctionArgumentClause {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
FunctionArgumentClause::IgnoreOrRespectNulls(null_treatment) => {
|
||||
write!(f, "{}", null_treatment)
|
||||
write!(f, "{null_treatment}")
|
||||
}
|
||||
FunctionArgumentClause::OrderBy(order_by) => {
|
||||
write!(f, "ORDER BY {}", display_comma_separated(order_by))
|
||||
|
@ -7791,12 +7893,12 @@ pub enum SqlOption {
|
|||
impl fmt::Display for SqlOption {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
SqlOption::Clustered(c) => write!(f, "{}", c),
|
||||
SqlOption::Clustered(c) => write!(f, "{c}"),
|
||||
SqlOption::Ident(ident) => {
|
||||
write!(f, "{}", ident)
|
||||
write!(f, "{ident}")
|
||||
}
|
||||
SqlOption::KeyValue { key: name, value } => {
|
||||
write!(f, "{} = {}", name, value)
|
||||
write!(f, "{name} = {value}")
|
||||
}
|
||||
SqlOption::Partition {
|
||||
column_name,
|
||||
|
@ -7836,7 +7938,7 @@ impl fmt::Display for SqlOption {
|
|||
SqlOption::NamedParenthesizedList(value) => {
|
||||
write!(f, "{} = ", value.key)?;
|
||||
if let Some(key) = &value.name {
|
||||
write!(f, "{}", key)?;
|
||||
write!(f, "{key}")?;
|
||||
}
|
||||
if !value.values.is_empty() {
|
||||
write!(f, "({})", display_comma_separated(&value.values))?
|
||||
|
@ -7879,6 +7981,70 @@ impl fmt::Display for SecretOption {
|
|||
}
|
||||
}
|
||||
|
||||
/// A `CREATE SERVER` statement.
|
||||
///
|
||||
/// [PostgreSQL Documentation](https://www.postgresql.org/docs/current/sql-createserver.html)
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub struct CreateServerStatement {
|
||||
pub name: ObjectName,
|
||||
pub if_not_exists: bool,
|
||||
pub server_type: Option<Ident>,
|
||||
pub version: Option<Ident>,
|
||||
pub foreign_data_wrapper: ObjectName,
|
||||
pub options: Option<Vec<CreateServerOption>>,
|
||||
}
|
||||
|
||||
impl fmt::Display for CreateServerStatement {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let CreateServerStatement {
|
||||
name,
|
||||
if_not_exists,
|
||||
server_type,
|
||||
version,
|
||||
foreign_data_wrapper,
|
||||
options,
|
||||
} = self;
|
||||
|
||||
write!(
|
||||
f,
|
||||
"CREATE SERVER {if_not_exists}{name} ",
|
||||
if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" },
|
||||
)?;
|
||||
|
||||
if let Some(st) = server_type {
|
||||
write!(f, "TYPE {st} ")?;
|
||||
}
|
||||
|
||||
if let Some(v) = version {
|
||||
write!(f, "VERSION {v} ")?;
|
||||
}
|
||||
|
||||
write!(f, "FOREIGN DATA WRAPPER {foreign_data_wrapper}")?;
|
||||
|
||||
if let Some(o) = options {
|
||||
write!(f, " OPTIONS ({o})", o = display_comma_separated(o))?;
|
||||
}
|
||||
|
||||
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 CreateServerOption {
|
||||
pub key: Ident,
|
||||
pub value: Ident,
|
||||
}
|
||||
|
||||
impl fmt::Display for CreateServerOption {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{} {}", self.key, self.value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
|
@ -7893,7 +8059,7 @@ impl fmt::Display for AttachDuckDBDatabaseOption {
|
|||
AttachDuckDBDatabaseOption::ReadOnly(Some(true)) => write!(f, "READ_ONLY true"),
|
||||
AttachDuckDBDatabaseOption::ReadOnly(Some(false)) => write!(f, "READ_ONLY false"),
|
||||
AttachDuckDBDatabaseOption::ReadOnly(None) => write!(f, "READ_ONLY"),
|
||||
AttachDuckDBDatabaseOption::Type(t) => write!(f, "TYPE {}", t),
|
||||
AttachDuckDBDatabaseOption::Type(t) => write!(f, "TYPE {t}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9416,10 +9582,10 @@ impl fmt::Display for ShowStatementIn {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.clause)?;
|
||||
if let Some(parent_type) = &self.parent_type {
|
||||
write!(f, " {}", parent_type)?;
|
||||
write!(f, " {parent_type}")?;
|
||||
}
|
||||
if let Some(parent_name) = &self.parent_name {
|
||||
write!(f, " {}", parent_name)?;
|
||||
write!(f, " {parent_name}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -9500,7 +9666,7 @@ impl fmt::Display for TableObject {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::TableName(table_name) => write!(f, "{table_name}"),
|
||||
Self::TableFunction(func) => write!(f, "FUNCTION {}", func),
|
||||
Self::TableFunction(func) => write!(f, "FUNCTION {func}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9688,7 +9854,7 @@ pub struct ReturnStatement {
|
|||
impl fmt::Display for ReturnStatement {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match &self.value {
|
||||
Some(ReturnStatementValue::Expr(expr)) => write!(f, "RETURN {}", expr),
|
||||
Some(ReturnStatementValue::Expr(expr)) => write!(f, "RETURN {expr}"),
|
||||
None => write!(f, "RETURN"),
|
||||
}
|
||||
}
|
||||
|
@ -9737,8 +9903,31 @@ impl fmt::Display for NullInclusion {
|
|||
}
|
||||
}
|
||||
|
||||
/// Checks membership of a value in a JSON array
|
||||
///
|
||||
/// Syntax:
|
||||
/// ```sql
|
||||
/// <value> MEMBER OF(<array>)
|
||||
/// ```
|
||||
/// [MySQL](https://dev.mysql.com/doc/refman/8.4/en/json-search-functions.html#operator_member-of)
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub struct MemberOf {
|
||||
pub value: Box<Expr>,
|
||||
pub array: Box<Expr>,
|
||||
}
|
||||
|
||||
impl fmt::Display for MemberOf {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{} MEMBER OF({})", self.value, self.array)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tokenizer::Location;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
@ -10034,4 +10223,16 @@ mod tests {
|
|||
test_steps(OneOrManyWithParens::Many(vec![2]), vec![2], 3);
|
||||
test_steps(OneOrManyWithParens::Many(vec![3, 4]), vec![3, 4], 4);
|
||||
}
|
||||
|
||||
// Tests that the position in the code of an `Ident` does not affect its
|
||||
// ordering.
|
||||
#[test]
|
||||
fn test_ident_ord() {
|
||||
let mut a = Ident::with_span(Span::new(Location::new(1, 1), Location::new(1, 1)), "a");
|
||||
let mut b = Ident::with_span(Span::new(Location::new(2, 2), Location::new(2, 2)), "b");
|
||||
|
||||
assert!(a < b);
|
||||
std::mem::swap(&mut a.span, &mut b.span);
|
||||
assert!(a < b);
|
||||
}
|
||||
}
|
||||
|
|
231
src/ast/query.rs
231
src/ast/query.rs
|
@ -1047,7 +1047,7 @@ impl fmt::Display for ConnectBy {
|
|||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub struct Setting {
|
||||
pub key: Ident,
|
||||
pub value: Value,
|
||||
pub value: Expr,
|
||||
}
|
||||
|
||||
impl fmt::Display for Setting {
|
||||
|
@ -1183,7 +1183,7 @@ impl fmt::Display for TableIndexHints {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{} {} ", self.hint_type, self.index_type)?;
|
||||
if let Some(for_clause) = &self.for_clause {
|
||||
write!(f, "FOR {} ", for_clause)?;
|
||||
write!(f, "FOR {for_clause} ")?;
|
||||
}
|
||||
write!(f, "({})", display_comma_separated(&self.index_names))
|
||||
}
|
||||
|
@ -1459,7 +1459,7 @@ impl fmt::Display for TableSampleQuantity {
|
|||
}
|
||||
write!(f, "{}", self.value)?;
|
||||
if let Some(unit) = &self.unit {
|
||||
write!(f, " {}", unit)?;
|
||||
write!(f, " {unit}")?;
|
||||
}
|
||||
if self.parenthesized {
|
||||
write!(f, ")")?;
|
||||
|
@ -1552,7 +1552,7 @@ impl fmt::Display for TableSampleBucket {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "BUCKET {} OUT OF {}", self.bucket, self.total)?;
|
||||
if let Some(on) = &self.on {
|
||||
write!(f, " ON {}", on)?;
|
||||
write!(f, " ON {on}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1561,19 +1561,19 @@ impl fmt::Display for TableSample {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.modifier)?;
|
||||
if let Some(name) = &self.name {
|
||||
write!(f, " {}", name)?;
|
||||
write!(f, " {name}")?;
|
||||
}
|
||||
if let Some(quantity) = &self.quantity {
|
||||
write!(f, " {}", quantity)?;
|
||||
write!(f, " {quantity}")?;
|
||||
}
|
||||
if let Some(seed) = &self.seed {
|
||||
write!(f, " {}", seed)?;
|
||||
write!(f, " {seed}")?;
|
||||
}
|
||||
if let Some(bucket) = &self.bucket {
|
||||
write!(f, " ({})", bucket)?;
|
||||
write!(f, " ({bucket})")?;
|
||||
}
|
||||
if let Some(offset) = &self.offset {
|
||||
write!(f, " OFFSET {}", offset)?;
|
||||
write!(f, " OFFSET {offset}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1651,7 +1651,7 @@ impl fmt::Display for RowsPerMatch {
|
|||
RowsPerMatch::AllRows(mode) => {
|
||||
write!(f, "ALL ROWS PER MATCH")?;
|
||||
if let Some(mode) = mode {
|
||||
write!(f, " {}", mode)?;
|
||||
write!(f, " {mode}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1777,7 +1777,7 @@ impl fmt::Display for MatchRecognizePattern {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use MatchRecognizePattern::*;
|
||||
match self {
|
||||
Symbol(symbol) => write!(f, "{}", symbol),
|
||||
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, " ")),
|
||||
|
@ -2148,7 +2148,7 @@ 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)?;
|
||||
write!(f, " {data_type}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2398,7 +2398,7 @@ impl fmt::Display for OrderBy {
|
|||
write!(f, " {}", display_comma_separated(exprs))?;
|
||||
}
|
||||
OrderByKind::All(all) => {
|
||||
write!(f, " ALL{}", all)?;
|
||||
write!(f, " ALL{all}")?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2429,7 +2429,7 @@ impl fmt::Display for OrderByExpr {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}{}", self.expr, self.options)?;
|
||||
if let Some(ref with_fill) = self.with_fill {
|
||||
write!(f, " {}", with_fill)?
|
||||
write!(f, " {with_fill}")?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2452,13 +2452,13 @@ 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)?;
|
||||
write!(f, " FROM {from}")?;
|
||||
}
|
||||
if let Some(ref to) = self.to {
|
||||
write!(f, " TO {}", to)?;
|
||||
write!(f, " TO {to}")?;
|
||||
}
|
||||
if let Some(ref step) = self.step {
|
||||
write!(f, " STEP {}", step)?;
|
||||
write!(f, " STEP {step}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2487,7 +2487,7 @@ 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)?;
|
||||
write!(f, " AS {expr}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2565,7 +2565,7 @@ impl fmt::Display for LimitClause {
|
|||
Ok(())
|
||||
}
|
||||
LimitClause::OffsetCommaLimit { offset, limit } => {
|
||||
write!(f, " LIMIT {}, {}", offset, limit)
|
||||
write!(f, " LIMIT {offset}, {limit}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2684,6 +2684,79 @@ pub enum PipeOperator {
|
|||
/// Syntax: `|> TABLESAMPLE SYSTEM (10 PERCENT)
|
||||
/// See more at <https://cloud.google.com/bigquery/docs/reference/standard-sql/pipe-syntax#tablesample_pipe_operator>
|
||||
TableSample { sample: Box<TableSample> },
|
||||
/// Renames columns in the input table.
|
||||
///
|
||||
/// Syntax: `|> RENAME old_name AS new_name, ...`
|
||||
///
|
||||
/// See more at <https://cloud.google.com/bigquery/docs/reference/standard-sql/pipe-syntax#rename_pipe_operator>
|
||||
Rename { mappings: Vec<IdentWithAlias> },
|
||||
/// Combines the input table with one or more tables using UNION.
|
||||
///
|
||||
/// Syntax: `|> UNION [ALL|DISTINCT] (<query>), (<query>), ...`
|
||||
///
|
||||
/// See more at <https://cloud.google.com/bigquery/docs/reference/standard-sql/pipe-syntax#union_pipe_operator>
|
||||
Union {
|
||||
set_quantifier: SetQuantifier,
|
||||
queries: Vec<Query>,
|
||||
},
|
||||
/// Returns only the rows that are present in both the input table and the specified tables.
|
||||
///
|
||||
/// Syntax: `|> INTERSECT [DISTINCT] (<query>), (<query>), ...`
|
||||
///
|
||||
/// See more at <https://cloud.google.com/bigquery/docs/reference/standard-sql/pipe-syntax#intersect_pipe_operator>
|
||||
Intersect {
|
||||
set_quantifier: SetQuantifier,
|
||||
queries: Vec<Query>,
|
||||
},
|
||||
/// Returns only the rows that are present in the input table but not in the specified tables.
|
||||
///
|
||||
/// Syntax: `|> EXCEPT DISTINCT (<query>), (<query>), ...`
|
||||
///
|
||||
/// See more at <https://cloud.google.com/bigquery/docs/reference/standard-sql/pipe-syntax#except_pipe_operator>
|
||||
Except {
|
||||
set_quantifier: SetQuantifier,
|
||||
queries: Vec<Query>,
|
||||
},
|
||||
/// Calls a table function or procedure that returns a table.
|
||||
///
|
||||
/// Syntax: `|> CALL function_name(args) [AS alias]`
|
||||
///
|
||||
/// See more at <https://cloud.google.com/bigquery/docs/reference/standard-sql/pipe-syntax#call_pipe_operator>
|
||||
Call {
|
||||
function: Function,
|
||||
alias: Option<Ident>,
|
||||
},
|
||||
/// Pivots data from rows to columns.
|
||||
///
|
||||
/// Syntax: `|> PIVOT(aggregate_function(column) FOR pivot_column IN (value1, value2, ...)) [AS alias]`
|
||||
///
|
||||
/// See more at <https://cloud.google.com/bigquery/docs/reference/standard-sql/pipe-syntax#pivot_pipe_operator>
|
||||
Pivot {
|
||||
aggregate_functions: Vec<ExprWithAlias>,
|
||||
value_column: Vec<Ident>,
|
||||
value_source: PivotValueSource,
|
||||
alias: Option<Ident>,
|
||||
},
|
||||
/// The `UNPIVOT` pipe operator transforms columns into rows.
|
||||
///
|
||||
/// Syntax:
|
||||
/// ```sql
|
||||
/// |> UNPIVOT(value_column FOR name_column IN (column1, column2, ...)) [alias]
|
||||
/// ```
|
||||
///
|
||||
/// See more at <https://cloud.google.com/bigquery/docs/reference/standard-sql/pipe-syntax#unpivot_pipe_operator>
|
||||
Unpivot {
|
||||
value_column: Ident,
|
||||
name_column: Ident,
|
||||
unpivot_columns: Vec<Ident>,
|
||||
alias: Option<Ident>,
|
||||
},
|
||||
/// Joins the input table with another table.
|
||||
///
|
||||
/// Syntax: `|> [JOIN_TYPE] JOIN <table> [alias] ON <condition>` or `|> [JOIN_TYPE] JOIN <table> [alias] USING (<columns>)`
|
||||
///
|
||||
/// See more at <https://cloud.google.com/bigquery/docs/reference/standard-sql/pipe-syntax#join_pipe_operator>
|
||||
Join(Join),
|
||||
}
|
||||
|
||||
impl fmt::Display for PipeOperator {
|
||||
|
@ -2702,12 +2775,12 @@ impl fmt::Display for PipeOperator {
|
|||
write!(f, "DROP {}", display_comma_separated(columns.as_slice()))
|
||||
}
|
||||
PipeOperator::As { alias } => {
|
||||
write!(f, "AS {}", alias)
|
||||
write!(f, "AS {alias}")
|
||||
}
|
||||
PipeOperator::Limit { expr, offset } => {
|
||||
write!(f, "LIMIT {}", expr)?;
|
||||
write!(f, "LIMIT {expr}")?;
|
||||
if let Some(offset) = offset {
|
||||
write!(f, " OFFSET {}", offset)?;
|
||||
write!(f, " OFFSET {offset}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2730,16 +2803,96 @@ impl fmt::Display for PipeOperator {
|
|||
}
|
||||
|
||||
PipeOperator::Where { expr } => {
|
||||
write!(f, "WHERE {}", expr)
|
||||
write!(f, "WHERE {expr}")
|
||||
}
|
||||
PipeOperator::OrderBy { exprs } => {
|
||||
write!(f, "ORDER BY {}", display_comma_separated(exprs.as_slice()))
|
||||
}
|
||||
|
||||
PipeOperator::TableSample { sample } => {
|
||||
write!(f, "{}", sample)
|
||||
write!(f, "{sample}")
|
||||
}
|
||||
PipeOperator::Rename { mappings } => {
|
||||
write!(f, "RENAME {}", display_comma_separated(mappings))
|
||||
}
|
||||
PipeOperator::Union {
|
||||
set_quantifier,
|
||||
queries,
|
||||
} => Self::fmt_set_operation(f, "UNION", set_quantifier, queries),
|
||||
PipeOperator::Intersect {
|
||||
set_quantifier,
|
||||
queries,
|
||||
} => Self::fmt_set_operation(f, "INTERSECT", set_quantifier, queries),
|
||||
PipeOperator::Except {
|
||||
set_quantifier,
|
||||
queries,
|
||||
} => Self::fmt_set_operation(f, "EXCEPT", set_quantifier, queries),
|
||||
PipeOperator::Call { function, alias } => {
|
||||
write!(f, "CALL {function}")?;
|
||||
Self::fmt_optional_alias(f, alias)
|
||||
}
|
||||
PipeOperator::Pivot {
|
||||
aggregate_functions,
|
||||
value_column,
|
||||
value_source,
|
||||
alias,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"PIVOT({} FOR {} IN ({}))",
|
||||
display_comma_separated(aggregate_functions),
|
||||
Expr::CompoundIdentifier(value_column.to_vec()),
|
||||
value_source
|
||||
)?;
|
||||
Self::fmt_optional_alias(f, alias)
|
||||
}
|
||||
PipeOperator::Unpivot {
|
||||
value_column,
|
||||
name_column,
|
||||
unpivot_columns,
|
||||
alias,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"UNPIVOT({} FOR {} IN ({}))",
|
||||
value_column,
|
||||
name_column,
|
||||
display_comma_separated(unpivot_columns)
|
||||
)?;
|
||||
Self::fmt_optional_alias(f, alias)
|
||||
}
|
||||
PipeOperator::Join(join) => write!(f, "{join}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PipeOperator {
|
||||
/// Helper function to format optional alias for pipe operators
|
||||
fn fmt_optional_alias(f: &mut fmt::Formatter<'_>, alias: &Option<Ident>) -> fmt::Result {
|
||||
if let Some(alias) = alias {
|
||||
write!(f, " AS {alias}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Helper function to format set operations (UNION, INTERSECT, EXCEPT) with queries
|
||||
fn fmt_set_operation(
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
operation: &str,
|
||||
set_quantifier: &SetQuantifier,
|
||||
queries: &[Query],
|
||||
) -> fmt::Result {
|
||||
write!(f, "{operation}")?;
|
||||
match set_quantifier {
|
||||
SetQuantifier::None => {}
|
||||
_ => {
|
||||
write!(f, " {set_quantifier}")?;
|
||||
}
|
||||
}
|
||||
write!(f, " ")?;
|
||||
let parenthesized_queries: Vec<String> =
|
||||
queries.iter().map(|query| format!("({query})")).collect();
|
||||
write!(f, "{}", display_comma_separated(&parenthesized_queries))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3016,7 +3169,7 @@ pub enum FormatClause {
|
|||
impl fmt::Display for FormatClause {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
FormatClause::Identifier(ident) => write!(f, "FORMAT {}", ident),
|
||||
FormatClause::Identifier(ident) => write!(f, "FORMAT {ident}"),
|
||||
FormatClause::Null => write!(f, "FORMAT NULL"),
|
||||
}
|
||||
}
|
||||
|
@ -3078,9 +3231,9 @@ impl fmt::Display for ForClause {
|
|||
without_array_wrapper,
|
||||
} => {
|
||||
write!(f, "FOR JSON ")?;
|
||||
write!(f, "{}", for_json)?;
|
||||
write!(f, "{for_json}")?;
|
||||
if let Some(root) = root {
|
||||
write!(f, ", ROOT('{}')", root)?;
|
||||
write!(f, ", ROOT('{root}')")?;
|
||||
}
|
||||
if *include_null_values {
|
||||
write!(f, ", INCLUDE_NULL_VALUES")?;
|
||||
|
@ -3098,7 +3251,7 @@ impl fmt::Display for ForClause {
|
|||
r#type,
|
||||
} => {
|
||||
write!(f, "FOR XML ")?;
|
||||
write!(f, "{}", for_xml)?;
|
||||
write!(f, "{for_xml}")?;
|
||||
if *binary_base64 {
|
||||
write!(f, ", BINARY BASE64")?;
|
||||
}
|
||||
|
@ -3106,7 +3259,7 @@ impl fmt::Display for ForClause {
|
|||
write!(f, ", TYPE")?;
|
||||
}
|
||||
if let Some(root) = root {
|
||||
write!(f, ", ROOT('{}')", root)?;
|
||||
write!(f, ", ROOT('{root}')")?;
|
||||
}
|
||||
if *elements {
|
||||
write!(f, ", ELEMENTS")?;
|
||||
|
@ -3133,7 +3286,7 @@ impl fmt::Display for ForXml {
|
|||
ForXml::Raw(root) => {
|
||||
write!(f, "RAW")?;
|
||||
if let Some(root) = root {
|
||||
write!(f, "('{}')", root)?;
|
||||
write!(f, "('{root}')")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -3142,7 +3295,7 @@ impl fmt::Display for ForXml {
|
|||
ForXml::Path(root) => {
|
||||
write!(f, "PATH")?;
|
||||
if let Some(root) = root {
|
||||
write!(f, "('{}')", root)?;
|
||||
write!(f, "('{root}')")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -3205,7 +3358,7 @@ impl fmt::Display for JsonTableColumn {
|
|||
JsonTableColumn::Named(json_table_named_column) => {
|
||||
write!(f, "{json_table_named_column}")
|
||||
}
|
||||
JsonTableColumn::ForOrdinality(ident) => write!(f, "{} FOR ORDINALITY", ident),
|
||||
JsonTableColumn::ForOrdinality(ident) => write!(f, "{ident} FOR ORDINALITY"),
|
||||
JsonTableColumn::Nested(json_table_nested_column) => {
|
||||
write!(f, "{json_table_nested_column}")
|
||||
}
|
||||
|
@ -3271,10 +3424,10 @@ impl fmt::Display for JsonTableNamedColumn {
|
|||
self.path
|
||||
)?;
|
||||
if let Some(on_empty) = &self.on_empty {
|
||||
write!(f, " {} ON EMPTY", on_empty)?;
|
||||
write!(f, " {on_empty} ON EMPTY")?;
|
||||
}
|
||||
if let Some(on_error) = &self.on_error {
|
||||
write!(f, " {} ON ERROR", on_error)?;
|
||||
write!(f, " {on_error} ON ERROR")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -3296,7 +3449,7 @@ impl fmt::Display for JsonTableColumnErrorHandling {
|
|||
match self {
|
||||
JsonTableColumnErrorHandling::Null => write!(f, "NULL"),
|
||||
JsonTableColumnErrorHandling::Default(json_string) => {
|
||||
write!(f, "DEFAULT {}", json_string)
|
||||
write!(f, "DEFAULT {json_string}")
|
||||
}
|
||||
JsonTableColumnErrorHandling::Error => write!(f, "ERROR"),
|
||||
}
|
||||
|
@ -3429,12 +3582,12 @@ impl fmt::Display for XmlTableColumn {
|
|||
default,
|
||||
nullable,
|
||||
} => {
|
||||
write!(f, " {}", r#type)?;
|
||||
write!(f, " {type}")?;
|
||||
if let Some(p) = path {
|
||||
write!(f, " PATH {}", p)?;
|
||||
write!(f, " PATH {p}")?;
|
||||
}
|
||||
if let Some(d) = default {
|
||||
write!(f, " DEFAULT {}", d)?;
|
||||
write!(f, " DEFAULT {d}")?;
|
||||
}
|
||||
if !*nullable {
|
||||
write!(f, " NOT NULL")?;
|
||||
|
@ -3465,7 +3618,7 @@ impl fmt::Display for XmlPassingArgument {
|
|||
}
|
||||
write!(f, "{}", self.expr)?;
|
||||
if let Some(alias) = &self.alias {
|
||||
write!(f, " AS {}", alias)?;
|
||||
write!(f, " AS {alias}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
use crate::ast::query::SelectItemQualifiedWildcardKind;
|
||||
use crate::ast::{query::SelectItemQualifiedWildcardKind, ColumnOptions};
|
||||
use core::iter;
|
||||
|
||||
use crate::tokenizer::Span;
|
||||
|
@ -28,16 +28,17 @@ use super::{
|
|||
ConstraintCharacteristics, CopySource, CreateIndex, CreateTable, CreateTableOptions, Cte,
|
||||
Delete, DoUpdate, ExceptSelectItem, ExcludeSelectItem, Expr, ExprWithAlias, Fetch, FromTable,
|
||||
Function, FunctionArg, FunctionArgExpr, FunctionArgumentClause, FunctionArgumentList,
|
||||
FunctionArguments, GroupByExpr, HavingBound, IfStatement, IlikeSelectItem, Insert, Interpolate,
|
||||
InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView,
|
||||
LimitClause, MatchRecognizePattern, Measure, NamedParenthesizedList, NamedWindowDefinition,
|
||||
ObjectName, ObjectNamePart, Offset, OnConflict, OnConflictAction, OnInsert, OpenStatement,
|
||||
OrderBy, OrderByExpr, OrderByKind, Partition, PivotValueSource, ProjectionSelect, Query,
|
||||
RaiseStatement, RaiseStatementValue, ReferentialAction, RenameSelectItem, ReplaceSelectElement,
|
||||
ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SqlOption, Statement, Subscript,
|
||||
SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint, TableFactor, TableObject,
|
||||
TableOptionsClustered, TableWithJoins, UpdateTableFromKind, Use, Value, Values, ViewColumnDef,
|
||||
WhileStatement, WildcardAdditionalOptions, With, WithFill,
|
||||
FunctionArguments, GroupByExpr, HavingBound, IfStatement, IlikeSelectItem, IndexColumn, Insert,
|
||||
Interpolate, InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonPath, JsonPathElem,
|
||||
LateralView, LimitClause, MatchRecognizePattern, Measure, NamedParenthesizedList,
|
||||
NamedWindowDefinition, ObjectName, ObjectNamePart, Offset, OnConflict, OnConflictAction,
|
||||
OnInsert, OpenStatement, OrderBy, OrderByExpr, OrderByKind, Partition, PivotValueSource,
|
||||
ProjectionSelect, Query, RaiseStatement, RaiseStatementValue, ReferentialAction,
|
||||
RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem,
|
||||
SetExpr, SqlOption, Statement, Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef,
|
||||
TableConstraint, TableFactor, TableObject, TableOptionsClustered, TableWithJoins,
|
||||
UpdateTableFromKind, Use, Value, Values, ViewColumnDef, WhileStatement,
|
||||
WildcardAdditionalOptions, With, WithFill,
|
||||
};
|
||||
|
||||
/// Given an iterator of spans, return the [Span::union] of all spans.
|
||||
|
@ -422,6 +423,7 @@ impl Spanned for Statement {
|
|||
Statement::CreateIndex(create_index) => create_index.span(),
|
||||
Statement::CreateRole { .. } => Span::empty(),
|
||||
Statement::CreateSecret { .. } => Span::empty(),
|
||||
Statement::CreateServer { .. } => Span::empty(),
|
||||
Statement::CreateConnector { .. } => Span::empty(),
|
||||
Statement::AlterTable {
|
||||
name,
|
||||
|
@ -650,7 +652,7 @@ impl Spanned for TableConstraint {
|
|||
name.iter()
|
||||
.map(|i| i.span)
|
||||
.chain(index_name.iter().map(|i| i.span))
|
||||
.chain(columns.iter().map(|i| i.span))
|
||||
.chain(columns.iter().map(|i| i.span()))
|
||||
.chain(characteristics.iter().map(|i| i.span())),
|
||||
),
|
||||
TableConstraint::PrimaryKey {
|
||||
|
@ -664,7 +666,7 @@ impl Spanned for TableConstraint {
|
|||
name.iter()
|
||||
.map(|i| i.span)
|
||||
.chain(index_name.iter().map(|i| i.span))
|
||||
.chain(columns.iter().map(|i| i.span))
|
||||
.chain(columns.iter().map(|i| i.span()))
|
||||
.chain(characteristics.iter().map(|i| i.span())),
|
||||
),
|
||||
TableConstraint::ForeignKey {
|
||||
|
@ -700,7 +702,7 @@ impl Spanned for TableConstraint {
|
|||
} => union_spans(
|
||||
name.iter()
|
||||
.map(|i| i.span)
|
||||
.chain(columns.iter().map(|i| i.span)),
|
||||
.chain(columns.iter().map(|i| i.span())),
|
||||
),
|
||||
TableConstraint::FulltextOrSpatial {
|
||||
fulltext: _,
|
||||
|
@ -711,7 +713,7 @@ impl Spanned for TableConstraint {
|
|||
opt_index_name
|
||||
.iter()
|
||||
.map(|i| i.span)
|
||||
.chain(columns.iter().map(|i| i.span)),
|
||||
.chain(columns.iter().map(|i| i.span())),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -745,6 +747,12 @@ impl Spanned for CreateIndex {
|
|||
}
|
||||
}
|
||||
|
||||
impl Spanned for IndexColumn {
|
||||
fn span(&self) -> Span {
|
||||
self.column.span()
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned for CaseStatement {
|
||||
fn span(&self) -> Span {
|
||||
let CaseStatement {
|
||||
|
@ -917,6 +925,7 @@ impl Spanned for AlterColumnOperation {
|
|||
AlterColumnOperation::SetDataType {
|
||||
data_type: _,
|
||||
using,
|
||||
had_set: _,
|
||||
} => using.as_ref().map_or(Span::empty(), |u| u.span()),
|
||||
AlterColumnOperation::AddGenerated { .. } => Span::empty(),
|
||||
}
|
||||
|
@ -984,10 +993,13 @@ impl Spanned for ViewColumnDef {
|
|||
options,
|
||||
} = self;
|
||||
|
||||
union_spans(
|
||||
core::iter::once(name.span)
|
||||
.chain(options.iter().flat_map(|i| i.iter().map(|k| k.span()))),
|
||||
)
|
||||
name.span.union_opt(&options.as_ref().map(|o| o.span()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned for ColumnOptions {
|
||||
fn span(&self) -> Span {
|
||||
union_spans(self.as_slice().iter().map(|i| i.span()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1048,7 +1060,9 @@ impl Spanned for CreateTableOptions {
|
|||
match self {
|
||||
CreateTableOptions::None => Span::empty(),
|
||||
CreateTableOptions::With(vec) => union_spans(vec.iter().map(|i| i.span())),
|
||||
CreateTableOptions::Options(vec) => union_spans(vec.iter().map(|i| i.span())),
|
||||
CreateTableOptions::Options(vec) => {
|
||||
union_spans(vec.as_slice().iter().map(|i| i.span()))
|
||||
}
|
||||
CreateTableOptions::Plain(vec) => union_spans(vec.iter().map(|i| i.span())),
|
||||
CreateTableOptions::TableProperties(vec) => union_spans(vec.iter().map(|i| i.span())),
|
||||
}
|
||||
|
@ -1062,7 +1076,10 @@ impl Spanned for CreateTableOptions {
|
|||
impl Spanned for AlterTableOperation {
|
||||
fn span(&self) -> Span {
|
||||
match self {
|
||||
AlterTableOperation::AddConstraint(table_constraint) => table_constraint.span(),
|
||||
AlterTableOperation::AddConstraint {
|
||||
constraint,
|
||||
not_valid: _,
|
||||
} => constraint.span(),
|
||||
AlterTableOperation::AddColumn {
|
||||
column_keyword: _,
|
||||
if_not_exists: _,
|
||||
|
@ -1183,6 +1200,7 @@ impl Spanned for AlterTableOperation {
|
|||
AlterTableOperation::AutoIncrement { value, .. } => value.span(),
|
||||
AlterTableOperation::Lock { .. } => Span::empty(),
|
||||
AlterTableOperation::ReplicaIdentity { .. } => Span::empty(),
|
||||
AlterTableOperation::ValidateConstraint { name } => name.span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1398,7 +1416,6 @@ impl Spanned for AssignmentTarget {
|
|||
/// f.e. `IS NULL <expr>` reports as `<expr>::span`.
|
||||
///
|
||||
/// Missing spans:
|
||||
/// - [Expr::TypedString] # missing span for data_type
|
||||
/// - [Expr::MatchAgainst] # MySQL specific
|
||||
/// - [Expr::RLike] # MySQL specific
|
||||
/// - [Expr::Struct] # BigQuery specific
|
||||
|
@ -1607,6 +1624,7 @@ impl Spanned for Expr {
|
|||
Expr::OuterJoin(expr) => expr.span(),
|
||||
Expr::Prior(expr) => expr.span(),
|
||||
Expr::Lambda(_) => Span::empty(),
|
||||
Expr::MemberOf(member_of) => member_of.value.span().union(&member_of.array.span()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,7 +116,6 @@ impl From<ValueWithSpan> for Value {
|
|||
derive(Visit, VisitMut),
|
||||
visit(with = "visit_value")
|
||||
)]
|
||||
|
||||
pub enum Value {
|
||||
/// Numeric literal
|
||||
#[cfg(not(feature = "bigdecimal"))]
|
||||
|
@ -551,16 +550,16 @@ impl fmt::Display for EscapeUnicodeStringLiteral<'_> {
|
|||
write!(f, r#"\\"#)?;
|
||||
}
|
||||
x if x.is_ascii() => {
|
||||
write!(f, "{}", c)?;
|
||||
write!(f, "{c}")?;
|
||||
}
|
||||
_ => {
|
||||
let codepoint = c as u32;
|
||||
// if the character fits in 32 bits, we can use the \XXXX format
|
||||
// otherwise, we need to use the \+XXXXXX format
|
||||
if codepoint <= 0xFFFF {
|
||||
write!(f, "\\{:04X}", codepoint)?;
|
||||
write!(f, "\\{codepoint:04X}")?;
|
||||
} else {
|
||||
write!(f, "\\+{:06X}", codepoint)?;
|
||||
write!(f, "\\+{codepoint:06X}")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -926,10 +926,10 @@ mod tests {
|
|||
#[test]
|
||||
fn overflow() {
|
||||
let cond = (0..1000)
|
||||
.map(|n| format!("X = {}", n))
|
||||
.map(|n| format!("X = {n}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" OR ");
|
||||
let sql = format!("SELECT x where {0}", cond);
|
||||
let sql = format!("SELECT x where {cond}");
|
||||
|
||||
let dialect = GenericDialect {};
|
||||
let tokens = Tokenizer::new(&dialect, sql.as_str()).tokenize().unwrap();
|
||||
|
|
|
@ -46,7 +46,11 @@ pub struct BigQueryDialect;
|
|||
|
||||
impl Dialect for BigQueryDialect {
|
||||
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
|
||||
self.maybe_parse_statement(parser)
|
||||
if parser.parse_keyword(Keyword::BEGIN) {
|
||||
return Some(parser.parse_begin_exception_end());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// See <https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#identifiers>
|
||||
|
@ -141,48 +145,3 @@ impl Dialect for BigQueryDialect {
|
|||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl BigQueryDialect {
|
||||
fn maybe_parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
|
||||
if parser.peek_keyword(Keyword::BEGIN) {
|
||||
return Some(self.parse_begin(parser));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Parse a `BEGIN` statement.
|
||||
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#beginexceptionend>
|
||||
fn parse_begin(&self, parser: &mut Parser) -> Result<Statement, ParserError> {
|
||||
parser.expect_keyword(Keyword::BEGIN)?;
|
||||
|
||||
let statements = parser.parse_statement_list(&[Keyword::EXCEPTION, Keyword::END])?;
|
||||
|
||||
let has_exception_when_clause = parser.parse_keywords(&[
|
||||
Keyword::EXCEPTION,
|
||||
Keyword::WHEN,
|
||||
Keyword::ERROR,
|
||||
Keyword::THEN,
|
||||
]);
|
||||
let exception_statements = if has_exception_when_clause {
|
||||
if !parser.peek_keyword(Keyword::END) {
|
||||
Some(parser.parse_statement_list(&[Keyword::END])?)
|
||||
} else {
|
||||
Some(Default::default())
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
parser.expect_keyword(Keyword::END)?;
|
||||
|
||||
Ok(Statement::StartTransaction {
|
||||
begin: true,
|
||||
statements,
|
||||
exception_statements,
|
||||
has_end_keyword: true,
|
||||
transaction: None,
|
||||
modifier: None,
|
||||
modes: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,10 @@ impl Dialect for GenericDialect {
|
|||
true
|
||||
}
|
||||
|
||||
fn supports_left_associative_joins_without_parens(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_connect_by(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
@ -108,6 +112,10 @@ impl Dialect for GenericDialect {
|
|||
true
|
||||
}
|
||||
|
||||
fn supports_from_first_select(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_asc_desc_in_column_definition(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
|
|
@ -278,6 +278,34 @@ pub trait Dialect: Debug + Any {
|
|||
false
|
||||
}
|
||||
|
||||
/// Indicates whether the dialect supports left-associative join parsing
|
||||
/// by default when parentheses are omitted in nested joins.
|
||||
///
|
||||
/// Most dialects (like MySQL or Postgres) assume **left-associative** precedence,
|
||||
/// so a query like:
|
||||
///
|
||||
/// ```sql
|
||||
/// SELECT * FROM t1 NATURAL JOIN t5 INNER JOIN t0 ON ...
|
||||
/// ```
|
||||
/// is interpreted as:
|
||||
/// ```sql
|
||||
/// ((t1 NATURAL JOIN t5) INNER JOIN t0 ON ...)
|
||||
/// ```
|
||||
/// and internally represented as a **flat list** of joins.
|
||||
///
|
||||
/// In contrast, some dialects (e.g. **Snowflake**) assume **right-associative**
|
||||
/// precedence and interpret the same query as:
|
||||
/// ```sql
|
||||
/// (t1 NATURAL JOIN (t5 INNER JOIN t0 ON ...))
|
||||
/// ```
|
||||
/// which results in a **nested join** structure in the AST.
|
||||
///
|
||||
/// If this method returns `false`, the parser must build nested join trees
|
||||
/// even in the absence of parentheses to reflect the correct associativity
|
||||
fn supports_left_associative_joins_without_parens(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns true if the dialect supports the `(+)` syntax for OUTER JOIN.
|
||||
fn supports_outer_join_operator(&self) -> bool {
|
||||
false
|
||||
|
@ -587,7 +615,7 @@ pub trait Dialect: Debug + Any {
|
|||
}
|
||||
|
||||
let token = parser.peek_token();
|
||||
debug!("get_next_precedence_full() {:?}", token);
|
||||
debug!("get_next_precedence_full() {token:?}");
|
||||
match token.token {
|
||||
Token::Word(w) if w.keyword == Keyword::OR => Ok(p!(Or)),
|
||||
Token::Word(w) if w.keyword == Keyword::AND => Ok(p!(And)),
|
||||
|
@ -621,6 +649,7 @@ pub trait Dialect: Debug + Any {
|
|||
Token::Word(w) if w.keyword == Keyword::REGEXP => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::MATCH => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::MEMBER => Ok(p!(Like)),
|
||||
_ => Ok(self.prec_unknown()),
|
||||
},
|
||||
Token::Word(w) if w.keyword == Keyword::IS => Ok(p!(Is)),
|
||||
|
@ -633,6 +662,7 @@ pub trait Dialect: Debug + Any {
|
|||
Token::Word(w) if w.keyword == Keyword::REGEXP => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::MATCH => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::MEMBER => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::OPERATOR => Ok(p!(Between)),
|
||||
Token::Word(w) if w.keyword == Keyword::DIV => Ok(p!(MulDivModOp)),
|
||||
Token::Period => Ok(p!(Period)),
|
||||
|
@ -1028,6 +1058,19 @@ pub trait Dialect: Debug + Any {
|
|||
fn supports_set_names(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn supports_space_separated_column_options(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true if the dialect supports the `USING` clause in an `ALTER COLUMN` statement.
|
||||
/// Example:
|
||||
/// ```sql
|
||||
/// ALTER TABLE tbl ALTER COLUMN col SET DATA TYPE <type> USING <exp>`
|
||||
/// ```
|
||||
fn supports_alter_column_type_using(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// This represents the operators for which precedence must be defined
|
||||
|
|
|
@ -104,7 +104,7 @@ impl Dialect for PostgreSqlDialect {
|
|||
|
||||
fn get_next_precedence(&self, parser: &Parser) -> Option<Result<u8, ParserError>> {
|
||||
let token = parser.peek_token();
|
||||
debug!("get_next_precedence() {:?}", token);
|
||||
debug!("get_next_precedence() {token:?}");
|
||||
|
||||
// we only return some custom value here when the behaviour (not merely the numeric value) differs
|
||||
// from the default implementation
|
||||
|
@ -258,4 +258,8 @@ impl Dialect for PostgreSqlDialect {
|
|||
fn supports_set_names(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_alter_column_type_using(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,13 +80,15 @@ impl Dialect for RedshiftSqlDialect {
|
|||
}
|
||||
|
||||
fn is_identifier_start(&self, ch: char) -> bool {
|
||||
// Extends Postgres dialect with sharp
|
||||
PostgreSqlDialect {}.is_identifier_start(ch) || ch == '#'
|
||||
// Extends Postgres dialect with sharp and UTF-8 multibyte chars
|
||||
// https://docs.aws.amazon.com/redshift/latest/dg/r_names.html
|
||||
PostgreSqlDialect {}.is_identifier_start(ch) || ch == '#' || !ch.is_ascii()
|
||||
}
|
||||
|
||||
fn is_identifier_part(&self, ch: char) -> bool {
|
||||
// Extends Postgres dialect with sharp
|
||||
PostgreSqlDialect {}.is_identifier_part(ch) || ch == '#'
|
||||
// Extends Postgres dialect with sharp and UTF-8 multibyte chars
|
||||
// https://docs.aws.amazon.com/redshift/latest/dg/r_names.html
|
||||
PostgreSqlDialect {}.is_identifier_part(ch) || ch == '#' || !ch.is_ascii()
|
||||
}
|
||||
|
||||
/// redshift has `CONVERT(type, value)` instead of `CONVERT(value, type)`
|
||||
|
|
|
@ -131,6 +131,10 @@ impl Dialect for SnowflakeDialect {
|
|||
}
|
||||
|
||||
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
|
||||
if parser.parse_keyword(Keyword::BEGIN) {
|
||||
return Some(parser.parse_begin_exception_end());
|
||||
}
|
||||
|
||||
if parser.parse_keywords(&[Keyword::ALTER, Keyword::SESSION]) {
|
||||
// ALTER SESSION
|
||||
let set = match parser.parse_one_of_keywords(&[Keyword::SET, Keyword::UNSET]) {
|
||||
|
@ -279,6 +283,10 @@ impl Dialect for SnowflakeDialect {
|
|||
true
|
||||
}
|
||||
|
||||
fn supports_left_associative_joins_without_parens(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_reserved_for_identifier(&self, kw: Keyword) -> bool {
|
||||
// Unreserve some keywords that Snowflake accepts as identifiers
|
||||
// See: https://docs.snowflake.com/en/sql-reference/reserved-keywords
|
||||
|
@ -352,6 +360,10 @@ impl Dialect for SnowflakeDialect {
|
|||
fn get_reserved_keywords_for_select_item_operator(&self) -> &[Keyword] {
|
||||
&RESERVED_KEYWORDS_FOR_SELECT_ITEM_OPERATOR
|
||||
}
|
||||
|
||||
fn supports_space_separated_column_options(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_file_staging_command(kw: Keyword, parser: &mut Parser) -> Result<Statement, ParserError> {
|
||||
|
|
|
@ -395,6 +395,7 @@ define_keywords!(
|
|||
FUNCTION,
|
||||
FUNCTIONS,
|
||||
FUSION,
|
||||
FUTURE,
|
||||
GENERAL,
|
||||
GENERATE,
|
||||
GENERATED,
|
||||
|
@ -646,6 +647,7 @@ define_keywords!(
|
|||
ORDER,
|
||||
ORDINALITY,
|
||||
ORGANIZATION,
|
||||
OTHER,
|
||||
OUT,
|
||||
OUTER,
|
||||
OUTPUT,
|
||||
|
@ -814,6 +816,7 @@ define_keywords!(
|
|||
SERDE,
|
||||
SERDEPROPERTIES,
|
||||
SERIALIZABLE,
|
||||
SERVER,
|
||||
SERVICE,
|
||||
SESSION,
|
||||
SESSION_USER,
|
||||
|
@ -934,6 +937,8 @@ define_keywords!(
|
|||
TRY,
|
||||
TRY_CAST,
|
||||
TRY_CONVERT,
|
||||
TSQUERY,
|
||||
TSVECTOR,
|
||||
TUPLE,
|
||||
TYPE,
|
||||
UBIGINT,
|
||||
|
@ -977,6 +982,7 @@ define_keywords!(
|
|||
UUID,
|
||||
VACUUM,
|
||||
VALID,
|
||||
VALIDATE,
|
||||
VALIDATION_MODE,
|
||||
VALUE,
|
||||
VALUES,
|
||||
|
@ -1012,6 +1018,7 @@ define_keywords!(
|
|||
WITHOUT,
|
||||
WITHOUT_ARRAY_WRAPPER,
|
||||
WORK,
|
||||
WRAPPER,
|
||||
WRITE,
|
||||
XML,
|
||||
XMLNAMESPACES,
|
||||
|
|
|
@ -436,7 +436,7 @@ impl<'a> Parser<'a> {
|
|||
///
|
||||
/// See example on [`Parser::new()`] for an example
|
||||
pub fn try_with_sql(self, sql: &str) -> Result<Self, ParserError> {
|
||||
debug!("Parsing sql '{}'...", sql);
|
||||
debug!("Parsing sql '{sql}'...");
|
||||
let tokens = Tokenizer::new(self.dialect, sql)
|
||||
.with_unescape(self.options.unescape)
|
||||
.tokenize_with_location()?;
|
||||
|
@ -1226,10 +1226,10 @@ impl<'a> Parser<'a> {
|
|||
|
||||
expr = self.parse_compound_expr(expr, vec![])?;
|
||||
|
||||
debug!("prefix: {:?}", expr);
|
||||
debug!("prefix: {expr:?}");
|
||||
loop {
|
||||
let next_precedence = self.get_next_precedence()?;
|
||||
debug!("next precedence: {:?}", next_precedence);
|
||||
debug!("next precedence: {next_precedence:?}");
|
||||
|
||||
if precedence >= next_precedence {
|
||||
break;
|
||||
|
@ -1521,7 +1521,7 @@ impl<'a> Parser<'a> {
|
|||
DataType::Custom(..) => parser_err!("dummy", loc),
|
||||
data_type => Ok(Expr::TypedString {
|
||||
data_type,
|
||||
value: parser.parse_value()?.value,
|
||||
value: parser.parse_value()?,
|
||||
}),
|
||||
}
|
||||
})?;
|
||||
|
@ -1631,8 +1631,7 @@ impl<'a> Parser<'a> {
|
|||
Token::QuestionPipe => UnaryOperator::QuestionPipe,
|
||||
_ => {
|
||||
return Err(ParserError::ParserError(format!(
|
||||
"Unexpected token in unary operator parsing: {:?}",
|
||||
tok
|
||||
"Unexpected token in unary operator parsing: {tok:?}"
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
@ -1709,10 +1708,9 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
|
||||
fn parse_geometric_type(&mut self, kind: GeometricTypeKind) -> Result<Expr, ParserError> {
|
||||
let value: Value = self.parse_value()?.value;
|
||||
Ok(Expr::TypedString {
|
||||
data_type: DataType::GeometricType(kind),
|
||||
value,
|
||||
value: self.parse_value()?,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -2577,7 +2575,7 @@ impl<'a> Parser<'a> {
|
|||
trim_characters: None,
|
||||
})
|
||||
} else if self.consume_token(&Token::Comma)
|
||||
&& dialect_of!(self is SnowflakeDialect | BigQueryDialect | GenericDialect)
|
||||
&& dialect_of!(self is DuckDbDialect | SnowflakeDialect | BigQueryDialect | GenericDialect)
|
||||
{
|
||||
let characters = self.parse_comma_separated(Parser::parse_expr)?;
|
||||
self.expect_token(&Token::RParen)?;
|
||||
|
@ -2771,7 +2769,7 @@ impl<'a> Parser<'a> {
|
|||
|
||||
if self.dialect.supports_dictionary_syntax() {
|
||||
self.prev_token(); // Put back the '{'
|
||||
return self.parse_duckdb_struct_literal();
|
||||
return self.parse_dictionary();
|
||||
}
|
||||
|
||||
self.expected("an expression", token)
|
||||
|
@ -3034,7 +3032,6 @@ impl<'a> Parser<'a> {
|
|||
where
|
||||
F: FnMut(&mut Parser<'a>) -> Result<(StructField, MatchedTrailingBracket), ParserError>,
|
||||
{
|
||||
let start_token = self.peek_token();
|
||||
self.expect_keyword_is(Keyword::STRUCT)?;
|
||||
|
||||
// Nothing to do if we have no type information.
|
||||
|
@ -3047,16 +3044,10 @@ impl<'a> Parser<'a> {
|
|||
let trailing_bracket = loop {
|
||||
let (def, trailing_bracket) = elem_parser(self)?;
|
||||
field_defs.push(def);
|
||||
if !self.consume_token(&Token::Comma) {
|
||||
// The struct field definition is finished if it occurs `>>` or comma.
|
||||
if trailing_bracket.0 || !self.consume_token(&Token::Comma) {
|
||||
break trailing_bracket;
|
||||
}
|
||||
|
||||
// Angle brackets are balanced so we only expect the trailing `>>` after
|
||||
// we've matched all field types for the current struct.
|
||||
// e.g. this is invalid syntax `STRUCT<STRUCT<INT>>>, INT>(NULL)`
|
||||
if trailing_bracket.0 {
|
||||
return parser_err!("unmatched > in STRUCT definition", start_token.span.start);
|
||||
}
|
||||
};
|
||||
|
||||
Ok((
|
||||
|
@ -3147,7 +3138,7 @@ impl<'a> Parser<'a> {
|
|||
Ok(fields)
|
||||
}
|
||||
|
||||
/// DuckDB specific: Parse a duckdb [dictionary]
|
||||
/// DuckDB and ClickHouse specific: Parse a duckdb [dictionary] or a clickhouse [map] setting
|
||||
///
|
||||
/// Syntax:
|
||||
///
|
||||
|
@ -3156,18 +3147,18 @@ impl<'a> Parser<'a> {
|
|||
/// ```
|
||||
///
|
||||
/// [dictionary]: https://duckdb.org/docs/sql/data_types/struct#creating-structs
|
||||
fn parse_duckdb_struct_literal(&mut self) -> Result<Expr, ParserError> {
|
||||
/// [map]: https://clickhouse.com/docs/operations/settings/settings#additional_table_filters
|
||||
fn parse_dictionary(&mut self) -> Result<Expr, ParserError> {
|
||||
self.expect_token(&Token::LBrace)?;
|
||||
|
||||
let fields =
|
||||
self.parse_comma_separated0(Self::parse_duckdb_dictionary_field, Token::RBrace)?;
|
||||
let fields = self.parse_comma_separated0(Self::parse_dictionary_field, Token::RBrace)?;
|
||||
|
||||
self.expect_token(&Token::RBrace)?;
|
||||
|
||||
Ok(Expr::Dictionary(fields))
|
||||
}
|
||||
|
||||
/// Parse a field for a duckdb [dictionary]
|
||||
/// Parse a field for a duckdb [dictionary] or a clickhouse [map] setting
|
||||
///
|
||||
/// Syntax
|
||||
///
|
||||
|
@ -3176,7 +3167,8 @@ impl<'a> Parser<'a> {
|
|||
/// ```
|
||||
///
|
||||
/// [dictionary]: https://duckdb.org/docs/sql/data_types/struct#creating-structs
|
||||
fn parse_duckdb_dictionary_field(&mut self) -> Result<DictionaryField, ParserError> {
|
||||
/// [map]: https://clickhouse.com/docs/operations/settings/settings#additional_table_filters
|
||||
fn parse_dictionary_field(&mut self) -> Result<DictionaryField, ParserError> {
|
||||
let key = self.parse_identifier()?;
|
||||
|
||||
self.expect_token(&Token::Colon)?;
|
||||
|
@ -3616,6 +3608,19 @@ impl<'a> Parser<'a> {
|
|||
self.expected("IN or BETWEEN after NOT", self.peek_token())
|
||||
}
|
||||
}
|
||||
Keyword::MEMBER => {
|
||||
if self.parse_keyword(Keyword::OF) {
|
||||
self.expect_token(&Token::LParen)?;
|
||||
let array = self.parse_expr()?;
|
||||
self.expect_token(&Token::RParen)?;
|
||||
Ok(Expr::MemberOf(MemberOf {
|
||||
value: Box::new(expr),
|
||||
array: Box::new(array),
|
||||
}))
|
||||
} else {
|
||||
self.expected("OF after MEMBER", self.peek_token())
|
||||
}
|
||||
}
|
||||
// Can only happen if `get_next_precedence` got out of sync with this function
|
||||
_ => parser_err!(
|
||||
format!("No infix parser for token {:?}", tok.token),
|
||||
|
@ -3824,7 +3829,7 @@ impl<'a> Parser<'a> {
|
|||
});
|
||||
}
|
||||
self.expect_token(&Token::LParen)?;
|
||||
let in_op = match self.maybe_parse(|p| p.parse_query_body(p.dialect.prec_unknown()))? {
|
||||
let in_op = match self.maybe_parse(|p| p.parse_query())? {
|
||||
Some(subquery) => Expr::InSubquery {
|
||||
expr: Box::new(expr),
|
||||
subquery,
|
||||
|
@ -4669,6 +4674,8 @@ impl<'a> Parser<'a> {
|
|||
self.parse_create_procedure(or_alter)
|
||||
} else if self.parse_keyword(Keyword::CONNECTOR) {
|
||||
self.parse_create_connector()
|
||||
} else if self.parse_keyword(Keyword::SERVER) {
|
||||
self.parse_pg_create_server()
|
||||
} else {
|
||||
self.expected("an object type after CREATE", self.peek_token())
|
||||
}
|
||||
|
@ -6875,9 +6882,7 @@ impl<'a> Parser<'a> {
|
|||
None
|
||||
};
|
||||
|
||||
self.expect_token(&Token::LParen)?;
|
||||
let columns = self.parse_comma_separated(Parser::parse_create_index_expr)?;
|
||||
self.expect_token(&Token::RParen)?;
|
||||
let columns = self.parse_parenthesized_index_column_list()?;
|
||||
|
||||
let include = if self.parse_keyword(Keyword::INCLUDE) {
|
||||
self.expect_token(&Token::LParen)?;
|
||||
|
@ -7633,9 +7638,22 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
|
||||
pub fn parse_procedure_param(&mut self) -> Result<ProcedureParam, ParserError> {
|
||||
let mode = if self.parse_keyword(Keyword::IN) {
|
||||
Some(ArgMode::In)
|
||||
} else if self.parse_keyword(Keyword::OUT) {
|
||||
Some(ArgMode::Out)
|
||||
} else if self.parse_keyword(Keyword::INOUT) {
|
||||
Some(ArgMode::InOut)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let name = self.parse_identifier()?;
|
||||
let data_type = self.parse_data_type()?;
|
||||
Ok(ProcedureParam { name, data_type })
|
||||
Ok(ProcedureParam {
|
||||
name,
|
||||
data_type,
|
||||
mode,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_column_def(&mut self) -> Result<ColumnDef, ParserError> {
|
||||
|
@ -8077,7 +8095,7 @@ impl<'a> Parser<'a> {
|
|||
let index_name = self.parse_optional_ident()?;
|
||||
let index_type = self.parse_optional_using_then_index_type()?;
|
||||
|
||||
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
|
||||
let columns = self.parse_parenthesized_index_column_list()?;
|
||||
let index_options = self.parse_index_options()?;
|
||||
let characteristics = self.parse_constraint_characteristics()?;
|
||||
Ok(Some(TableConstraint::Unique {
|
||||
|
@ -8099,7 +8117,7 @@ impl<'a> Parser<'a> {
|
|||
let index_name = self.parse_optional_ident()?;
|
||||
let index_type = self.parse_optional_using_then_index_type()?;
|
||||
|
||||
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
|
||||
let columns = self.parse_parenthesized_index_column_list()?;
|
||||
let index_options = self.parse_index_options()?;
|
||||
let characteristics = self.parse_constraint_characteristics()?;
|
||||
Ok(Some(TableConstraint::PrimaryKey {
|
||||
|
@ -8177,7 +8195,7 @@ impl<'a> Parser<'a> {
|
|||
};
|
||||
|
||||
let index_type = self.parse_optional_using_then_index_type()?;
|
||||
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
|
||||
let columns = self.parse_parenthesized_index_column_list()?;
|
||||
|
||||
Ok(Some(TableConstraint::Index {
|
||||
display_as_key,
|
||||
|
@ -8206,7 +8224,7 @@ impl<'a> Parser<'a> {
|
|||
|
||||
let opt_index_name = self.parse_optional_ident()?;
|
||||
|
||||
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
|
||||
let columns = self.parse_parenthesized_index_column_list()?;
|
||||
|
||||
Ok(Some(TableConstraint::FulltextOrSpatial {
|
||||
fulltext,
|
||||
|
@ -8473,7 +8491,11 @@ impl<'a> Parser<'a> {
|
|||
pub fn parse_alter_table_operation(&mut self) -> Result<AlterTableOperation, ParserError> {
|
||||
let operation = if self.parse_keyword(Keyword::ADD) {
|
||||
if let Some(constraint) = self.parse_optional_table_constraint()? {
|
||||
AlterTableOperation::AddConstraint(constraint)
|
||||
let not_valid = self.parse_keywords(&[Keyword::NOT, Keyword::VALID]);
|
||||
AlterTableOperation::AddConstraint {
|
||||
constraint,
|
||||
not_valid,
|
||||
}
|
||||
} else if dialect_of!(self is ClickHouseDialect|GenericDialect)
|
||||
&& self.parse_keyword(Keyword::PROJECTION)
|
||||
{
|
||||
|
@ -8730,16 +8752,10 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
} else if self.parse_keywords(&[Keyword::DROP, Keyword::DEFAULT]) {
|
||||
AlterColumnOperation::DropDefault {}
|
||||
} else if self.parse_keywords(&[Keyword::SET, Keyword::DATA, Keyword::TYPE])
|
||||
|| (is_postgresql && self.parse_keyword(Keyword::TYPE))
|
||||
{
|
||||
let data_type = self.parse_data_type()?;
|
||||
let using = if is_postgresql && self.parse_keyword(Keyword::USING) {
|
||||
Some(self.parse_expr()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
AlterColumnOperation::SetDataType { data_type, using }
|
||||
} else if self.parse_keywords(&[Keyword::SET, Keyword::DATA, Keyword::TYPE]) {
|
||||
self.parse_set_data_type(true)?
|
||||
} else if self.parse_keyword(Keyword::TYPE) {
|
||||
self.parse_set_data_type(false)?
|
||||
} else if self.parse_keywords(&[Keyword::ADD, Keyword::GENERATED]) {
|
||||
let generated_as = if self.parse_keyword(Keyword::ALWAYS) {
|
||||
Some(GeneratedAs::Always)
|
||||
|
@ -8888,6 +8904,9 @@ impl<'a> Parser<'a> {
|
|||
};
|
||||
|
||||
AlterTableOperation::ReplicaIdentity { identity }
|
||||
} else if self.parse_keywords(&[Keyword::VALIDATE, Keyword::CONSTRAINT]) {
|
||||
let name = self.parse_identifier()?;
|
||||
AlterTableOperation::ValidateConstraint { name }
|
||||
} else {
|
||||
let options: Vec<SqlOption> =
|
||||
self.parse_options_with_keywords(&[Keyword::SET, Keyword::TBLPROPERTIES])?;
|
||||
|
@ -8905,6 +8924,22 @@ impl<'a> Parser<'a> {
|
|||
Ok(operation)
|
||||
}
|
||||
|
||||
fn parse_set_data_type(&mut self, had_set: bool) -> Result<AlterColumnOperation, ParserError> {
|
||||
let data_type = self.parse_data_type()?;
|
||||
let using = if self.dialect.supports_alter_column_type_using()
|
||||
&& self.parse_keyword(Keyword::USING)
|
||||
{
|
||||
Some(self.parse_expr()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(AlterColumnOperation::SetDataType {
|
||||
data_type,
|
||||
using,
|
||||
had_set,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_part_or_partition(&mut self) -> Result<Partition, ParserError> {
|
||||
let keyword = self.expect_one_of_keywords(&[Keyword::PART, Keyword::PARTITION])?;
|
||||
match keyword {
|
||||
|
@ -9916,6 +9951,12 @@ impl<'a> Parser<'a> {
|
|||
Ok(DataType::Unsigned)
|
||||
}
|
||||
}
|
||||
Keyword::TSVECTOR if dialect_is!(dialect is PostgreSqlDialect | GenericDialect) => {
|
||||
Ok(DataType::TsVector)
|
||||
}
|
||||
Keyword::TSQUERY if dialect_is!(dialect is PostgreSqlDialect | GenericDialect) => {
|
||||
Ok(DataType::TsQuery)
|
||||
}
|
||||
_ => {
|
||||
self.prev_token();
|
||||
let type_name = self.parse_object_name(false)?;
|
||||
|
@ -9978,6 +10019,48 @@ impl<'a> Parser<'a> {
|
|||
Ok(IdentWithAlias { ident, alias })
|
||||
}
|
||||
|
||||
/// Parse `identifier [AS] identifier` where the AS keyword is optional
|
||||
fn parse_identifier_with_optional_alias(&mut self) -> Result<IdentWithAlias, ParserError> {
|
||||
let ident = self.parse_identifier()?;
|
||||
let _after_as = self.parse_keyword(Keyword::AS);
|
||||
let alias = self.parse_identifier()?;
|
||||
Ok(IdentWithAlias { ident, alias })
|
||||
}
|
||||
|
||||
/// Parse comma-separated list of parenthesized queries for pipe operators
|
||||
fn parse_pipe_operator_queries(&mut self) -> Result<Vec<Query>, ParserError> {
|
||||
self.parse_comma_separated(|parser| {
|
||||
parser.expect_token(&Token::LParen)?;
|
||||
let query = parser.parse_query()?;
|
||||
parser.expect_token(&Token::RParen)?;
|
||||
Ok(*query)
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse set quantifier for pipe operators that require DISTINCT. E.g. INTERSECT and EXCEPT
|
||||
fn parse_distinct_required_set_quantifier(
|
||||
&mut self,
|
||||
operator_name: &str,
|
||||
) -> Result<SetQuantifier, ParserError> {
|
||||
let quantifier = self.parse_set_quantifier(&Some(SetOperator::Intersect));
|
||||
match quantifier {
|
||||
SetQuantifier::Distinct | SetQuantifier::DistinctByName => Ok(quantifier),
|
||||
_ => Err(ParserError::ParserError(format!(
|
||||
"{operator_name} pipe operator requires DISTINCT modifier",
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse optional identifier alias (with or without AS keyword)
|
||||
fn parse_identifier_optional_alias(&mut self) -> Result<Option<Ident>, ParserError> {
|
||||
if self.parse_keyword(Keyword::AS) {
|
||||
Ok(Some(self.parse_identifier()?))
|
||||
} else {
|
||||
// Check if the next token is an identifier (implicit alias)
|
||||
self.maybe_parse(|parser| parser.parse_identifier())
|
||||
}
|
||||
}
|
||||
|
||||
/// Optionally parses an alias for a select list item
|
||||
fn maybe_parse_select_item_alias(&mut self) -> Result<Option<Ident>, ParserError> {
|
||||
fn validator(explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
|
||||
|
@ -10569,17 +10652,7 @@ impl<'a> Parser<'a> {
|
|||
/// Parses a column definition within a view.
|
||||
fn parse_view_column(&mut self) -> Result<ViewColumnDef, ParserError> {
|
||||
let name = self.parse_identifier()?;
|
||||
let options = if (dialect_of!(self is BigQueryDialect | GenericDialect)
|
||||
&& self.parse_keyword(Keyword::OPTIONS))
|
||||
|| (dialect_of!(self is SnowflakeDialect | GenericDialect)
|
||||
&& self.parse_keyword(Keyword::COMMENT))
|
||||
{
|
||||
self.prev_token();
|
||||
self.parse_optional_column_option()?
|
||||
.map(|option| vec![option])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let options = self.parse_view_column_options()?;
|
||||
let data_type = if dialect_of!(self is ClickHouseDialect) {
|
||||
Some(self.parse_data_type()?)
|
||||
} else {
|
||||
|
@ -10592,6 +10665,25 @@ impl<'a> Parser<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
fn parse_view_column_options(&mut self) -> Result<Option<ColumnOptions>, ParserError> {
|
||||
let mut options = Vec::new();
|
||||
loop {
|
||||
let option = self.parse_optional_column_option()?;
|
||||
if let Some(option) = option {
|
||||
options.push(option);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if options.is_empty() {
|
||||
Ok(None)
|
||||
} else if self.dialect.supports_space_separated_column_options() {
|
||||
Ok(Some(ColumnOptions::SpaceSeparated(options)))
|
||||
} else {
|
||||
Ok(Some(ColumnOptions::CommaSeparated(options)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a parenthesized comma-separated list of unqualified, possibly quoted identifiers.
|
||||
/// For example: `(col1, "col 2", ...)`
|
||||
pub fn parse_parenthesized_column_list(
|
||||
|
@ -10602,6 +10694,14 @@ impl<'a> Parser<'a> {
|
|||
self.parse_parenthesized_column_list_inner(optional, allow_empty, |p| p.parse_identifier())
|
||||
}
|
||||
|
||||
/// Parses a parenthesized comma-separated list of index columns, which can be arbitrary
|
||||
/// expressions with ordering information (and an opclass in some dialects).
|
||||
fn parse_parenthesized_index_column_list(&mut self) -> Result<Vec<IndexColumn>, ParserError> {
|
||||
self.parse_parenthesized_column_list_inner(Mandatory, false, |p| {
|
||||
p.parse_create_index_expr()
|
||||
})
|
||||
}
|
||||
|
||||
/// Parses a parenthesized comma-separated list of qualified, possibly quoted identifiers.
|
||||
/// For example: `(db1.sc1.tbl1.col1, db1.sc1.tbl1."col 2", ...)`
|
||||
pub fn parse_parenthesized_qualified_column_list(
|
||||
|
@ -11107,6 +11207,19 @@ impl<'a> Parser<'a> {
|
|||
Keyword::AGGREGATE,
|
||||
Keyword::ORDER,
|
||||
Keyword::TABLESAMPLE,
|
||||
Keyword::RENAME,
|
||||
Keyword::UNION,
|
||||
Keyword::INTERSECT,
|
||||
Keyword::EXCEPT,
|
||||
Keyword::CALL,
|
||||
Keyword::PIVOT,
|
||||
Keyword::UNPIVOT,
|
||||
Keyword::JOIN,
|
||||
Keyword::INNER,
|
||||
Keyword::LEFT,
|
||||
Keyword::RIGHT,
|
||||
Keyword::FULL,
|
||||
Keyword::CROSS,
|
||||
])?;
|
||||
match kw {
|
||||
Keyword::SELECT => {
|
||||
|
@ -11173,6 +11286,121 @@ impl<'a> Parser<'a> {
|
|||
let sample = self.parse_table_sample(TableSampleModifier::TableSample)?;
|
||||
pipe_operators.push(PipeOperator::TableSample { sample });
|
||||
}
|
||||
Keyword::RENAME => {
|
||||
let mappings =
|
||||
self.parse_comma_separated(Parser::parse_identifier_with_optional_alias)?;
|
||||
pipe_operators.push(PipeOperator::Rename { mappings });
|
||||
}
|
||||
Keyword::UNION => {
|
||||
let set_quantifier = self.parse_set_quantifier(&Some(SetOperator::Union));
|
||||
let queries = self.parse_pipe_operator_queries()?;
|
||||
pipe_operators.push(PipeOperator::Union {
|
||||
set_quantifier,
|
||||
queries,
|
||||
});
|
||||
}
|
||||
Keyword::INTERSECT => {
|
||||
let set_quantifier =
|
||||
self.parse_distinct_required_set_quantifier("INTERSECT")?;
|
||||
let queries = self.parse_pipe_operator_queries()?;
|
||||
pipe_operators.push(PipeOperator::Intersect {
|
||||
set_quantifier,
|
||||
queries,
|
||||
});
|
||||
}
|
||||
Keyword::EXCEPT => {
|
||||
let set_quantifier = self.parse_distinct_required_set_quantifier("EXCEPT")?;
|
||||
let queries = self.parse_pipe_operator_queries()?;
|
||||
pipe_operators.push(PipeOperator::Except {
|
||||
set_quantifier,
|
||||
queries,
|
||||
});
|
||||
}
|
||||
Keyword::CALL => {
|
||||
let function_name = self.parse_object_name(false)?;
|
||||
let function_expr = self.parse_function(function_name)?;
|
||||
if let Expr::Function(function) = function_expr {
|
||||
let alias = self.parse_identifier_optional_alias()?;
|
||||
pipe_operators.push(PipeOperator::Call { function, alias });
|
||||
} else {
|
||||
return Err(ParserError::ParserError(
|
||||
"Expected function call after CALL".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
Keyword::PIVOT => {
|
||||
self.expect_token(&Token::LParen)?;
|
||||
let aggregate_functions =
|
||||
self.parse_comma_separated(Self::parse_aliased_function_call)?;
|
||||
self.expect_keyword_is(Keyword::FOR)?;
|
||||
let value_column = self.parse_period_separated(|p| p.parse_identifier())?;
|
||||
self.expect_keyword_is(Keyword::IN)?;
|
||||
|
||||
self.expect_token(&Token::LParen)?;
|
||||
let value_source = if self.parse_keyword(Keyword::ANY) {
|
||||
let order_by = if self.parse_keywords(&[Keyword::ORDER, Keyword::BY]) {
|
||||
self.parse_comma_separated(Parser::parse_order_by_expr)?
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
PivotValueSource::Any(order_by)
|
||||
} else if self.peek_sub_query() {
|
||||
PivotValueSource::Subquery(self.parse_query()?)
|
||||
} else {
|
||||
PivotValueSource::List(
|
||||
self.parse_comma_separated(Self::parse_expr_with_alias)?,
|
||||
)
|
||||
};
|
||||
self.expect_token(&Token::RParen)?;
|
||||
self.expect_token(&Token::RParen)?;
|
||||
|
||||
let alias = self.parse_identifier_optional_alias()?;
|
||||
|
||||
pipe_operators.push(PipeOperator::Pivot {
|
||||
aggregate_functions,
|
||||
value_column,
|
||||
value_source,
|
||||
alias,
|
||||
});
|
||||
}
|
||||
Keyword::UNPIVOT => {
|
||||
self.expect_token(&Token::LParen)?;
|
||||
let value_column = self.parse_identifier()?;
|
||||
self.expect_keyword(Keyword::FOR)?;
|
||||
let name_column = self.parse_identifier()?;
|
||||
self.expect_keyword(Keyword::IN)?;
|
||||
|
||||
self.expect_token(&Token::LParen)?;
|
||||
let unpivot_columns = self.parse_comma_separated(Parser::parse_identifier)?;
|
||||
self.expect_token(&Token::RParen)?;
|
||||
|
||||
self.expect_token(&Token::RParen)?;
|
||||
|
||||
let alias = self.parse_identifier_optional_alias()?;
|
||||
|
||||
pipe_operators.push(PipeOperator::Unpivot {
|
||||
value_column,
|
||||
name_column,
|
||||
unpivot_columns,
|
||||
alias,
|
||||
});
|
||||
}
|
||||
Keyword::JOIN
|
||||
| Keyword::INNER
|
||||
| Keyword::LEFT
|
||||
| Keyword::RIGHT
|
||||
| Keyword::FULL
|
||||
| Keyword::CROSS => {
|
||||
self.prev_token();
|
||||
let mut joins = self.parse_joins()?;
|
||||
if joins.len() != 1 {
|
||||
return Err(ParserError::ParserError(
|
||||
"Join pipe operator must have a single join".to_string(),
|
||||
));
|
||||
}
|
||||
let join = joins.swap_remove(0);
|
||||
pipe_operators.push(PipeOperator::Join(join))
|
||||
}
|
||||
unhandled => {
|
||||
return Err(ParserError::ParserError(format!(
|
||||
"`expect_one_of_keywords` further up allowed unhandled keyword: {unhandled:?}"
|
||||
|
@ -11190,7 +11418,7 @@ impl<'a> Parser<'a> {
|
|||
let key_values = self.parse_comma_separated(|p| {
|
||||
let key = p.parse_identifier()?;
|
||||
p.expect_token(&Token::Eq)?;
|
||||
let value = p.parse_value()?.value;
|
||||
let value = p.parse_expr()?;
|
||||
Ok(Setting { key, value })
|
||||
})?;
|
||||
Some(key_values)
|
||||
|
@ -12468,7 +12696,11 @@ impl<'a> Parser<'a> {
|
|||
};
|
||||
let mut relation = self.parse_table_factor()?;
|
||||
|
||||
if self.peek_parens_less_nested_join() {
|
||||
if !self
|
||||
.dialect
|
||||
.supports_left_associative_joins_without_parens()
|
||||
&& self.peek_parens_less_nested_join()
|
||||
{
|
||||
let joins = self.parse_joins()?;
|
||||
relation = TableFactor::NestedJoin {
|
||||
table_with_joins: Box::new(TableWithJoins { relation, joins }),
|
||||
|
@ -13628,7 +13860,7 @@ impl<'a> Parser<'a> {
|
|||
let ident = self.parse_identifier()?;
|
||||
if let GranteeName::ObjectName(namespace) = name {
|
||||
name = GranteeName::ObjectName(ObjectName::from(vec![Ident::new(
|
||||
format!("{}:{}", namespace, ident),
|
||||
format!("{namespace}:{ident}"),
|
||||
)]));
|
||||
};
|
||||
}
|
||||
|
@ -13665,6 +13897,33 @@ impl<'a> Parser<'a> {
|
|||
Some(GrantObjects::AllTablesInSchema {
|
||||
schemas: self.parse_comma_separated(|p| p.parse_object_name(false))?,
|
||||
})
|
||||
} else if self.parse_keywords(&[
|
||||
Keyword::FUTURE,
|
||||
Keyword::SCHEMAS,
|
||||
Keyword::IN,
|
||||
Keyword::DATABASE,
|
||||
]) {
|
||||
Some(GrantObjects::FutureSchemasInDatabase {
|
||||
databases: self.parse_comma_separated(|p| p.parse_object_name(false))?,
|
||||
})
|
||||
} else if self.parse_keywords(&[
|
||||
Keyword::FUTURE,
|
||||
Keyword::TABLES,
|
||||
Keyword::IN,
|
||||
Keyword::SCHEMA,
|
||||
]) {
|
||||
Some(GrantObjects::FutureTablesInSchema {
|
||||
schemas: self.parse_comma_separated(|p| p.parse_object_name(false))?,
|
||||
})
|
||||
} else if self.parse_keywords(&[
|
||||
Keyword::FUTURE,
|
||||
Keyword::VIEWS,
|
||||
Keyword::IN,
|
||||
Keyword::SCHEMA,
|
||||
]) {
|
||||
Some(GrantObjects::FutureViewsInSchema {
|
||||
schemas: self.parse_comma_separated(|p| p.parse_object_name(false))?,
|
||||
})
|
||||
} else if self.parse_keywords(&[
|
||||
Keyword::ALL,
|
||||
Keyword::SEQUENCES,
|
||||
|
@ -14598,7 +14857,7 @@ impl<'a> Parser<'a> {
|
|||
self.dialect
|
||||
.get_reserved_keywords_for_select_item_operator(),
|
||||
)
|
||||
.map(|keyword| Ident::new(format!("{:?}", keyword)));
|
||||
.map(|keyword| Ident::new(format!("{keyword:?}")));
|
||||
|
||||
match self.parse_wildcard_expr()? {
|
||||
Expr::QualifiedWildcard(prefix, token) => Ok(SelectItem::QualifiedWildcard(
|
||||
|
@ -15018,7 +15277,8 @@ impl<'a> Parser<'a> {
|
|||
|
||||
/// Parse a FETCH clause
|
||||
pub fn parse_fetch(&mut self) -> Result<Fetch, ParserError> {
|
||||
self.expect_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT])?;
|
||||
let _ = self.parse_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT]);
|
||||
|
||||
let (quantity, percent) = if self
|
||||
.parse_one_of_keywords(&[Keyword::ROW, Keyword::ROWS])
|
||||
.is_some()
|
||||
|
@ -15027,16 +15287,16 @@ impl<'a> Parser<'a> {
|
|||
} else {
|
||||
let quantity = Expr::Value(self.parse_value()?);
|
||||
let percent = self.parse_keyword(Keyword::PERCENT);
|
||||
self.expect_one_of_keywords(&[Keyword::ROW, Keyword::ROWS])?;
|
||||
let _ = self.parse_one_of_keywords(&[Keyword::ROW, Keyword::ROWS]);
|
||||
(Some(quantity), percent)
|
||||
};
|
||||
|
||||
let with_ties = if self.parse_keyword(Keyword::ONLY) {
|
||||
false
|
||||
} else if self.parse_keywords(&[Keyword::WITH, Keyword::TIES]) {
|
||||
true
|
||||
} else {
|
||||
return self.expected("one of ONLY or WITH TIES", self.peek_token());
|
||||
self.parse_keywords(&[Keyword::WITH, Keyword::TIES])
|
||||
};
|
||||
|
||||
Ok(Fetch {
|
||||
with_ties,
|
||||
percent,
|
||||
|
@ -15099,7 +15359,7 @@ impl<'a> Parser<'a> {
|
|||
transaction: Some(BeginTransactionKind::Transaction),
|
||||
modifier: None,
|
||||
statements: vec![],
|
||||
exception_statements: None,
|
||||
exception: None,
|
||||
has_end_keyword: false,
|
||||
})
|
||||
}
|
||||
|
@ -15131,11 +15391,56 @@ impl<'a> Parser<'a> {
|
|||
transaction,
|
||||
modifier,
|
||||
statements: vec![],
|
||||
exception_statements: None,
|
||||
exception: None,
|
||||
has_end_keyword: false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_begin_exception_end(&mut self) -> Result<Statement, ParserError> {
|
||||
let statements = self.parse_statement_list(&[Keyword::EXCEPTION, Keyword::END])?;
|
||||
|
||||
let exception = if self.parse_keyword(Keyword::EXCEPTION) {
|
||||
let mut when = Vec::new();
|
||||
|
||||
// We can have multiple `WHEN` arms so we consume all cases until `END`
|
||||
while !self.peek_keyword(Keyword::END) {
|
||||
self.expect_keyword(Keyword::WHEN)?;
|
||||
|
||||
// Each `WHEN` case can have one or more conditions, e.g.
|
||||
// WHEN EXCEPTION_1 [OR EXCEPTION_2] THEN
|
||||
// So we parse identifiers until the `THEN` keyword.
|
||||
let mut idents = Vec::new();
|
||||
|
||||
while !self.parse_keyword(Keyword::THEN) {
|
||||
let ident = self.parse_identifier()?;
|
||||
idents.push(ident);
|
||||
|
||||
self.maybe_parse(|p| p.expect_keyword(Keyword::OR))?;
|
||||
}
|
||||
|
||||
let statements = self.parse_statement_list(&[Keyword::WHEN, Keyword::END])?;
|
||||
|
||||
when.push(ExceptionWhen { idents, statements });
|
||||
}
|
||||
|
||||
Some(when)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.expect_keyword(Keyword::END)?;
|
||||
|
||||
Ok(Statement::StartTransaction {
|
||||
begin: true,
|
||||
statements,
|
||||
exception,
|
||||
has_end_keyword: true,
|
||||
transaction: None,
|
||||
modifier: None,
|
||||
modes: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_end(&mut self) -> Result<Statement, ParserError> {
|
||||
let modifier = if !self.dialect.supports_end_transaction_modifier() {
|
||||
None
|
||||
|
@ -15706,6 +16011,49 @@ impl<'a> Parser<'a> {
|
|||
Ok(sequence_options)
|
||||
}
|
||||
|
||||
/// Parse a `CREATE SERVER` statement.
|
||||
///
|
||||
/// See [Statement::CreateServer]
|
||||
pub fn parse_pg_create_server(&mut self) -> Result<Statement, ParserError> {
|
||||
let ine = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
|
||||
let name = self.parse_object_name(false)?;
|
||||
|
||||
let server_type = if self.parse_keyword(Keyword::TYPE) {
|
||||
Some(self.parse_identifier()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let version = if self.parse_keyword(Keyword::VERSION) {
|
||||
Some(self.parse_identifier()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.expect_keywords(&[Keyword::FOREIGN, Keyword::DATA, Keyword::WRAPPER])?;
|
||||
let foreign_data_wrapper = self.parse_object_name(false)?;
|
||||
|
||||
let mut options = None;
|
||||
if self.parse_keyword(Keyword::OPTIONS) {
|
||||
self.expect_token(&Token::LParen)?;
|
||||
options = Some(self.parse_comma_separated(|p| {
|
||||
let key = p.parse_identifier()?;
|
||||
let value = p.parse_identifier()?;
|
||||
Ok(CreateServerOption { key, value })
|
||||
})?);
|
||||
self.expect_token(&Token::RParen)?;
|
||||
}
|
||||
|
||||
Ok(Statement::CreateServer(CreateServerStatement {
|
||||
name,
|
||||
if_not_exists: ine,
|
||||
server_type,
|
||||
version,
|
||||
foreign_data_wrapper,
|
||||
options,
|
||||
}))
|
||||
}
|
||||
|
||||
/// The index of the first unprocessed token.
|
||||
pub fn index(&self) -> usize {
|
||||
self.index
|
||||
|
@ -15729,6 +16077,13 @@ impl<'a> Parser<'a> {
|
|||
pub fn parse_create_procedure(&mut self, or_alter: bool) -> Result<Statement, ParserError> {
|
||||
let name = self.parse_object_name(false)?;
|
||||
let params = self.parse_optional_procedure_parameters()?;
|
||||
|
||||
let language = if self.parse_keyword(Keyword::LANGUAGE) {
|
||||
Some(self.parse_identifier()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.expect_keyword_is(Keyword::AS)?;
|
||||
|
||||
let body = self.parse_conditional_statements(&[Keyword::END])?;
|
||||
|
@ -15737,6 +16092,7 @@ impl<'a> Parser<'a> {
|
|||
name,
|
||||
or_alter,
|
||||
params,
|
||||
language,
|
||||
body,
|
||||
})
|
||||
}
|
||||
|
@ -16483,6 +16839,20 @@ mod tests {
|
|||
}};
|
||||
}
|
||||
|
||||
fn mk_expected_col(name: &str) -> IndexColumn {
|
||||
IndexColumn {
|
||||
column: OrderByExpr {
|
||||
expr: Expr::Identifier(name.into()),
|
||||
options: OrderByOptions {
|
||||
asc: None,
|
||||
nulls_first: None,
|
||||
},
|
||||
with_fill: None,
|
||||
},
|
||||
operator_class: None,
|
||||
}
|
||||
}
|
||||
|
||||
let dialect =
|
||||
TestedDialects::new(vec![Box::new(GenericDialect {}), Box::new(MySqlDialect {})]);
|
||||
|
||||
|
@ -16493,7 +16863,7 @@ mod tests {
|
|||
display_as_key: false,
|
||||
name: None,
|
||||
index_type: None,
|
||||
columns: vec![Ident::new("c1")],
|
||||
columns: vec![mk_expected_col("c1")],
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -16504,7 +16874,7 @@ mod tests {
|
|||
display_as_key: true,
|
||||
name: None,
|
||||
index_type: None,
|
||||
columns: vec![Ident::new("c1")],
|
||||
columns: vec![mk_expected_col("c1")],
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -16515,7 +16885,7 @@ mod tests {
|
|||
display_as_key: false,
|
||||
name: Some(Ident::with_quote('\'', "index")),
|
||||
index_type: None,
|
||||
columns: vec![Ident::new("c1"), Ident::new("c2")],
|
||||
columns: vec![mk_expected_col("c1"), mk_expected_col("c2")],
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -16526,7 +16896,7 @@ mod tests {
|
|||
display_as_key: false,
|
||||
name: None,
|
||||
index_type: Some(IndexType::BTree),
|
||||
columns: vec![Ident::new("c1")],
|
||||
columns: vec![mk_expected_col("c1")],
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -16537,7 +16907,7 @@ mod tests {
|
|||
display_as_key: false,
|
||||
name: None,
|
||||
index_type: Some(IndexType::Hash),
|
||||
columns: vec![Ident::new("c1")],
|
||||
columns: vec![mk_expected_col("c1")],
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -16548,7 +16918,7 @@ mod tests {
|
|||
display_as_key: false,
|
||||
name: Some(Ident::new("idx_name")),
|
||||
index_type: Some(IndexType::BTree),
|
||||
columns: vec![Ident::new("c1")],
|
||||
columns: vec![mk_expected_col("c1")],
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -16559,7 +16929,7 @@ mod tests {
|
|||
display_as_key: false,
|
||||
name: Some(Ident::new("idx_name")),
|
||||
index_type: Some(IndexType::Hash),
|
||||
columns: vec![Ident::new("c1")],
|
||||
columns: vec![mk_expected_col("c1")],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -270,7 +270,7 @@ impl TestedDialects {
|
|||
tokenizer = tokenizer.with_unescape(options.unescape);
|
||||
}
|
||||
let tokens = tokenizer.tokenize().unwrap();
|
||||
assert_eq!(expected, tokens, "Tokenized differently for {:?}", dialect);
|
||||
assert_eq!(expected, tokens, "Tokenized differently for {dialect:?}");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -366,6 +366,11 @@ pub fn number(n: &str) -> Value {
|
|||
Value::Number(n.parse().unwrap(), false)
|
||||
}
|
||||
|
||||
/// Creates a [Value::SingleQuotedString]
|
||||
pub fn single_quoted_string(s: impl Into<String>) -> Value {
|
||||
Value::SingleQuotedString(s.into())
|
||||
}
|
||||
|
||||
pub fn table_alias(name: impl Into<String>) -> Option<TableAlias> {
|
||||
Some(TableAlias {
|
||||
name: Ident::new(name),
|
||||
|
@ -448,3 +453,52 @@ pub fn call(function: &str, args: impl IntoIterator<Item = Expr>) -> Expr {
|
|||
within_group: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the first index column (mysql calls it a key part) of the first index found in a
|
||||
/// [`Statement::CreateIndex`], [`Statement::CreateTable`], or [`Statement::AlterTable`].
|
||||
pub fn index_column(stmt: Statement) -> Expr {
|
||||
match stmt {
|
||||
Statement::CreateIndex(CreateIndex { columns, .. }) => {
|
||||
columns.first().unwrap().column.expr.clone()
|
||||
}
|
||||
Statement::CreateTable(CreateTable { constraints, .. }) => {
|
||||
match constraints.first().unwrap() {
|
||||
TableConstraint::Index { columns, .. } => {
|
||||
columns.first().unwrap().column.expr.clone()
|
||||
}
|
||||
TableConstraint::Unique { columns, .. } => {
|
||||
columns.first().unwrap().column.expr.clone()
|
||||
}
|
||||
TableConstraint::PrimaryKey { columns, .. } => {
|
||||
columns.first().unwrap().column.expr.clone()
|
||||
}
|
||||
TableConstraint::FulltextOrSpatial { columns, .. } => {
|
||||
columns.first().unwrap().column.expr.clone()
|
||||
}
|
||||
_ => panic!("Expected an index, unique, primary, full text, or spatial constraint (foreign key does not support general key part expressions)"),
|
||||
}
|
||||
}
|
||||
Statement::AlterTable { operations, .. } => match operations.first().unwrap() {
|
||||
AlterTableOperation::AddConstraint { constraint, .. } => {
|
||||
match constraint {
|
||||
TableConstraint::Index { columns, .. } => {
|
||||
columns.first().unwrap().column.expr.clone()
|
||||
}
|
||||
TableConstraint::Unique { columns, .. } => {
|
||||
columns.first().unwrap().column.expr.clone()
|
||||
}
|
||||
TableConstraint::PrimaryKey { columns, .. } => {
|
||||
columns.first().unwrap().column.expr.clone()
|
||||
}
|
||||
TableConstraint::FulltextOrSpatial {
|
||||
columns,
|
||||
..
|
||||
} => columns.first().unwrap().column.expr.clone(),
|
||||
_ => panic!("Expected an index, unique, primary, full text, or spatial constraint (foreign key does not support general key part expressions)"),
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected a constraint"),
|
||||
},
|
||||
_ => panic!("Expected CREATE INDEX, ALTER TABLE, or CREATE TABLE, got: {stmt:?}"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1751,7 +1751,7 @@ impl<'a> Tokenizer<'a> {
|
|||
(None, Some(tok)) => Ok(Some(tok)),
|
||||
(None, None) => self.tokenizer_error(
|
||||
chars.location(),
|
||||
format!("Expected a valid binary operator after '{}'", prefix),
|
||||
format!("Expected a valid binary operator after '{prefix}'"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -1809,7 +1809,7 @@ impl<'a> Tokenizer<'a> {
|
|||
chars.next();
|
||||
|
||||
let mut temp = String::new();
|
||||
let end_delimiter = format!("${}$", value);
|
||||
let end_delimiter = format!("${value}$");
|
||||
|
||||
loop {
|
||||
match chars.next() {
|
||||
|
@ -2402,13 +2402,13 @@ fn take_char_from_hex_digits(
|
|||
location: chars.location(),
|
||||
})?;
|
||||
let digit = next_char.to_digit(16).ok_or_else(|| TokenizerError {
|
||||
message: format!("Invalid hex digit in escaped unicode string: {}", next_char),
|
||||
message: format!("Invalid hex digit in escaped unicode string: {next_char}"),
|
||||
location: chars.location(),
|
||||
})?;
|
||||
result = result * 16 + digit;
|
||||
}
|
||||
char::from_u32(result).ok_or_else(|| TokenizerError {
|
||||
message: format!("Invalid unicode character: {:x}", result),
|
||||
message: format!("Invalid unicode character: {result:x}"),
|
||||
location: chars.location(),
|
||||
})
|
||||
}
|
||||
|
@ -3504,7 +3504,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn check_unescape(s: &str, expected: Option<&str>) {
|
||||
let s = format!("'{}'", s);
|
||||
let s = format!("'{s}'");
|
||||
let mut state = State {
|
||||
peekable: s.chars().peekable(),
|
||||
line: 0,
|
||||
|
|
|
@ -261,10 +261,10 @@ fn parse_at_at_identifier() {
|
|||
|
||||
#[test]
|
||||
fn parse_begin() {
|
||||
let sql = r#"BEGIN SELECT 1; EXCEPTION WHEN ERROR THEN SELECT 2; END"#;
|
||||
let sql = r#"BEGIN SELECT 1; EXCEPTION WHEN ERROR THEN SELECT 2; RAISE USING MESSAGE = FORMAT('ERR: %s', 'Bad'); END"#;
|
||||
let Statement::StartTransaction {
|
||||
statements,
|
||||
exception_statements,
|
||||
exception,
|
||||
has_end_keyword,
|
||||
..
|
||||
} = bigquery().verified_stmt(sql)
|
||||
|
@ -272,7 +272,10 @@ fn parse_begin() {
|
|||
unreachable!();
|
||||
};
|
||||
assert_eq!(1, statements.len());
|
||||
assert_eq!(1, exception_statements.unwrap().len());
|
||||
assert!(exception.is_some());
|
||||
|
||||
let exception = exception.unwrap();
|
||||
assert_eq!(1, exception.len());
|
||||
assert!(has_end_keyword);
|
||||
|
||||
bigquery().verified_stmt(
|
||||
|
@ -352,14 +355,16 @@ fn parse_create_view_with_options() {
|
|||
ViewColumnDef {
|
||||
name: Ident::new("age"),
|
||||
data_type: None,
|
||||
options: Some(vec![ColumnOption::Options(vec![SqlOption::KeyValue {
|
||||
key: Ident::new("description"),
|
||||
value: Expr::Value(
|
||||
Value::DoubleQuotedString("field age".to_string()).with_span(
|
||||
Span::new(Location::new(1, 42), Location::new(1, 52))
|
||||
)
|
||||
),
|
||||
}])]),
|
||||
options: Some(ColumnOptions::CommaSeparated(vec![ColumnOption::Options(
|
||||
vec![SqlOption::KeyValue {
|
||||
key: Ident::new("description"),
|
||||
value: Expr::Value(
|
||||
Value::DoubleQuotedString("field age".to_string()).with_span(
|
||||
Span::new(Location::new(1, 42), Location::new(1, 52))
|
||||
)
|
||||
),
|
||||
}]
|
||||
)])),
|
||||
},
|
||||
],
|
||||
columns
|
||||
|
@ -635,35 +640,6 @@ fn parse_nested_data_types() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_invalid_brackets() {
|
||||
let sql = "SELECT STRUCT<INT64>>(NULL)";
|
||||
assert_eq!(
|
||||
bigquery_and_generic()
|
||||
.parse_sql_statements(sql)
|
||||
.unwrap_err(),
|
||||
ParserError::ParserError("unmatched > in STRUCT literal".to_string())
|
||||
);
|
||||
|
||||
let sql = "SELECT STRUCT<STRUCT<INT64>>>(NULL)";
|
||||
assert_eq!(
|
||||
bigquery_and_generic()
|
||||
.parse_sql_statements(sql)
|
||||
.unwrap_err(),
|
||||
ParserError::ParserError("Expected: (, found: >".to_string())
|
||||
);
|
||||
|
||||
let sql = "CREATE TABLE table (x STRUCT<STRUCT<INT64>>>)";
|
||||
assert_eq!(
|
||||
bigquery_and_generic()
|
||||
.parse_sql_statements(sql)
|
||||
.unwrap_err(),
|
||||
ParserError::ParserError(
|
||||
"Expected: ',' or ')' after column definition, found: >".to_string()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_tuple_struct_literal() {
|
||||
// tuple syntax: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#tuple_syntax
|
||||
|
@ -930,7 +906,10 @@ fn parse_typed_struct_syntax_bigquery() {
|
|||
&Expr::Struct {
|
||||
values: vec![Expr::TypedString {
|
||||
data_type: DataType::Datetime(None),
|
||||
value: Value::SingleQuotedString("1999-01-01 01:23:34.45".into())
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("1999-01-01 01:23:34.45".into()),
|
||||
span: Span::empty(),
|
||||
},
|
||||
}],
|
||||
fields: vec![StructField {
|
||||
field_name: None,
|
||||
|
@ -989,9 +968,12 @@ fn parse_typed_struct_syntax_bigquery() {
|
|||
&Expr::Struct {
|
||||
values: vec![Expr::TypedString {
|
||||
data_type: DataType::JSON,
|
||||
value: Value::SingleQuotedString(
|
||||
r#"{"class" : {"students" : [{"name" : "Jane"}]}}"#.into()
|
||||
)
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString(
|
||||
r#"{"class" : {"students" : [{"name" : "Jane"}]}}"#.into()
|
||||
),
|
||||
span: Span::empty(),
|
||||
}
|
||||
}],
|
||||
fields: vec![StructField {
|
||||
field_name: None,
|
||||
|
@ -1022,7 +1004,12 @@ fn parse_typed_struct_syntax_bigquery() {
|
|||
&Expr::Struct {
|
||||
values: vec![Expr::TypedString {
|
||||
data_type: DataType::Timestamp(None, TimezoneInfo::None),
|
||||
value: Value::SingleQuotedString("2008-12-25 15:30:00 America/Los_Angeles".into())
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString(
|
||||
"2008-12-25 15:30:00 America/Los_Angeles".into()
|
||||
),
|
||||
span: Span::empty(),
|
||||
},
|
||||
}],
|
||||
fields: vec![StructField {
|
||||
field_name: None,
|
||||
|
@ -1037,7 +1024,10 @@ fn parse_typed_struct_syntax_bigquery() {
|
|||
&Expr::Struct {
|
||||
values: vec![Expr::TypedString {
|
||||
data_type: DataType::Time(None, TimezoneInfo::None),
|
||||
value: Value::SingleQuotedString("15:30:00".into())
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("15:30:00".into()),
|
||||
span: Span::empty(),
|
||||
}
|
||||
}],
|
||||
fields: vec![StructField {
|
||||
field_name: None,
|
||||
|
@ -1055,7 +1045,10 @@ fn parse_typed_struct_syntax_bigquery() {
|
|||
&Expr::Struct {
|
||||
values: vec![Expr::TypedString {
|
||||
data_type: DataType::Numeric(ExactNumberInfo::None),
|
||||
value: Value::SingleQuotedString("1".into())
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("1".into()),
|
||||
span: Span::empty(),
|
||||
}
|
||||
}],
|
||||
fields: vec![StructField {
|
||||
field_name: None,
|
||||
|
@ -1069,7 +1062,10 @@ fn parse_typed_struct_syntax_bigquery() {
|
|||
&Expr::Struct {
|
||||
values: vec![Expr::TypedString {
|
||||
data_type: DataType::BigNumeric(ExactNumberInfo::None),
|
||||
value: Value::SingleQuotedString("1".into())
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("1".into()),
|
||||
span: Span::empty(),
|
||||
}
|
||||
}],
|
||||
fields: vec![StructField {
|
||||
field_name: None,
|
||||
|
@ -1243,7 +1239,10 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
|
|||
&Expr::Struct {
|
||||
values: vec![Expr::TypedString {
|
||||
data_type: DataType::Datetime(None),
|
||||
value: Value::SingleQuotedString("1999-01-01 01:23:34.45".into())
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("1999-01-01 01:23:34.45".into()),
|
||||
span: Span::empty(),
|
||||
}
|
||||
}],
|
||||
fields: vec![StructField {
|
||||
field_name: None,
|
||||
|
@ -1302,9 +1301,12 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
|
|||
&Expr::Struct {
|
||||
values: vec![Expr::TypedString {
|
||||
data_type: DataType::JSON,
|
||||
value: Value::SingleQuotedString(
|
||||
r#"{"class" : {"students" : [{"name" : "Jane"}]}}"#.into()
|
||||
)
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString(
|
||||
r#"{"class" : {"students" : [{"name" : "Jane"}]}}"#.into()
|
||||
),
|
||||
span: Span::empty(),
|
||||
}
|
||||
}],
|
||||
fields: vec![StructField {
|
||||
field_name: None,
|
||||
|
@ -1335,7 +1337,12 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
|
|||
&Expr::Struct {
|
||||
values: vec![Expr::TypedString {
|
||||
data_type: DataType::Timestamp(None, TimezoneInfo::None),
|
||||
value: Value::SingleQuotedString("2008-12-25 15:30:00 America/Los_Angeles".into())
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString(
|
||||
"2008-12-25 15:30:00 America/Los_Angeles".into()
|
||||
),
|
||||
span: Span::empty(),
|
||||
}
|
||||
}],
|
||||
fields: vec![StructField {
|
||||
field_name: None,
|
||||
|
@ -1350,7 +1357,10 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
|
|||
&Expr::Struct {
|
||||
values: vec![Expr::TypedString {
|
||||
data_type: DataType::Time(None, TimezoneInfo::None),
|
||||
value: Value::SingleQuotedString("15:30:00".into())
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("15:30:00".into()),
|
||||
span: Span::empty(),
|
||||
}
|
||||
}],
|
||||
fields: vec![StructField {
|
||||
field_name: None,
|
||||
|
@ -1368,7 +1378,10 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
|
|||
&Expr::Struct {
|
||||
values: vec![Expr::TypedString {
|
||||
data_type: DataType::Numeric(ExactNumberInfo::None),
|
||||
value: Value::SingleQuotedString("1".into())
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("1".into()),
|
||||
span: Span::empty(),
|
||||
}
|
||||
}],
|
||||
fields: vec![StructField {
|
||||
field_name: None,
|
||||
|
@ -1382,7 +1395,10 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
|
|||
&Expr::Struct {
|
||||
values: vec![Expr::TypedString {
|
||||
data_type: DataType::BigNumeric(ExactNumberInfo::None),
|
||||
value: Value::SingleQuotedString("1".into())
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("1".into()),
|
||||
span: Span::empty(),
|
||||
}
|
||||
}],
|
||||
fields: vec![StructField {
|
||||
field_name: None,
|
||||
|
@ -2417,7 +2433,10 @@ fn test_triple_quote_typed_strings() {
|
|||
assert_eq!(
|
||||
Expr::TypedString {
|
||||
data_type: DataType::JSON,
|
||||
value: Value::TripleDoubleQuotedString(r#"{"foo":"bar's"}"#.into())
|
||||
value: ValueWithSpan {
|
||||
value: Value::TripleDoubleQuotedString(r#"{"foo":"bar's"}"#.into()),
|
||||
span: Span::empty(),
|
||||
}
|
||||
},
|
||||
expr
|
||||
);
|
||||
|
@ -2472,3 +2491,78 @@ fn test_struct_field_options() {
|
|||
")",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_struct_trailing_and_nested_bracket() {
|
||||
bigquery().verified_stmt(concat!(
|
||||
"CREATE TABLE my_table (",
|
||||
"f0 STRING, ",
|
||||
"f1 STRUCT<a STRING, b STRUCT<c INT64, d STRING>>, ",
|
||||
"f2 STRING",
|
||||
")",
|
||||
));
|
||||
|
||||
// More complex nested structs
|
||||
bigquery().verified_stmt(concat!(
|
||||
"CREATE TABLE my_table (",
|
||||
"f0 STRING, ",
|
||||
"f1 STRUCT<a STRING, b STRUCT<c INT64, d STRUCT<e STRING>>>, ",
|
||||
"f2 STRUCT<h STRING, i STRUCT<j INT64, k STRUCT<l STRUCT<m STRING>>>>, ",
|
||||
"f3 STRUCT<e STRING, f STRUCT<c INT64>>",
|
||||
")",
|
||||
));
|
||||
|
||||
// Bad case with missing closing bracket
|
||||
assert_eq!(
|
||||
ParserError::ParserError("Expected: >, found: )".to_owned()),
|
||||
bigquery()
|
||||
.parse_sql_statements("CREATE TABLE my_table(f1 STRUCT<a STRING, b INT64)")
|
||||
.unwrap_err()
|
||||
);
|
||||
|
||||
// Bad case with redundant closing bracket
|
||||
assert_eq!(
|
||||
ParserError::ParserError(
|
||||
"unmatched > after parsing data type STRUCT<a STRING, b INT64>)".to_owned()
|
||||
),
|
||||
bigquery()
|
||||
.parse_sql_statements("CREATE TABLE my_table(f1 STRUCT<a STRING, b INT64>>)")
|
||||
.unwrap_err()
|
||||
);
|
||||
|
||||
// Base case with redundant closing bracket in nested struct
|
||||
assert_eq!(
|
||||
ParserError::ParserError(
|
||||
"Expected: ',' or ')' after column definition, found: >".to_owned()
|
||||
),
|
||||
bigquery()
|
||||
.parse_sql_statements("CREATE TABLE my_table(f1 STRUCT<a STRUCT<b INT>>>, c INT64)")
|
||||
.unwrap_err()
|
||||
);
|
||||
|
||||
let sql = "SELECT STRUCT<INT64>>(NULL)";
|
||||
assert_eq!(
|
||||
bigquery_and_generic()
|
||||
.parse_sql_statements(sql)
|
||||
.unwrap_err(),
|
||||
ParserError::ParserError("unmatched > in STRUCT literal".to_string())
|
||||
);
|
||||
|
||||
let sql = "SELECT STRUCT<STRUCT<INT64>>>(NULL)";
|
||||
assert_eq!(
|
||||
bigquery_and_generic()
|
||||
.parse_sql_statements(sql)
|
||||
.unwrap_err(),
|
||||
ParserError::ParserError("Expected: (, found: >".to_string())
|
||||
);
|
||||
|
||||
let sql = "CREATE TABLE table (x STRUCT<STRUCT<INT64>>>)";
|
||||
assert_eq!(
|
||||
bigquery_and_generic()
|
||||
.parse_sql_statements(sql)
|
||||
.unwrap_err(),
|
||||
ParserError::ParserError(
|
||||
"Expected: ',' or ')' after column definition, found: >".to_string()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ use test_utils::*;
|
|||
use sqlparser::ast::Expr::{BinaryOp, Identifier};
|
||||
use sqlparser::ast::SelectItem::UnnamedExpr;
|
||||
use sqlparser::ast::TableFactor::Table;
|
||||
use sqlparser::ast::Value::Number;
|
||||
use sqlparser::ast::Value::Boolean;
|
||||
use sqlparser::ast::*;
|
||||
use sqlparser::dialect::ClickHouseDialect;
|
||||
use sqlparser::dialect::GenericDialect;
|
||||
|
@ -914,7 +914,7 @@ fn parse_create_view_with_fields_data_types() {
|
|||
}]),
|
||||
vec![]
|
||||
)),
|
||||
options: None
|
||||
options: None,
|
||||
},
|
||||
ViewColumnDef {
|
||||
name: "f".into(),
|
||||
|
@ -926,7 +926,7 @@ fn parse_create_view_with_fields_data_types() {
|
|||
}]),
|
||||
vec![]
|
||||
)),
|
||||
options: None
|
||||
options: None,
|
||||
},
|
||||
]
|
||||
);
|
||||
|
@ -965,38 +965,103 @@ fn parse_limit_by() {
|
|||
|
||||
#[test]
|
||||
fn parse_settings_in_query() {
|
||||
match clickhouse_and_generic()
|
||||
.verified_stmt(r#"SELECT * FROM t SETTINGS max_threads = 1, max_block_size = 10000"#)
|
||||
{
|
||||
Statement::Query(query) => {
|
||||
assert_eq!(
|
||||
query.settings,
|
||||
Some(vec![
|
||||
Setting {
|
||||
key: Ident::new("max_threads"),
|
||||
value: Number("1".parse().unwrap(), false)
|
||||
},
|
||||
Setting {
|
||||
key: Ident::new("max_block_size"),
|
||||
value: Number("10000".parse().unwrap(), false)
|
||||
},
|
||||
])
|
||||
);
|
||||
fn check_settings(sql: &str, expected: Vec<Setting>) {
|
||||
match clickhouse_and_generic().verified_stmt(sql) {
|
||||
Statement::Query(q) => {
|
||||
assert_eq!(q.settings, Some(expected));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
for (sql, expected_settings) in [
|
||||
(
|
||||
r#"SELECT * FROM t SETTINGS max_threads = 1, max_block_size = 10000"#,
|
||||
vec![
|
||||
Setting {
|
||||
key: Ident::new("max_threads"),
|
||||
value: Expr::value(number("1")),
|
||||
},
|
||||
Setting {
|
||||
key: Ident::new("max_block_size"),
|
||||
value: Expr::value(number("10000")),
|
||||
},
|
||||
],
|
||||
),
|
||||
(
|
||||
r#"SELECT * FROM t SETTINGS additional_table_filters = {'table_1': 'x != 2'}"#,
|
||||
vec![Setting {
|
||||
key: Ident::new("additional_table_filters"),
|
||||
value: Expr::Dictionary(vec![DictionaryField {
|
||||
key: Ident::with_quote('\'', "table_1"),
|
||||
value: Expr::value(single_quoted_string("x != 2")).into(),
|
||||
}]),
|
||||
}],
|
||||
),
|
||||
(
|
||||
r#"SELECT * FROM t SETTINGS additional_result_filter = 'x != 2', query_plan_optimize_lazy_materialization = false"#,
|
||||
vec![
|
||||
Setting {
|
||||
key: Ident::new("additional_result_filter"),
|
||||
value: Expr::value(single_quoted_string("x != 2")),
|
||||
},
|
||||
Setting {
|
||||
key: Ident::new("query_plan_optimize_lazy_materialization"),
|
||||
value: Expr::value(Boolean(false)),
|
||||
},
|
||||
],
|
||||
),
|
||||
] {
|
||||
check_settings(sql, expected_settings);
|
||||
}
|
||||
|
||||
let invalid_cases = vec![
|
||||
"SELECT * FROM t SETTINGS a",
|
||||
"SELECT * FROM t SETTINGS a=",
|
||||
"SELECT * FROM t SETTINGS a=1, b",
|
||||
"SELECT * FROM t SETTINGS a=1, b=",
|
||||
"SELECT * FROM t SETTINGS a=1, b=c",
|
||||
("SELECT * FROM t SETTINGS a", "Expected: =, found: EOF"),
|
||||
(
|
||||
"SELECT * FROM t SETTINGS a=",
|
||||
"Expected: an expression, found: EOF",
|
||||
),
|
||||
("SELECT * FROM t SETTINGS a=1, b", "Expected: =, found: EOF"),
|
||||
(
|
||||
"SELECT * FROM t SETTINGS a=1, b=",
|
||||
"Expected: an expression, found: EOF",
|
||||
),
|
||||
(
|
||||
"SELECT * FROM t SETTINGS a = {",
|
||||
"Expected: identifier, found: EOF",
|
||||
),
|
||||
(
|
||||
"SELECT * FROM t SETTINGS a = {'b'",
|
||||
"Expected: :, found: EOF",
|
||||
),
|
||||
(
|
||||
"SELECT * FROM t SETTINGS a = {'b': ",
|
||||
"Expected: an expression, found: EOF",
|
||||
),
|
||||
(
|
||||
"SELECT * FROM t SETTINGS a = {'b': 'c',}",
|
||||
"Expected: identifier, found: }",
|
||||
),
|
||||
(
|
||||
"SELECT * FROM t SETTINGS a = {'b': 'c', 'd'}",
|
||||
"Expected: :, found: }",
|
||||
),
|
||||
(
|
||||
"SELECT * FROM t SETTINGS a = {'b': 'c', 'd': }",
|
||||
"Expected: an expression, found: }",
|
||||
),
|
||||
(
|
||||
"SELECT * FROM t SETTINGS a = {ANY(b)}",
|
||||
"Expected: :, found: (",
|
||||
),
|
||||
];
|
||||
for sql in invalid_cases {
|
||||
clickhouse_and_generic()
|
||||
.parse_sql_statements(sql)
|
||||
.expect_err("Expected: SETTINGS key = value, found: ");
|
||||
for (sql, error_msg) in invalid_cases {
|
||||
assert_eq!(
|
||||
clickhouse_and_generic()
|
||||
.parse_sql_statements(sql)
|
||||
.unwrap_err(),
|
||||
ParserError(error_msg.to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
|
@ -1345,7 +1410,7 @@ fn parse_use() {
|
|||
for object_name in &valid_object_names {
|
||||
// Test single identifier without quotes
|
||||
assert_eq!(
|
||||
clickhouse().verified_stmt(&format!("USE {}", object_name)),
|
||||
clickhouse().verified_stmt(&format!("USE {object_name}")),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::new(
|
||||
object_name.to_string()
|
||||
)])))
|
||||
|
@ -1353,7 +1418,7 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test single identifier with different type of quotes
|
||||
assert_eq!(
|
||||
clickhouse().verified_stmt(&format!("USE {0}{1}{0}", quote, object_name)),
|
||||
clickhouse().verified_stmt(&format!("USE {quote}{object_name}{quote}")),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
object_name.to_string(),
|
||||
|
@ -1367,7 +1432,7 @@ fn parse_use() {
|
|||
fn test_query_with_format_clause() {
|
||||
let format_options = vec!["TabSeparated", "JSONCompact", "NULL"];
|
||||
for format in &format_options {
|
||||
let sql = format!("SELECT * FROM t FORMAT {}", format);
|
||||
let sql = format!("SELECT * FROM t FORMAT {format}");
|
||||
match clickhouse_and_generic().verified_stmt(&sql) {
|
||||
Statement::Query(query) => {
|
||||
if *format == "NULL" {
|
||||
|
@ -1550,11 +1615,11 @@ fn parse_select_table_function_settings() {
|
|||
settings: Some(vec![
|
||||
Setting {
|
||||
key: "s0".into(),
|
||||
value: Value::Number("3".parse().unwrap(), false),
|
||||
value: Expr::value(number("3")),
|
||||
},
|
||||
Setting {
|
||||
key: "s1".into(),
|
||||
value: Value::SingleQuotedString("s".into()),
|
||||
value: Expr::value(single_quoted_string("s")),
|
||||
},
|
||||
]),
|
||||
},
|
||||
|
@ -1575,11 +1640,11 @@ fn parse_select_table_function_settings() {
|
|||
settings: Some(vec![
|
||||
Setting {
|
||||
key: "s0".into(),
|
||||
value: Value::Number("3".parse().unwrap(), false),
|
||||
value: Expr::value(number("3")),
|
||||
},
|
||||
Setting {
|
||||
key: "s1".into(),
|
||||
value: Value::SingleQuotedString("s".into()),
|
||||
value: Expr::value(single_quoted_string("s")),
|
||||
},
|
||||
]),
|
||||
},
|
||||
|
@ -1589,7 +1654,6 @@ fn parse_select_table_function_settings() {
|
|||
"SELECT * FROM t(SETTINGS a=)",
|
||||
"SELECT * FROM t(SETTINGS a=1, b)",
|
||||
"SELECT * FROM t(SETTINGS a=1, b=)",
|
||||
"SELECT * FROM t(SETTINGS a=1, b=c)",
|
||||
];
|
||||
for sql in invalid_cases {
|
||||
clickhouse_and_generic()
|
||||
|
|
|
@ -2225,7 +2225,7 @@ fn parse_in_subquery() {
|
|||
assert_eq!(
|
||||
Expr::InSubquery {
|
||||
expr: Box::new(Expr::Identifier(Ident::new("segment"))),
|
||||
subquery: verified_query("SELECT segm FROM bar").body,
|
||||
subquery: Box::new(verified_query("SELECT segm FROM bar")),
|
||||
negated: false,
|
||||
},
|
||||
select.selection.unwrap()
|
||||
|
@ -2239,7 +2239,9 @@ fn parse_in_union() {
|
|||
assert_eq!(
|
||||
Expr::InSubquery {
|
||||
expr: Box::new(Expr::Identifier(Ident::new("segment"))),
|
||||
subquery: verified_query("(SELECT segm FROM bar) UNION (SELECT segm FROM bar2)").body,
|
||||
subquery: Box::new(verified_query(
|
||||
"(SELECT segm FROM bar) UNION (SELECT segm FROM bar2)"
|
||||
)),
|
||||
negated: false,
|
||||
},
|
||||
select.selection.unwrap()
|
||||
|
@ -3561,7 +3563,7 @@ fn test_double_value() {
|
|||
for (input, expected) in test_cases {
|
||||
for (i, expr) in input.iter().enumerate() {
|
||||
if let Statement::Query(query) =
|
||||
dialects.one_statement_parses_to(&format!("SELECT {}", expr), "")
|
||||
dialects.one_statement_parses_to(&format!("SELECT {expr}"), "")
|
||||
{
|
||||
if let SetExpr::Select(select) = *query.body {
|
||||
assert_eq!(expected[i], select.projection[0]);
|
||||
|
@ -4021,13 +4023,13 @@ fn parse_create_table_column_constraint_characteristics() {
|
|||
syntax
|
||||
};
|
||||
|
||||
let sql = format!("CREATE TABLE t (a int UNIQUE {})", syntax);
|
||||
let sql = format!("CREATE TABLE t (a int UNIQUE {syntax})");
|
||||
let expected_clause = if syntax.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" {syntax}")
|
||||
};
|
||||
let expected = format!("CREATE TABLE t (a INT UNIQUE{})", expected_clause);
|
||||
let expected = format!("CREATE TABLE t (a INT UNIQUE{expected_clause})");
|
||||
let ast = one_statement_parses_to(&sql, &expected);
|
||||
|
||||
let expected_value = if deferrable.is_some() || initially.is_some() || enforced.is_some() {
|
||||
|
@ -4954,7 +4956,7 @@ fn parse_alter_table_constraints() {
|
|||
match alter_table_op(verified_stmt(&format!(
|
||||
"ALTER TABLE tab ADD {constraint_text}"
|
||||
))) {
|
||||
AlterTableOperation::AddConstraint(constraint) => {
|
||||
AlterTableOperation::AddConstraint { constraint, .. } => {
|
||||
assert_eq!(constraint_text, constraint.to_string());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
|
@ -5055,22 +5057,21 @@ fn parse_alter_table_alter_column_type() {
|
|||
AlterColumnOperation::SetDataType {
|
||||
data_type: DataType::Text,
|
||||
using: None,
|
||||
had_set: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
verified_stmt(&format!("{alter_stmt} ALTER COLUMN is_active TYPE TEXT"));
|
||||
|
||||
let dialect = TestedDialects::new(vec![Box::new(GenericDialect {})]);
|
||||
let dialects = all_dialects_where(|d| d.supports_alter_column_type_using());
|
||||
dialects.verified_stmt(&format!(
|
||||
"{alter_stmt} ALTER COLUMN is_active SET DATA TYPE TEXT USING 'text'"
|
||||
));
|
||||
|
||||
let res =
|
||||
dialect.parse_sql_statements(&format!("{alter_stmt} ALTER COLUMN is_active TYPE TEXT"));
|
||||
assert_eq!(
|
||||
ParserError::ParserError("Expected: SET/DROP NOT NULL, SET DEFAULT, or SET DATA TYPE after ALTER COLUMN, found: TYPE".to_string()),
|
||||
res.unwrap_err()
|
||||
);
|
||||
|
||||
let res = dialect.parse_sql_statements(&format!(
|
||||
let dialects = all_dialects_except(|d| d.supports_alter_column_type_using());
|
||||
let res = dialects.parse_sql_statements(&format!(
|
||||
"{alter_stmt} ALTER COLUMN is_active SET DATA TYPE TEXT USING 'text'"
|
||||
));
|
||||
assert_eq!(
|
||||
|
@ -5850,7 +5851,10 @@ fn parse_literal_date() {
|
|||
assert_eq!(
|
||||
&Expr::TypedString {
|
||||
data_type: DataType::Date,
|
||||
value: Value::SingleQuotedString("1999-01-01".into()),
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("1999-01-01".into()),
|
||||
span: Span::empty(),
|
||||
}
|
||||
},
|
||||
expr_from_projection(only(&select.projection)),
|
||||
);
|
||||
|
@ -5863,7 +5867,10 @@ fn parse_literal_time() {
|
|||
assert_eq!(
|
||||
&Expr::TypedString {
|
||||
data_type: DataType::Time(None, TimezoneInfo::None),
|
||||
value: Value::SingleQuotedString("01:23:34".into()),
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("01:23:34".into()),
|
||||
span: Span::empty(),
|
||||
},
|
||||
},
|
||||
expr_from_projection(only(&select.projection)),
|
||||
);
|
||||
|
@ -5876,7 +5883,10 @@ fn parse_literal_datetime() {
|
|||
assert_eq!(
|
||||
&Expr::TypedString {
|
||||
data_type: DataType::Datetime(None),
|
||||
value: Value::SingleQuotedString("1999-01-01 01:23:34.45".into()),
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("1999-01-01 01:23:34.45".into()),
|
||||
span: Span::empty(),
|
||||
},
|
||||
},
|
||||
expr_from_projection(only(&select.projection)),
|
||||
);
|
||||
|
@ -5889,7 +5899,10 @@ fn parse_literal_timestamp_without_time_zone() {
|
|||
assert_eq!(
|
||||
&Expr::TypedString {
|
||||
data_type: DataType::Timestamp(None, TimezoneInfo::None),
|
||||
value: Value::SingleQuotedString("1999-01-01 01:23:34".into()),
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("1999-01-01 01:23:34".into()),
|
||||
span: Span::empty(),
|
||||
},
|
||||
},
|
||||
expr_from_projection(only(&select.projection)),
|
||||
);
|
||||
|
@ -5904,7 +5917,10 @@ fn parse_literal_timestamp_with_time_zone() {
|
|||
assert_eq!(
|
||||
&Expr::TypedString {
|
||||
data_type: DataType::Timestamp(None, TimezoneInfo::Tz),
|
||||
value: Value::SingleQuotedString("1999-01-01 01:23:34Z".into()),
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("1999-01-01 01:23:34Z".into()),
|
||||
span: Span::empty(),
|
||||
},
|
||||
},
|
||||
expr_from_projection(only(&select.projection)),
|
||||
);
|
||||
|
@ -6476,8 +6492,9 @@ fn parse_json_keyword() {
|
|||
assert_eq!(
|
||||
&Expr::TypedString {
|
||||
data_type: DataType::JSON,
|
||||
value: Value::SingleQuotedString(
|
||||
r#"{
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString(
|
||||
r#"{
|
||||
"id": 10,
|
||||
"type": "fruit",
|
||||
"name": "apple",
|
||||
|
@ -6497,8 +6514,10 @@ fn parse_json_keyword() {
|
|||
]
|
||||
}
|
||||
}"#
|
||||
.to_string()
|
||||
)
|
||||
.to_string()
|
||||
),
|
||||
span: Span::empty(),
|
||||
}
|
||||
},
|
||||
expr_from_projection(only(&select.projection)),
|
||||
);
|
||||
|
@ -6510,7 +6529,10 @@ fn parse_typed_strings() {
|
|||
assert_eq!(
|
||||
Expr::TypedString {
|
||||
data_type: DataType::JSON,
|
||||
value: Value::SingleQuotedString(r#"{"foo":"bar"}"#.into())
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString(r#"{"foo":"bar"}"#.into()),
|
||||
span: Span::empty(),
|
||||
}
|
||||
},
|
||||
expr
|
||||
);
|
||||
|
@ -6528,7 +6550,10 @@ fn parse_bignumeric_keyword() {
|
|||
assert_eq!(
|
||||
&Expr::TypedString {
|
||||
data_type: DataType::BigNumeric(ExactNumberInfo::None),
|
||||
value: Value::SingleQuotedString(r#"0"#.into())
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString(r#"0"#.into()),
|
||||
span: Span::empty(),
|
||||
}
|
||||
},
|
||||
expr_from_projection(only(&select.projection)),
|
||||
);
|
||||
|
@ -6539,7 +6564,10 @@ fn parse_bignumeric_keyword() {
|
|||
assert_eq!(
|
||||
&Expr::TypedString {
|
||||
data_type: DataType::BigNumeric(ExactNumberInfo::None),
|
||||
value: Value::SingleQuotedString(r#"123456"#.into())
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString(r#"123456"#.into()),
|
||||
span: Span::empty(),
|
||||
}
|
||||
},
|
||||
expr_from_projection(only(&select.projection)),
|
||||
);
|
||||
|
@ -6550,7 +6578,10 @@ fn parse_bignumeric_keyword() {
|
|||
assert_eq!(
|
||||
&Expr::TypedString {
|
||||
data_type: DataType::BigNumeric(ExactNumberInfo::None),
|
||||
value: Value::SingleQuotedString(r#"-3.14"#.into())
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString(r#"-3.14"#.into()),
|
||||
span: Span::empty(),
|
||||
}
|
||||
},
|
||||
expr_from_projection(only(&select.projection)),
|
||||
);
|
||||
|
@ -6561,7 +6592,10 @@ fn parse_bignumeric_keyword() {
|
|||
assert_eq!(
|
||||
&Expr::TypedString {
|
||||
data_type: DataType::BigNumeric(ExactNumberInfo::None),
|
||||
value: Value::SingleQuotedString(r#"-0.54321"#.into())
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString(r#"-0.54321"#.into()),
|
||||
span: Span::empty(),
|
||||
}
|
||||
},
|
||||
expr_from_projection(only(&select.projection)),
|
||||
);
|
||||
|
@ -6572,7 +6606,10 @@ fn parse_bignumeric_keyword() {
|
|||
assert_eq!(
|
||||
&Expr::TypedString {
|
||||
data_type: DataType::BigNumeric(ExactNumberInfo::None),
|
||||
value: Value::SingleQuotedString(r#"1.23456e05"#.into())
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString(r#"1.23456e05"#.into()),
|
||||
span: Span::empty(),
|
||||
}
|
||||
},
|
||||
expr_from_projection(only(&select.projection)),
|
||||
);
|
||||
|
@ -6583,7 +6620,10 @@ fn parse_bignumeric_keyword() {
|
|||
assert_eq!(
|
||||
&Expr::TypedString {
|
||||
data_type: DataType::BigNumeric(ExactNumberInfo::None),
|
||||
value: Value::SingleQuotedString(r#"-9.876e-3"#.into())
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString(r#"-9.876e-3"#.into()),
|
||||
span: Span::empty(),
|
||||
}
|
||||
},
|
||||
expr_from_projection(only(&select.projection)),
|
||||
);
|
||||
|
@ -7497,7 +7537,7 @@ fn parse_cte_in_data_modification_statements() {
|
|||
assert_eq!(query.with.unwrap().to_string(), "WITH x AS (SELECT 1)");
|
||||
assert!(matches!(*query.body, SetExpr::Update(_)));
|
||||
}
|
||||
other => panic!("Expected: UPDATE, got: {:?}", other),
|
||||
other => panic!("Expected: UPDATE, got: {other:?}"),
|
||||
}
|
||||
|
||||
match verified_stmt("WITH t (x) AS (SELECT 9) DELETE FROM q WHERE id IN (SELECT x FROM t)") {
|
||||
|
@ -7505,7 +7545,7 @@ fn parse_cte_in_data_modification_statements() {
|
|||
assert_eq!(query.with.unwrap().to_string(), "WITH t (x) AS (SELECT 9)");
|
||||
assert!(matches!(*query.body, SetExpr::Delete(_)));
|
||||
}
|
||||
other => panic!("Expected: DELETE, got: {:?}", other),
|
||||
other => panic!("Expected: DELETE, got: {other:?}"),
|
||||
}
|
||||
|
||||
match verified_stmt("WITH x AS (SELECT 42) INSERT INTO t SELECT foo FROM x") {
|
||||
|
@ -7513,7 +7553,7 @@ fn parse_cte_in_data_modification_statements() {
|
|||
assert_eq!(query.with.unwrap().to_string(), "WITH x AS (SELECT 42)");
|
||||
assert!(matches!(*query.body, SetExpr::Insert(_)));
|
||||
}
|
||||
other => panic!("Expected: INSERT, got: {:?}", other),
|
||||
other => panic!("Expected: INSERT, got: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7760,7 +7800,6 @@ fn parse_trim() {
|
|||
Box::new(MySqlDialect {}),
|
||||
//Box::new(BigQueryDialect {}),
|
||||
Box::new(SQLiteDialect {}),
|
||||
Box::new(DuckDbDialect {}),
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
|
@ -7988,7 +8027,7 @@ fn parse_create_view_with_columns() {
|
|||
.map(|name| ViewColumnDef {
|
||||
name,
|
||||
data_type: None,
|
||||
options: None
|
||||
options: None,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
@ -8592,8 +8631,11 @@ fn lateral_function() {
|
|||
#[test]
|
||||
fn parse_start_transaction() {
|
||||
let dialects = all_dialects_except(|d|
|
||||
// BigQuery does not support this syntax
|
||||
d.is::<BigQueryDialect>());
|
||||
// BigQuery and Snowflake does not support this syntax
|
||||
//
|
||||
// BigQuery: <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#begin_transaction>
|
||||
// Snowflake: <https://docs.snowflake.com/en/sql-reference/sql/begin>
|
||||
d.is::<BigQueryDialect>() || d.is::<SnowflakeDialect>());
|
||||
match dialects
|
||||
.verified_stmt("START TRANSACTION READ ONLY, READ WRITE, ISOLATION LEVEL SERIALIZABLE")
|
||||
{
|
||||
|
@ -9381,9 +9423,11 @@ fn parse_grant() {
|
|||
verified_stmt("GRANT SELECT ON VIEW view1 TO ROLE role1");
|
||||
verified_stmt("GRANT EXEC ON my_sp TO runner");
|
||||
verified_stmt("GRANT UPDATE ON my_table TO updater_role AS dbo");
|
||||
|
||||
all_dialects_where(|d| d.identifier_quote_style("none") == Some('['))
|
||||
.verified_stmt("GRANT SELECT ON [my_table] TO [public]");
|
||||
verified_stmt("GRANT SELECT ON FUTURE SCHEMAS IN DATABASE db1 TO ROLE role1");
|
||||
verified_stmt("GRANT SELECT ON FUTURE TABLES IN SCHEMA db1.sc1 TO ROLE role1");
|
||||
verified_stmt("GRANT SELECT ON FUTURE VIEWS IN SCHEMA db1.sc1 TO ROLE role1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -10038,7 +10082,7 @@ fn parse_offset_and_limit() {
|
|||
#[test]
|
||||
fn parse_time_functions() {
|
||||
fn test_time_function(func_name: &'static str) {
|
||||
let sql = format!("SELECT {}()", func_name);
|
||||
let sql = format!("SELECT {func_name}()");
|
||||
let select = verified_only_select(&sql);
|
||||
let select_localtime_func_call_ast = Function {
|
||||
name: ObjectName::from(vec![Ident::new(func_name)]),
|
||||
|
@ -10060,7 +10104,7 @@ fn parse_time_functions() {
|
|||
);
|
||||
|
||||
// Validating Parenthesis
|
||||
let sql_without_parens = format!("SELECT {}", func_name);
|
||||
let sql_without_parens = format!("SELECT {func_name}");
|
||||
let mut ast_without_parens = select_localtime_func_call_ast;
|
||||
ast_without_parens.args = FunctionArguments::None;
|
||||
assert_eq!(
|
||||
|
@ -11061,10 +11105,17 @@ fn parse_non_latin_identifiers() {
|
|||
Box::new(RedshiftSqlDialect {}),
|
||||
Box::new(MySqlDialect {}),
|
||||
]);
|
||||
|
||||
supported_dialects.verified_stmt("SELECT a.説明 FROM test.public.inter01 AS a");
|
||||
supported_dialects.verified_stmt("SELECT a.説明 FROM inter01 AS a, inter01_transactions AS b WHERE a.説明 = b.取引 GROUP BY a.説明");
|
||||
supported_dialects.verified_stmt("SELECT 説明, hühnervögel, garçon, Москва, 東京 FROM inter01");
|
||||
|
||||
let supported_dialects = TestedDialects::new(vec![
|
||||
Box::new(GenericDialect {}),
|
||||
Box::new(DuckDbDialect {}),
|
||||
Box::new(PostgreSqlDialect {}),
|
||||
Box::new(MsSqlDialect {}),
|
||||
Box::new(MySqlDialect {}),
|
||||
]);
|
||||
assert!(supported_dialects
|
||||
.parse_sql_statements("SELECT 💝 FROM table1")
|
||||
.is_err());
|
||||
|
@ -14301,7 +14352,7 @@ fn overflow() {
|
|||
let expr = std::iter::repeat_n("1", 1000)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" + ");
|
||||
let sql = format!("SELECT {}", expr);
|
||||
let sql = format!("SELECT {expr}");
|
||||
|
||||
let mut statements = Parser::parse_sql(&GenericDialect {}, sql.as_str()).unwrap();
|
||||
let statement = statements.pop().unwrap();
|
||||
|
@ -14601,7 +14652,7 @@ fn test_conditional_statement_span() {
|
|||
else_block.unwrap().span()
|
||||
);
|
||||
}
|
||||
stmt => panic!("Unexpected statement: {:?}", stmt),
|
||||
stmt => panic!("Unexpected statement: {stmt:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14821,7 +14872,10 @@ fn test_geometry_type() {
|
|||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::TypedString {
|
||||
data_type: DataType::GeometricType(GeometricTypeKind::Point),
|
||||
value: Value::SingleQuotedString("1,2".to_string()),
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("1,2".to_string()),
|
||||
span: Span::empty(),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -14830,7 +14884,10 @@ fn test_geometry_type() {
|
|||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::TypedString {
|
||||
data_type: DataType::GeometricType(GeometricTypeKind::Line),
|
||||
value: Value::SingleQuotedString("1,2,3,4".to_string()),
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("1,2,3,4".to_string()),
|
||||
span: Span::empty(),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -14839,7 +14896,10 @@ fn test_geometry_type() {
|
|||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::TypedString {
|
||||
data_type: DataType::GeometricType(GeometricTypeKind::GeometricPath),
|
||||
value: Value::SingleQuotedString("1,2,3,4".to_string()),
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("1,2,3,4".to_string()),
|
||||
span: Span::empty(),
|
||||
},
|
||||
}
|
||||
);
|
||||
let sql = "box '1,2,3,4'";
|
||||
|
@ -14847,7 +14907,10 @@ fn test_geometry_type() {
|
|||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::TypedString {
|
||||
data_type: DataType::GeometricType(GeometricTypeKind::GeometricBox),
|
||||
value: Value::SingleQuotedString("1,2,3,4".to_string()),
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("1,2,3,4".to_string()),
|
||||
span: Span::empty(),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -14856,7 +14919,10 @@ fn test_geometry_type() {
|
|||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::TypedString {
|
||||
data_type: DataType::GeometricType(GeometricTypeKind::Circle),
|
||||
value: Value::SingleQuotedString("1,2,3".to_string()),
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("1,2,3".to_string()),
|
||||
span: Span::empty(),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -14865,7 +14931,10 @@ fn test_geometry_type() {
|
|||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::TypedString {
|
||||
data_type: DataType::GeometricType(GeometricTypeKind::Polygon),
|
||||
value: Value::SingleQuotedString("1,2,3,4".to_string()),
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("1,2,3,4".to_string()),
|
||||
span: Span::empty(),
|
||||
},
|
||||
}
|
||||
);
|
||||
let sql = "lseg '1,2,3,4'";
|
||||
|
@ -14873,7 +14942,10 @@ fn test_geometry_type() {
|
|||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::TypedString {
|
||||
data_type: DataType::GeometricType(GeometricTypeKind::LineSegment),
|
||||
value: Value::SingleQuotedString("1,2,3,4".to_string()),
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("1,2,3,4".to_string()),
|
||||
span: Span::empty(),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -15210,10 +15282,426 @@ fn parse_pipeline_operator() {
|
|||
dialects.verified_stmt("SELECT * FROM tbl |> TABLESAMPLE SYSTEM (50 PERCENT)");
|
||||
dialects.verified_stmt("SELECT * FROM tbl |> TABLESAMPLE SYSTEM (50) REPEATABLE (10)");
|
||||
|
||||
// rename pipe operator
|
||||
dialects.verified_stmt("SELECT * FROM users |> RENAME old_name AS new_name");
|
||||
dialects.verified_stmt("SELECT * FROM users |> RENAME id AS user_id, name AS user_name");
|
||||
dialects.verified_query_with_canonical(
|
||||
"SELECT * FROM users |> RENAME id user_id",
|
||||
"SELECT * FROM users |> RENAME id AS user_id",
|
||||
);
|
||||
|
||||
// union pipe operator
|
||||
dialects.verified_stmt("SELECT * FROM users |> UNION ALL (SELECT * FROM admins)");
|
||||
dialects.verified_stmt("SELECT * FROM users |> UNION DISTINCT (SELECT * FROM admins)");
|
||||
dialects.verified_stmt("SELECT * FROM users |> UNION (SELECT * FROM admins)");
|
||||
|
||||
// union pipe operator with multiple queries
|
||||
dialects.verified_stmt(
|
||||
"SELECT * FROM users |> UNION ALL (SELECT * FROM admins), (SELECT * FROM guests)",
|
||||
);
|
||||
dialects.verified_stmt("SELECT * FROM users |> UNION DISTINCT (SELECT * FROM admins), (SELECT * FROM guests), (SELECT * FROM employees)");
|
||||
dialects.verified_stmt(
|
||||
"SELECT * FROM users |> UNION (SELECT * FROM admins), (SELECT * FROM guests)",
|
||||
);
|
||||
|
||||
// union pipe operator with BY NAME modifier
|
||||
dialects.verified_stmt("SELECT * FROM users |> UNION BY NAME (SELECT * FROM admins)");
|
||||
dialects.verified_stmt("SELECT * FROM users |> UNION ALL BY NAME (SELECT * FROM admins)");
|
||||
dialects.verified_stmt("SELECT * FROM users |> UNION DISTINCT BY NAME (SELECT * FROM admins)");
|
||||
|
||||
// union pipe operator with BY NAME and multiple queries
|
||||
dialects.verified_stmt(
|
||||
"SELECT * FROM users |> UNION BY NAME (SELECT * FROM admins), (SELECT * FROM guests)",
|
||||
);
|
||||
|
||||
// intersect pipe operator (BigQuery requires DISTINCT modifier for INTERSECT)
|
||||
dialects.verified_stmt("SELECT * FROM users |> INTERSECT DISTINCT (SELECT * FROM admins)");
|
||||
|
||||
// intersect pipe operator with BY NAME modifier
|
||||
dialects
|
||||
.verified_stmt("SELECT * FROM users |> INTERSECT DISTINCT BY NAME (SELECT * FROM admins)");
|
||||
|
||||
// intersect pipe operator with multiple queries
|
||||
dialects.verified_stmt(
|
||||
"SELECT * FROM users |> INTERSECT DISTINCT (SELECT * FROM admins), (SELECT * FROM guests)",
|
||||
);
|
||||
|
||||
// intersect pipe operator with BY NAME and multiple queries
|
||||
dialects.verified_stmt("SELECT * FROM users |> INTERSECT DISTINCT BY NAME (SELECT * FROM admins), (SELECT * FROM guests)");
|
||||
|
||||
// except pipe operator (BigQuery requires DISTINCT modifier for EXCEPT)
|
||||
dialects.verified_stmt("SELECT * FROM users |> EXCEPT DISTINCT (SELECT * FROM admins)");
|
||||
|
||||
// except pipe operator with BY NAME modifier
|
||||
dialects.verified_stmt("SELECT * FROM users |> EXCEPT DISTINCT BY NAME (SELECT * FROM admins)");
|
||||
|
||||
// except pipe operator with multiple queries
|
||||
dialects.verified_stmt(
|
||||
"SELECT * FROM users |> EXCEPT DISTINCT (SELECT * FROM admins), (SELECT * FROM guests)",
|
||||
);
|
||||
|
||||
// except pipe operator with BY NAME and multiple queries
|
||||
dialects.verified_stmt("SELECT * FROM users |> EXCEPT DISTINCT BY NAME (SELECT * FROM admins), (SELECT * FROM guests)");
|
||||
|
||||
// call pipe operator
|
||||
dialects.verified_stmt("SELECT * FROM users |> CALL my_function()");
|
||||
dialects.verified_stmt("SELECT * FROM users |> CALL process_data(5, 'test')");
|
||||
dialects.verified_stmt(
|
||||
"SELECT * FROM users |> CALL namespace.function_name(col1, col2, 'literal')",
|
||||
);
|
||||
|
||||
// call pipe operator with complex arguments
|
||||
dialects.verified_stmt("SELECT * FROM users |> CALL transform_data(col1 + col2)");
|
||||
dialects.verified_stmt("SELECT * FROM users |> CALL analyze_data('param1', 100, true)");
|
||||
|
||||
// call pipe operator with aliases
|
||||
dialects.verified_stmt("SELECT * FROM input_table |> CALL tvf1(arg1) AS al");
|
||||
dialects.verified_stmt("SELECT * FROM users |> CALL process_data(5) AS result_table");
|
||||
dialects.verified_stmt("SELECT * FROM users |> CALL namespace.func() AS my_alias");
|
||||
|
||||
// multiple call pipe operators in sequence
|
||||
dialects.verified_stmt("SELECT * FROM input_table |> CALL tvf1(arg1) |> CALL tvf2(arg2, arg3)");
|
||||
dialects.verified_stmt(
|
||||
"SELECT * FROM data |> CALL transform(col1) |> CALL validate() |> CALL process(param)",
|
||||
);
|
||||
|
||||
// multiple call pipe operators with aliases
|
||||
dialects.verified_stmt(
|
||||
"SELECT * FROM input_table |> CALL tvf1(arg1) AS step1 |> CALL tvf2(arg2) AS step2",
|
||||
);
|
||||
dialects.verified_stmt(
|
||||
"SELECT * FROM data |> CALL preprocess() AS clean_data |> CALL analyze(mode) AS results",
|
||||
);
|
||||
|
||||
// call pipe operators mixed with other pipe operators
|
||||
dialects.verified_stmt(
|
||||
"SELECT * FROM users |> CALL transform() |> WHERE status = 'active' |> CALL process(param)",
|
||||
);
|
||||
dialects.verified_stmt(
|
||||
"SELECT * FROM data |> CALL preprocess() AS clean |> SELECT col1, col2 |> CALL validate()",
|
||||
);
|
||||
|
||||
// pivot pipe operator
|
||||
dialects.verified_stmt(
|
||||
"SELECT * FROM monthly_sales |> PIVOT(SUM(amount) FOR quarter IN ('Q1', 'Q2', 'Q3', 'Q4'))",
|
||||
);
|
||||
dialects.verified_stmt("SELECT * FROM sales_data |> PIVOT(AVG(revenue) FOR region IN ('North', 'South', 'East', 'West'))");
|
||||
|
||||
// pivot pipe operator with multiple aggregate functions
|
||||
dialects.verified_stmt("SELECT * FROM data |> PIVOT(SUM(sales) AS total_sales, COUNT(*) AS num_transactions FOR month IN ('Jan', 'Feb', 'Mar'))");
|
||||
|
||||
// pivot pipe operator with compound column names
|
||||
dialects.verified_stmt("SELECT * FROM sales |> PIVOT(SUM(amount) FOR product.category IN ('Electronics', 'Clothing'))");
|
||||
|
||||
// pivot pipe operator mixed with other pipe operators
|
||||
dialects.verified_stmt("SELECT * FROM sales_data |> WHERE year = 2023 |> PIVOT(SUM(revenue) FOR quarter IN ('Q1', 'Q2', 'Q3', 'Q4'))");
|
||||
|
||||
// pivot pipe operator with aliases
|
||||
dialects.verified_stmt("SELECT * FROM monthly_sales |> PIVOT(SUM(sales) FOR quarter IN ('Q1', 'Q2')) AS quarterly_sales");
|
||||
dialects.verified_stmt("SELECT * FROM data |> PIVOT(AVG(price) FOR category IN ('A', 'B', 'C')) AS avg_by_category");
|
||||
dialects.verified_stmt("SELECT * FROM sales |> PIVOT(COUNT(*) AS transactions, SUM(amount) AS total FOR region IN ('North', 'South')) AS regional_summary");
|
||||
|
||||
// pivot pipe operator with implicit aliases (without AS keyword)
|
||||
dialects.verified_query_with_canonical(
|
||||
"SELECT * FROM monthly_sales |> PIVOT(SUM(sales) FOR quarter IN ('Q1', 'Q2')) quarterly_sales",
|
||||
"SELECT * FROM monthly_sales |> PIVOT(SUM(sales) FOR quarter IN ('Q1', 'Q2')) AS quarterly_sales",
|
||||
);
|
||||
dialects.verified_query_with_canonical(
|
||||
"SELECT * FROM data |> PIVOT(AVG(price) FOR category IN ('A', 'B', 'C')) avg_by_category",
|
||||
"SELECT * FROM data |> PIVOT(AVG(price) FOR category IN ('A', 'B', 'C')) AS avg_by_category",
|
||||
);
|
||||
|
||||
// unpivot pipe operator basic usage
|
||||
dialects
|
||||
.verified_stmt("SELECT * FROM sales |> UNPIVOT(revenue FOR quarter IN (Q1, Q2, Q3, Q4))");
|
||||
dialects.verified_stmt("SELECT * FROM data |> UNPIVOT(value FOR category IN (A, B, C))");
|
||||
dialects.verified_stmt(
|
||||
"SELECT * FROM metrics |> UNPIVOT(measurement FOR metric_type IN (cpu, memory, disk))",
|
||||
);
|
||||
|
||||
// unpivot pipe operator with multiple columns
|
||||
dialects.verified_stmt("SELECT * FROM quarterly_sales |> UNPIVOT(amount FOR period IN (jan, feb, mar, apr, may, jun))");
|
||||
dialects.verified_stmt(
|
||||
"SELECT * FROM report |> UNPIVOT(score FOR subject IN (math, science, english, history))",
|
||||
);
|
||||
|
||||
// unpivot pipe operator mixed with other pipe operators
|
||||
dialects.verified_stmt("SELECT * FROM sales_data |> WHERE year = 2023 |> UNPIVOT(revenue FOR quarter IN (Q1, Q2, Q3, Q4))");
|
||||
|
||||
// unpivot pipe operator with aliases
|
||||
dialects.verified_stmt("SELECT * FROM quarterly_sales |> UNPIVOT(amount FOR period IN (Q1, Q2)) AS unpivoted_sales");
|
||||
dialects.verified_stmt(
|
||||
"SELECT * FROM data |> UNPIVOT(value FOR category IN (A, B, C)) AS transformed_data",
|
||||
);
|
||||
dialects.verified_stmt("SELECT * FROM metrics |> UNPIVOT(measurement FOR metric_type IN (cpu, memory)) AS metric_measurements");
|
||||
|
||||
// unpivot pipe operator with implicit aliases (without AS keyword)
|
||||
dialects.verified_query_with_canonical(
|
||||
"SELECT * FROM quarterly_sales |> UNPIVOT(amount FOR period IN (Q1, Q2)) unpivoted_sales",
|
||||
"SELECT * FROM quarterly_sales |> UNPIVOT(amount FOR period IN (Q1, Q2)) AS unpivoted_sales",
|
||||
);
|
||||
dialects.verified_query_with_canonical(
|
||||
"SELECT * FROM data |> UNPIVOT(value FOR category IN (A, B, C)) transformed_data",
|
||||
"SELECT * FROM data |> UNPIVOT(value FOR category IN (A, B, C)) AS transformed_data",
|
||||
);
|
||||
|
||||
// many pipes
|
||||
dialects.verified_stmt(
|
||||
"SELECT * FROM CustomerOrders |> AGGREGATE SUM(cost) AS total_cost GROUP BY customer_id, state, item_type |> EXTEND COUNT(*) OVER (PARTITION BY customer_id) AS num_orders |> WHERE num_orders > 1 |> AGGREGATE AVG(total_cost) AS average GROUP BY state DESC, item_type ASC",
|
||||
);
|
||||
|
||||
// join pipe operator - INNER JOIN
|
||||
dialects.verified_stmt("SELECT * FROM users |> JOIN orders ON users.id = orders.user_id");
|
||||
dialects.verified_stmt("SELECT * FROM users |> INNER JOIN orders ON users.id = orders.user_id");
|
||||
|
||||
// join pipe operator - LEFT JOIN
|
||||
dialects.verified_stmt("SELECT * FROM users |> LEFT JOIN orders ON users.id = orders.user_id");
|
||||
dialects.verified_stmt(
|
||||
"SELECT * FROM users |> LEFT OUTER JOIN orders ON users.id = orders.user_id",
|
||||
);
|
||||
|
||||
// join pipe operator - RIGHT JOIN
|
||||
dialects.verified_stmt("SELECT * FROM users |> RIGHT JOIN orders ON users.id = orders.user_id");
|
||||
dialects.verified_stmt(
|
||||
"SELECT * FROM users |> RIGHT OUTER JOIN orders ON users.id = orders.user_id",
|
||||
);
|
||||
|
||||
// join pipe operator - FULL JOIN
|
||||
dialects.verified_stmt("SELECT * FROM users |> FULL JOIN orders ON users.id = orders.user_id");
|
||||
dialects.verified_query_with_canonical(
|
||||
"SELECT * FROM users |> FULL OUTER JOIN orders ON users.id = orders.user_id",
|
||||
"SELECT * FROM users |> FULL JOIN orders ON users.id = orders.user_id",
|
||||
);
|
||||
|
||||
// join pipe operator - CROSS JOIN
|
||||
dialects.verified_stmt("SELECT * FROM users |> CROSS JOIN orders");
|
||||
|
||||
// join pipe operator with USING
|
||||
dialects.verified_query_with_canonical(
|
||||
"SELECT * FROM users |> JOIN orders USING (user_id)",
|
||||
"SELECT * FROM users |> JOIN orders USING(user_id)",
|
||||
);
|
||||
dialects.verified_query_with_canonical(
|
||||
"SELECT * FROM users |> LEFT JOIN orders USING (user_id, order_date)",
|
||||
"SELECT * FROM users |> LEFT JOIN orders USING(user_id, order_date)",
|
||||
);
|
||||
|
||||
// join pipe operator with alias
|
||||
dialects.verified_query_with_canonical(
|
||||
"SELECT * FROM users |> JOIN orders o ON users.id = o.user_id",
|
||||
"SELECT * FROM users |> JOIN orders AS o ON users.id = o.user_id",
|
||||
);
|
||||
dialects.verified_stmt("SELECT * FROM users |> LEFT JOIN orders AS o ON users.id = o.user_id");
|
||||
|
||||
// join pipe operator with complex ON condition
|
||||
dialects.verified_stmt("SELECT * FROM users |> JOIN orders ON users.id = orders.user_id AND orders.status = 'active'");
|
||||
dialects.verified_stmt("SELECT * FROM users |> LEFT JOIN orders ON users.id = orders.user_id AND orders.amount > 100");
|
||||
|
||||
// multiple join pipe operators
|
||||
dialects.verified_stmt("SELECT * FROM users |> JOIN orders ON users.id = orders.user_id |> JOIN products ON orders.product_id = products.id");
|
||||
dialects.verified_stmt("SELECT * FROM users |> LEFT JOIN orders ON users.id = orders.user_id |> RIGHT JOIN products ON orders.product_id = products.id");
|
||||
|
||||
// join pipe operator with other pipe operators
|
||||
dialects.verified_stmt("SELECT * FROM users |> JOIN orders ON users.id = orders.user_id |> WHERE orders.amount > 100");
|
||||
dialects.verified_stmt("SELECT * FROM users |> WHERE users.active = true |> LEFT JOIN orders ON users.id = orders.user_id");
|
||||
dialects.verified_stmt("SELECT * FROM users |> JOIN orders ON users.id = orders.user_id |> SELECT users.name, orders.amount");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_pipeline_operator_negative_tests() {
|
||||
let dialects = all_dialects_where(|d| d.supports_pipe_operator());
|
||||
|
||||
// Test that plain EXCEPT without DISTINCT fails
|
||||
assert_eq!(
|
||||
ParserError::ParserError("EXCEPT pipe operator requires DISTINCT modifier".to_string()),
|
||||
dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> EXCEPT (SELECT * FROM admins)")
|
||||
.unwrap_err()
|
||||
);
|
||||
|
||||
// Test that EXCEPT ALL fails
|
||||
assert_eq!(
|
||||
ParserError::ParserError("EXCEPT pipe operator requires DISTINCT modifier".to_string()),
|
||||
dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> EXCEPT ALL (SELECT * FROM admins)")
|
||||
.unwrap_err()
|
||||
);
|
||||
|
||||
// Test that EXCEPT BY NAME without DISTINCT fails
|
||||
assert_eq!(
|
||||
ParserError::ParserError("EXCEPT pipe operator requires DISTINCT modifier".to_string()),
|
||||
dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> EXCEPT BY NAME (SELECT * FROM admins)")
|
||||
.unwrap_err()
|
||||
);
|
||||
|
||||
// Test that EXCEPT ALL BY NAME fails
|
||||
assert_eq!(
|
||||
ParserError::ParserError("EXCEPT pipe operator requires DISTINCT modifier".to_string()),
|
||||
dialects
|
||||
.parse_sql_statements(
|
||||
"SELECT * FROM users |> EXCEPT ALL BY NAME (SELECT * FROM admins)"
|
||||
)
|
||||
.unwrap_err()
|
||||
);
|
||||
|
||||
// Test that plain INTERSECT without DISTINCT fails
|
||||
assert_eq!(
|
||||
ParserError::ParserError("INTERSECT pipe operator requires DISTINCT modifier".to_string()),
|
||||
dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> INTERSECT (SELECT * FROM admins)")
|
||||
.unwrap_err()
|
||||
);
|
||||
|
||||
// Test that INTERSECT ALL fails
|
||||
assert_eq!(
|
||||
ParserError::ParserError("INTERSECT pipe operator requires DISTINCT modifier".to_string()),
|
||||
dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> INTERSECT ALL (SELECT * FROM admins)")
|
||||
.unwrap_err()
|
||||
);
|
||||
|
||||
// Test that INTERSECT BY NAME without DISTINCT fails
|
||||
assert_eq!(
|
||||
ParserError::ParserError("INTERSECT pipe operator requires DISTINCT modifier".to_string()),
|
||||
dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> INTERSECT BY NAME (SELECT * FROM admins)")
|
||||
.unwrap_err()
|
||||
);
|
||||
|
||||
// Test that INTERSECT ALL BY NAME fails
|
||||
assert_eq!(
|
||||
ParserError::ParserError("INTERSECT pipe operator requires DISTINCT modifier".to_string()),
|
||||
dialects
|
||||
.parse_sql_statements(
|
||||
"SELECT * FROM users |> INTERSECT ALL BY NAME (SELECT * FROM admins)"
|
||||
)
|
||||
.unwrap_err()
|
||||
);
|
||||
|
||||
// Test that CALL without function name fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> CALL")
|
||||
.is_err());
|
||||
|
||||
// Test that CALL without parentheses fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> CALL my_function")
|
||||
.is_err());
|
||||
|
||||
// Test that CALL with invalid function syntax fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> CALL 123invalid")
|
||||
.is_err());
|
||||
|
||||
// Test that CALL with malformed arguments fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> CALL my_function(,)")
|
||||
.is_err());
|
||||
|
||||
// Test that CALL with invalid alias syntax fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> CALL my_function() AS")
|
||||
.is_err());
|
||||
|
||||
// Test that PIVOT without parentheses fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> PIVOT SUM(amount) FOR month IN ('Jan')")
|
||||
.is_err());
|
||||
|
||||
// Test that PIVOT without FOR keyword fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> PIVOT(SUM(amount) month IN ('Jan'))")
|
||||
.is_err());
|
||||
|
||||
// Test that PIVOT without IN keyword fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> PIVOT(SUM(amount) FOR month ('Jan'))")
|
||||
.is_err());
|
||||
|
||||
// Test that PIVOT with empty IN list fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> PIVOT(SUM(amount) FOR month IN ())")
|
||||
.is_err());
|
||||
|
||||
// Test that PIVOT with invalid alias syntax fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> PIVOT(SUM(amount) FOR month IN ('Jan')) AS")
|
||||
.is_err());
|
||||
|
||||
// Test UNPIVOT negative cases
|
||||
|
||||
// Test that UNPIVOT without parentheses fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> UNPIVOT value FOR name IN col1, col2")
|
||||
.is_err());
|
||||
|
||||
// Test that UNPIVOT without FOR keyword fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> UNPIVOT(value name IN (col1, col2))")
|
||||
.is_err());
|
||||
|
||||
// Test that UNPIVOT without IN keyword fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> UNPIVOT(value FOR name (col1, col2))")
|
||||
.is_err());
|
||||
|
||||
// Test that UNPIVOT with missing value column fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> UNPIVOT(FOR name IN (col1, col2))")
|
||||
.is_err());
|
||||
|
||||
// Test that UNPIVOT with missing name column fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> UNPIVOT(value FOR IN (col1, col2))")
|
||||
.is_err());
|
||||
|
||||
// Test that UNPIVOT with empty IN list fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> UNPIVOT(value FOR name IN ())")
|
||||
.is_err());
|
||||
|
||||
// Test that UNPIVOT with invalid alias syntax fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> UNPIVOT(value FOR name IN (col1, col2)) AS")
|
||||
.is_err());
|
||||
|
||||
// Test that UNPIVOT with missing closing parenthesis fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> UNPIVOT(value FOR name IN (col1, col2)")
|
||||
.is_err());
|
||||
|
||||
// Test that JOIN without table name fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> JOIN ON users.id = orders.user_id")
|
||||
.is_err());
|
||||
|
||||
// Test that CROSS JOIN with ON condition fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements(
|
||||
"SELECT * FROM users |> CROSS JOIN orders ON users.id = orders.user_id"
|
||||
)
|
||||
.is_err());
|
||||
|
||||
// Test that CROSS JOIN with USING condition fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> CROSS JOIN orders USING (user_id)")
|
||||
.is_err());
|
||||
|
||||
// Test that JOIN with empty USING list fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> JOIN orders USING ()")
|
||||
.is_err());
|
||||
|
||||
// Test that JOIN with malformed ON condition fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> JOIN orders ON")
|
||||
.is_err());
|
||||
|
||||
// Test that JOIN with invalid USING syntax fails
|
||||
assert!(dialects
|
||||
.parse_sql_statements("SELECT * FROM users |> JOIN orders USING user_id")
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -15300,6 +15788,11 @@ fn parse_return() {
|
|||
let _ = all_dialects().verified_stmt("RETURN 1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_subquery_limit() {
|
||||
let _ = all_dialects().verified_stmt("SELECT t1_id, t1_name FROM t1 WHERE t1_id IN (SELECT t2_id FROM t2 WHERE t1_name = t2_name LIMIT 10)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_open() {
|
||||
let open_cursor = "OPEN Employee_Cursor";
|
||||
|
@ -15346,3 +15839,118 @@ fn check_enforced() {
|
|||
"CREATE TABLE t (a INT, b INT, c INT, CHECK (a > 0) NOT ENFORCED, CHECK (b > 0) ENFORCED, CHECK (c > 0))",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join_precedence() {
|
||||
all_dialects_except(|d| !d.supports_left_associative_joins_without_parens())
|
||||
.verified_query_with_canonical(
|
||||
"SELECT *
|
||||
FROM t1
|
||||
NATURAL JOIN t5
|
||||
INNER JOIN t0 ON (t0.v1 + t5.v0) > 0
|
||||
WHERE t0.v1 = t1.v0",
|
||||
// canonical string without parentheses
|
||||
"SELECT * FROM t1 NATURAL JOIN t5 INNER JOIN t0 ON (t0.v1 + t5.v0) > 0 WHERE t0.v1 = t1.v0",
|
||||
);
|
||||
all_dialects_except(|d| d.supports_left_associative_joins_without_parens()).verified_query_with_canonical(
|
||||
"SELECT *
|
||||
FROM t1
|
||||
NATURAL JOIN t5
|
||||
INNER JOIN t0 ON (t0.v1 + t5.v0) > 0
|
||||
WHERE t0.v1 = t1.v0",
|
||||
// canonical string with parentheses
|
||||
"SELECT * FROM t1 NATURAL JOIN (t5 INNER JOIN t0 ON (t0.v1 + t5.v0) > 0) WHERE t0.v1 = t1.v0",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_procedure_with_language() {
|
||||
let sql = r#"CREATE PROCEDURE test_proc LANGUAGE sql AS BEGIN SELECT 1; END"#;
|
||||
match verified_stmt(sql) {
|
||||
Statement::CreateProcedure {
|
||||
or_alter,
|
||||
name,
|
||||
params,
|
||||
language,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(or_alter, false);
|
||||
assert_eq!(name.to_string(), "test_proc");
|
||||
assert_eq!(params, Some(vec![]));
|
||||
assert_eq!(
|
||||
language,
|
||||
Some(Ident {
|
||||
value: "sql".into(),
|
||||
quote_style: None,
|
||||
span: Span {
|
||||
start: Location::empty(),
|
||||
end: Location::empty()
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_procedure_with_parameter_modes() {
|
||||
let sql = r#"CREATE PROCEDURE test_proc (IN a INTEGER, OUT b TEXT, INOUT c TIMESTAMP, d BOOL) AS BEGIN SELECT 1; END"#;
|
||||
match verified_stmt(sql) {
|
||||
Statement::CreateProcedure {
|
||||
or_alter,
|
||||
name,
|
||||
params,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(or_alter, false);
|
||||
assert_eq!(name.to_string(), "test_proc");
|
||||
let fake_span = Span {
|
||||
start: Location { line: 0, column: 0 },
|
||||
end: Location { line: 0, column: 0 },
|
||||
};
|
||||
assert_eq!(
|
||||
params,
|
||||
Some(vec![
|
||||
ProcedureParam {
|
||||
name: Ident {
|
||||
value: "a".into(),
|
||||
quote_style: None,
|
||||
span: fake_span,
|
||||
},
|
||||
data_type: DataType::Integer(None),
|
||||
mode: Some(ArgMode::In)
|
||||
},
|
||||
ProcedureParam {
|
||||
name: Ident {
|
||||
value: "b".into(),
|
||||
quote_style: None,
|
||||
span: fake_span,
|
||||
},
|
||||
data_type: DataType::Text,
|
||||
mode: Some(ArgMode::Out)
|
||||
},
|
||||
ProcedureParam {
|
||||
name: Ident {
|
||||
value: "c".into(),
|
||||
quote_style: None,
|
||||
span: fake_span,
|
||||
},
|
||||
data_type: DataType::Timestamp(None, TimezoneInfo::None),
|
||||
mode: Some(ArgMode::InOut)
|
||||
},
|
||||
ProcedureParam {
|
||||
name: Ident {
|
||||
value: "d".into(),
|
||||
quote_style: None,
|
||||
span: fake_span,
|
||||
},
|
||||
data_type: DataType::Bool,
|
||||
mode: None
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ use sqlparser::ast::helpers::attached_token::AttachedToken;
|
|||
use sqlparser::ast::*;
|
||||
use sqlparser::dialect::{DatabricksDialect, GenericDialect};
|
||||
use sqlparser::parser::ParserError;
|
||||
use sqlparser::tokenizer::Span;
|
||||
use test_utils::*;
|
||||
|
||||
#[macro_use]
|
||||
|
@ -213,7 +214,7 @@ fn parse_use() {
|
|||
for object_name in &valid_object_names {
|
||||
// Test single identifier without quotes
|
||||
assert_eq!(
|
||||
databricks().verified_stmt(&format!("USE {}", object_name)),
|
||||
databricks().verified_stmt(&format!("USE {object_name}")),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::new(
|
||||
object_name.to_string()
|
||||
)])))
|
||||
|
@ -221,7 +222,7 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test single identifier with different type of quotes
|
||||
assert_eq!(
|
||||
databricks().verified_stmt(&format!("USE {0}{1}{0}", quote, object_name)),
|
||||
databricks().verified_stmt(&format!("USE {quote}{object_name}{quote}")),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
object_name.to_string(),
|
||||
|
@ -233,21 +234,21 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test single identifier with keyword and different type of quotes
|
||||
assert_eq!(
|
||||
databricks().verified_stmt(&format!("USE CATALOG {0}my_catalog{0}", quote)),
|
||||
databricks().verified_stmt(&format!("USE CATALOG {quote}my_catalog{quote}")),
|
||||
Statement::Use(Use::Catalog(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
"my_catalog".to_string(),
|
||||
)])))
|
||||
);
|
||||
assert_eq!(
|
||||
databricks().verified_stmt(&format!("USE DATABASE {0}my_database{0}", quote)),
|
||||
databricks().verified_stmt(&format!("USE DATABASE {quote}my_database{quote}")),
|
||||
Statement::Use(Use::Database(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
"my_database".to_string(),
|
||||
)])))
|
||||
);
|
||||
assert_eq!(
|
||||
databricks().verified_stmt(&format!("USE SCHEMA {0}my_schema{0}", quote)),
|
||||
databricks().verified_stmt(&format!("USE SCHEMA {quote}my_schema{quote}")),
|
||||
Statement::Use(Use::Schema(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
"my_schema".to_string(),
|
||||
|
@ -328,7 +329,10 @@ fn data_type_timestamp_ntz() {
|
|||
databricks().verified_expr("TIMESTAMP_NTZ '2025-03-29T18:52:00'"),
|
||||
Expr::TypedString {
|
||||
data_type: DataType::TimestampNtz,
|
||||
value: Value::SingleQuotedString("2025-03-29T18:52:00".to_owned())
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("2025-03-29T18:52:00".to_owned()),
|
||||
span: Span::empty(),
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -357,6 +361,6 @@ fn data_type_timestamp_ntz() {
|
|||
}]
|
||||
);
|
||||
}
|
||||
s => panic!("Unexpected statement: {:?}", s),
|
||||
s => panic!("Unexpected statement: {s:?}"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ use test_utils::*;
|
|||
|
||||
use sqlparser::ast::*;
|
||||
use sqlparser::dialect::{DuckDbDialect, GenericDialect};
|
||||
use sqlparser::parser::ParserError;
|
||||
|
||||
fn duckdb() -> TestedDialects {
|
||||
TestedDialects::new(vec![Box::new(DuckDbDialect {})])
|
||||
|
@ -368,7 +369,7 @@ fn test_duckdb_specific_int_types() {
|
|||
("HUGEINT", DataType::HugeInt),
|
||||
];
|
||||
for (dtype_string, data_type) in duckdb_dtypes {
|
||||
let sql = format!("SELECT 123::{}", dtype_string);
|
||||
let sql = format!("SELECT 123::{dtype_string}");
|
||||
let select = duckdb().verified_only_select(&sql);
|
||||
assert_eq!(
|
||||
&Expr::Cast {
|
||||
|
@ -792,7 +793,7 @@ fn parse_use() {
|
|||
for object_name in &valid_object_names {
|
||||
// Test single identifier without quotes
|
||||
assert_eq!(
|
||||
duckdb().verified_stmt(&format!("USE {}", object_name)),
|
||||
duckdb().verified_stmt(&format!("USE {object_name}")),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::new(
|
||||
object_name.to_string()
|
||||
)])))
|
||||
|
@ -800,7 +801,7 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test single identifier with different type of quotes
|
||||
assert_eq!(
|
||||
duckdb().verified_stmt(&format!("USE {0}{1}{0}", quote, object_name)),
|
||||
duckdb().verified_stmt(&format!("USE {quote}{object_name}{quote}")),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
object_name.to_string(),
|
||||
|
@ -812,7 +813,9 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test double identifier with different type of quotes
|
||||
assert_eq!(
|
||||
duckdb().verified_stmt(&format!("USE {0}CATALOG{0}.{0}my_schema{0}", quote)),
|
||||
duckdb().verified_stmt(&format!(
|
||||
"USE {quote}CATALOG{quote}.{quote}my_schema{quote}"
|
||||
)),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![
|
||||
Ident::with_quote(quote, "CATALOG"),
|
||||
Ident::with_quote(quote, "my_schema")
|
||||
|
@ -828,3 +831,32 @@ fn parse_use() {
|
|||
])))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_duckdb_trim() {
|
||||
let real_sql = r#"SELECT customer_id, TRIM(item_price_id, '"', "a") AS item_price_id FROM models_staging.subscriptions"#;
|
||||
assert_eq!(duckdb().verified_stmt(real_sql).to_string(), real_sql);
|
||||
|
||||
let sql_only_select = "SELECT TRIM('xyz', 'a')";
|
||||
let select = duckdb().verified_only_select(sql_only_select);
|
||||
assert_eq!(
|
||||
&Expr::Trim {
|
||||
expr: Box::new(Expr::Value(
|
||||
Value::SingleQuotedString("xyz".to_owned()).with_empty_span()
|
||||
)),
|
||||
trim_where: None,
|
||||
trim_what: None,
|
||||
trim_characters: Some(vec![Expr::Value(
|
||||
Value::SingleQuotedString("a".to_owned()).with_empty_span()
|
||||
)]),
|
||||
},
|
||||
expr_from_projection(only(&select.projection))
|
||||
);
|
||||
|
||||
// missing comma separation
|
||||
let error_sql = "SELECT TRIM('xyz' 'a')";
|
||||
assert_eq!(
|
||||
ParserError::ParserError("Expected: ), found: 'a'".to_owned()),
|
||||
duckdb().parse_sql_statements(error_sql).unwrap_err()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -524,7 +524,7 @@ fn parse_use() {
|
|||
for object_name in &valid_object_names {
|
||||
// Test single identifier without quotes
|
||||
assert_eq!(
|
||||
hive().verified_stmt(&format!("USE {}", object_name)),
|
||||
hive().verified_stmt(&format!("USE {object_name}")),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::new(
|
||||
object_name.to_string()
|
||||
)])))
|
||||
|
@ -532,7 +532,7 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test single identifier with different type of quotes
|
||||
assert_eq!(
|
||||
hive().verified_stmt(&format!("USE {}{}{}", quote, object_name, quote)),
|
||||
hive().verified_stmt(&format!("USE {quote}{object_name}{quote}")),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
object_name.to_string(),
|
||||
|
|
|
@ -153,7 +153,8 @@ fn parse_create_procedure() {
|
|||
quote_style: None,
|
||||
span: Span::empty(),
|
||||
},
|
||||
data_type: DataType::Int(None)
|
||||
data_type: DataType::Int(None),
|
||||
mode: None,
|
||||
},
|
||||
ProcedureParam {
|
||||
name: Ident {
|
||||
|
@ -164,14 +165,16 @@ fn parse_create_procedure() {
|
|||
data_type: DataType::Varchar(Some(CharacterLength::IntegerLength {
|
||||
length: 256,
|
||||
unit: None
|
||||
}))
|
||||
})),
|
||||
mode: None,
|
||||
}
|
||||
]),
|
||||
name: ObjectName::from(vec![Ident {
|
||||
value: "test".into(),
|
||||
quote_style: None,
|
||||
span: Span::empty(),
|
||||
}])
|
||||
}]),
|
||||
language: None,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -1670,7 +1673,7 @@ fn parse_use() {
|
|||
for object_name in &valid_object_names {
|
||||
// Test single identifier without quotes
|
||||
assert_eq!(
|
||||
ms().verified_stmt(&format!("USE {}", object_name)),
|
||||
ms().verified_stmt(&format!("USE {object_name}")),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::new(
|
||||
object_name.to_string()
|
||||
)])))
|
||||
|
@ -1678,7 +1681,7 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test single identifier with different type of quotes
|
||||
assert_eq!(
|
||||
ms().verified_stmt(&format!("USE {}{}{}", quote, object_name, quote)),
|
||||
ms().verified_stmt(&format!("USE {quote}{object_name}{quote}")),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
object_name.to_string(),
|
||||
|
@ -2184,7 +2187,7 @@ fn parse_mssql_if_else() {
|
|||
"IF 1 = 1 BEGIN SET @A = 1; END ELSE SET @A = 2;"
|
||||
);
|
||||
}
|
||||
_ => panic!("Unexpected statements: {:?}", stmts),
|
||||
_ => panic!("Unexpected statements: {stmts:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2234,7 +2237,7 @@ fn test_mssql_if_statements_span() {
|
|||
Span::new(Location::new(1, 21), Location::new(1, 36))
|
||||
);
|
||||
}
|
||||
stmt => panic!("Unexpected statement: {:?}", stmt),
|
||||
stmt => panic!("Unexpected statement: {stmt:?}"),
|
||||
}
|
||||
|
||||
// Blocks
|
||||
|
@ -2255,7 +2258,7 @@ fn test_mssql_if_statements_span() {
|
|||
Span::new(Location::new(1, 32), Location::new(1, 57))
|
||||
);
|
||||
}
|
||||
stmt => panic!("Unexpected statement: {:?}", stmt),
|
||||
stmt => panic!("Unexpected statement: {stmt:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -593,7 +593,7 @@ fn parse_use() {
|
|||
for object_name in &valid_object_names {
|
||||
// Test single identifier without quotes
|
||||
assert_eq!(
|
||||
mysql_and_generic().verified_stmt(&format!("USE {}", object_name)),
|
||||
mysql_and_generic().verified_stmt(&format!("USE {object_name}")),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::new(
|
||||
object_name.to_string()
|
||||
)])))
|
||||
|
@ -601,8 +601,7 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test single identifier with different type of quotes
|
||||
assert_eq!(
|
||||
mysql_and_generic()
|
||||
.verified_stmt(&format!("USE {}{}{}", quote, object_name, quote)),
|
||||
mysql_and_generic().verified_stmt(&format!("USE {quote}{object_name}{quote}")),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
object_name.to_string(),
|
||||
|
@ -670,6 +669,20 @@ fn table_constraint_unique_primary_ctor(
|
|||
characteristics: Option<ConstraintCharacteristics>,
|
||||
unique_index_type_display: Option<KeyOrIndexDisplay>,
|
||||
) -> TableConstraint {
|
||||
let columns = columns
|
||||
.into_iter()
|
||||
.map(|ident| IndexColumn {
|
||||
column: OrderByExpr {
|
||||
expr: Expr::Identifier(ident),
|
||||
options: OrderByOptions {
|
||||
asc: None,
|
||||
nulls_first: None,
|
||||
},
|
||||
with_fill: None,
|
||||
},
|
||||
operator_class: None,
|
||||
})
|
||||
.collect();
|
||||
match unique_index_type_display {
|
||||
Some(index_type_display) => TableConstraint::Unique {
|
||||
name,
|
||||
|
@ -795,6 +808,67 @@ fn parse_create_table_primary_and_unique_key_with_index_options() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_prefix_key_part() {
|
||||
let expected = vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::value(
|
||||
number("10"),
|
||||
)))];
|
||||
for sql in [
|
||||
"CREATE INDEX idx_index ON t(textcol(10))",
|
||||
"ALTER TABLE tab ADD INDEX idx_index (textcol(10))",
|
||||
"ALTER TABLE tab ADD PRIMARY KEY (textcol(10))",
|
||||
"ALTER TABLE tab ADD UNIQUE KEY (textcol(10))",
|
||||
"ALTER TABLE tab ADD UNIQUE KEY (textcol(10))",
|
||||
"ALTER TABLE tab ADD FULLTEXT INDEX (textcol(10))",
|
||||
"CREATE TABLE t (textcol TEXT, INDEX idx_index (textcol(10)))",
|
||||
] {
|
||||
match index_column(mysql_and_generic().verified_stmt(sql)) {
|
||||
Expr::Function(Function {
|
||||
name,
|
||||
args: FunctionArguments::List(FunctionArgumentList { args, .. }),
|
||||
..
|
||||
}) => {
|
||||
assert_eq!(name.to_string(), "textcol");
|
||||
assert_eq!(args, expected);
|
||||
}
|
||||
expr => panic!("unexpected expression {expr} for {sql}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_functional_key_part() {
|
||||
assert_eq!(
|
||||
index_column(
|
||||
mysql_and_generic()
|
||||
.verified_stmt("CREATE INDEX idx_index ON t((col COLLATE utf8mb4_bin) DESC)")
|
||||
),
|
||||
Expr::Nested(Box::new(Expr::Collate {
|
||||
expr: Box::new(Expr::Identifier("col".into())),
|
||||
collation: ObjectName(vec![sqlparser::ast::ObjectNamePart::Identifier(
|
||||
Ident::new("utf8mb4_bin")
|
||||
)]),
|
||||
}))
|
||||
);
|
||||
assert_eq!(
|
||||
index_column(mysql_and_generic().verified_stmt(
|
||||
r#"CREATE TABLE t (jsoncol JSON, PRIMARY KEY ((CAST(col ->> '$.id' AS UNSIGNED)) ASC))"#
|
||||
)),
|
||||
Expr::Nested(Box::new(Expr::Cast {
|
||||
kind: CastKind::Cast,
|
||||
expr: Box::new(Expr::BinaryOp {
|
||||
left: Box::new(Expr::Identifier(Ident::new("col"))),
|
||||
op: BinaryOperator::LongArrow,
|
||||
right: Box::new(Expr::Value(
|
||||
Value::SingleQuotedString("$.id".to_string()).with_empty_span()
|
||||
)),
|
||||
}),
|
||||
data_type: DataType::Unsigned,
|
||||
format: None,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_table_primary_and_unique_key_with_index_type() {
|
||||
let sqls = ["UNIQUE", "PRIMARY KEY"].map(|key_ty| {
|
||||
|
@ -2188,11 +2262,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() {
|
|||
Some(SelectItem::UnnamedExpr(Expr::CompoundIdentifier(parts))) => {
|
||||
assert_eq!(&[Ident::new("t"), Ident::new("15to29")], &parts[..]);
|
||||
}
|
||||
proj => panic!("Unexpected projection: {:?}", proj),
|
||||
proj => panic!("Unexpected projection: {proj:?}"),
|
||||
},
|
||||
body => panic!("Unexpected statement body: {:?}", body),
|
||||
body => panic!("Unexpected statement body: {body:?}"),
|
||||
},
|
||||
stmt => panic!("Unexpected statement: {:?}", stmt),
|
||||
stmt => panic!("Unexpected statement: {stmt:?}"),
|
||||
}
|
||||
|
||||
// Case 2: Qualified column name that starts with digits and on its own represents a number.
|
||||
|
@ -2202,11 +2276,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() {
|
|||
Some(SelectItem::UnnamedExpr(Expr::CompoundIdentifier(parts))) => {
|
||||
assert_eq!(&[Ident::new("t"), Ident::new("15e29")], &parts[..]);
|
||||
}
|
||||
proj => panic!("Unexpected projection: {:?}", proj),
|
||||
proj => panic!("Unexpected projection: {proj:?}"),
|
||||
},
|
||||
body => panic!("Unexpected statement body: {:?}", body),
|
||||
body => panic!("Unexpected statement body: {body:?}"),
|
||||
},
|
||||
stmt => panic!("Unexpected statement: {:?}", stmt),
|
||||
stmt => panic!("Unexpected statement: {stmt:?}"),
|
||||
}
|
||||
|
||||
// Case 3: Unqualified, the same token is parsed as a number.
|
||||
|
@ -2220,11 +2294,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() {
|
|||
Some(SelectItem::UnnamedExpr(Expr::Value(ValueWithSpan { value, .. }))) => {
|
||||
assert_eq!(&number("15e29"), value);
|
||||
}
|
||||
proj => panic!("Unexpected projection: {:?}", proj),
|
||||
proj => panic!("Unexpected projection: {proj:?}"),
|
||||
},
|
||||
body => panic!("Unexpected statement body: {:?}", body),
|
||||
body => panic!("Unexpected statement body: {body:?}"),
|
||||
},
|
||||
stmt => panic!("Unexpected statement: {:?}", stmt),
|
||||
stmt => panic!("Unexpected statement: {stmt:?}"),
|
||||
}
|
||||
|
||||
// Case 4: Quoted simple identifier.
|
||||
|
@ -2234,11 +2308,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() {
|
|||
Some(SelectItem::UnnamedExpr(Expr::Identifier(name))) => {
|
||||
assert_eq!(&Ident::with_quote('`', "15e29"), name);
|
||||
}
|
||||
proj => panic!("Unexpected projection: {:?}", proj),
|
||||
proj => panic!("Unexpected projection: {proj:?}"),
|
||||
},
|
||||
body => panic!("Unexpected statement body: {:?}", body),
|
||||
body => panic!("Unexpected statement body: {body:?}"),
|
||||
},
|
||||
stmt => panic!("Unexpected statement: {:?}", stmt),
|
||||
stmt => panic!("Unexpected statement: {stmt:?}"),
|
||||
}
|
||||
|
||||
// Case 5: Quoted compound identifier.
|
||||
|
@ -2251,11 +2325,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() {
|
|||
&parts[..]
|
||||
);
|
||||
}
|
||||
proj => panic!("Unexpected projection: {:?}", proj),
|
||||
proj => panic!("Unexpected projection: {proj:?}"),
|
||||
},
|
||||
body => panic!("Unexpected statement body: {:?}", body),
|
||||
body => panic!("Unexpected statement body: {body:?}"),
|
||||
},
|
||||
stmt => panic!("Unexpected statement: {:?}", stmt),
|
||||
stmt => panic!("Unexpected statement: {stmt:?}"),
|
||||
}
|
||||
|
||||
// Case 6: Multi-level compound identifiers.
|
||||
|
@ -2272,11 +2346,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() {
|
|||
&parts[..]
|
||||
);
|
||||
}
|
||||
proj => panic!("Unexpected projection: {:?}", proj),
|
||||
proj => panic!("Unexpected projection: {proj:?}"),
|
||||
},
|
||||
body => panic!("Unexpected statement body: {:?}", body),
|
||||
body => panic!("Unexpected statement body: {body:?}"),
|
||||
},
|
||||
stmt => panic!("Unexpected statement: {:?}", stmt),
|
||||
stmt => panic!("Unexpected statement: {stmt:?}"),
|
||||
}
|
||||
|
||||
// Case 7: Multi-level compound quoted identifiers.
|
||||
|
@ -2293,11 +2367,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() {
|
|||
&parts[..]
|
||||
);
|
||||
}
|
||||
proj => panic!("Unexpected projection: {:?}", proj),
|
||||
proj => panic!("Unexpected projection: {proj:?}"),
|
||||
},
|
||||
body => panic!("Unexpected statement body: {:?}", body),
|
||||
body => panic!("Unexpected statement body: {body:?}"),
|
||||
},
|
||||
stmt => panic!("Unexpected statement: {:?}", stmt),
|
||||
stmt => panic!("Unexpected statement: {stmt:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4035,3 +4109,28 @@ fn parse_alter_table_drop_index() {
|
|||
AlterTableOperation::DropIndex { name } if name.value == "idx_index"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_json_member_of() {
|
||||
mysql().verified_stmt(r#"SELECT 17 MEMBER OF('[23, "abc", 17, "ab", 10]')"#);
|
||||
let sql = r#"SELECT 'ab' MEMBER OF('[23, "abc", 17, "ab", 10]')"#;
|
||||
let stmt = mysql().verified_stmt(sql);
|
||||
match stmt {
|
||||
Statement::Query(query) => {
|
||||
let select = query.body.as_select().unwrap();
|
||||
assert_eq!(
|
||||
select.projection,
|
||||
vec![SelectItem::UnnamedExpr(Expr::MemberOf(MemberOf {
|
||||
value: Box::new(Expr::Value(
|
||||
Value::SingleQuotedString("ab".to_string()).into()
|
||||
)),
|
||||
array: Box::new(Expr::Value(
|
||||
Value::SingleQuotedString(r#"[23, "abc", 17, "ab", 10]"#.to_string())
|
||||
.into()
|
||||
)),
|
||||
}))]
|
||||
);
|
||||
}
|
||||
_ => panic!("Unexpected statement {stmt}"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -606,9 +606,10 @@ fn parse_alter_table_constraints_unique_nulls_distinct() {
|
|||
.verified_stmt("ALTER TABLE t ADD CONSTRAINT b UNIQUE NULLS NOT DISTINCT (c)")
|
||||
{
|
||||
Statement::AlterTable { operations, .. } => match &operations[0] {
|
||||
AlterTableOperation::AddConstraint(TableConstraint::Unique {
|
||||
nulls_distinct, ..
|
||||
}) => {
|
||||
AlterTableOperation::AddConstraint {
|
||||
constraint: TableConstraint::Unique { nulls_distinct, .. },
|
||||
..
|
||||
} => {
|
||||
assert_eq!(nulls_distinct, &NullsDistinctOption::NotDistinct)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
|
@ -764,10 +765,7 @@ fn parse_drop_extension() {
|
|||
|
||||
#[test]
|
||||
fn parse_alter_table_alter_column() {
|
||||
pg().one_statement_parses_to(
|
||||
"ALTER TABLE tab ALTER COLUMN is_active TYPE TEXT USING 'text'",
|
||||
"ALTER TABLE tab ALTER COLUMN is_active SET DATA TYPE TEXT USING 'text'",
|
||||
);
|
||||
pg().verified_stmt("ALTER TABLE tab ALTER COLUMN is_active TYPE TEXT USING 'text'");
|
||||
|
||||
match alter_table_op(
|
||||
pg().verified_stmt(
|
||||
|
@ -783,6 +781,7 @@ fn parse_alter_table_alter_column() {
|
|||
AlterColumnOperation::SetDataType {
|
||||
data_type: DataType::Text,
|
||||
using: Some(using_expr),
|
||||
had_set: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -2535,12 +2534,12 @@ fn parse_create_indices_with_operator_classes() {
|
|||
for expected_operator_class in &operator_classes {
|
||||
let single_column_sql_statement = format!(
|
||||
"CREATE INDEX the_index_name ON users USING {expected_index_type} (concat_users_name(first_name, last_name){})",
|
||||
expected_operator_class.as_ref().map(|oc| format!(" {}", oc))
|
||||
expected_operator_class.as_ref().map(|oc| format!(" {oc}"))
|
||||
.unwrap_or_default()
|
||||
);
|
||||
let multi_column_sql_statement = format!(
|
||||
"CREATE INDEX the_index_name ON users USING {expected_index_type} (column_name,concat_users_name(first_name, last_name){})",
|
||||
expected_operator_class.as_ref().map(|oc| format!(" {}", oc))
|
||||
expected_operator_class.as_ref().map(|oc| format!(" {oc}"))
|
||||
.unwrap_or_default()
|
||||
);
|
||||
|
||||
|
@ -3273,7 +3272,7 @@ fn test_fn_arg_with_value_operator() {
|
|||
assert!(matches!(
|
||||
&args[..],
|
||||
&[FunctionArg::ExprNamed { operator: FunctionArgOperator::Value, .. }]
|
||||
), "Invalid function argument: {:?}", args);
|
||||
), "Invalid function argument: {args:?}");
|
||||
}
|
||||
other => panic!("Expected: JSON_OBJECT('name' VALUE 'value') to be parsed as a function, but got {other:?}"),
|
||||
}
|
||||
|
@ -5258,7 +5257,10 @@ fn parse_at_time_zone() {
|
|||
left: Box::new(Expr::AtTimeZone {
|
||||
timestamp: Box::new(Expr::TypedString {
|
||||
data_type: DataType::Timestamp(None, TimezoneInfo::None),
|
||||
value: Value::SingleQuotedString("2001-09-28 01:00".to_string()),
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("2001-09-28 01:00".to_string()),
|
||||
span: Span::empty(),
|
||||
},
|
||||
}),
|
||||
time_zone: Box::new(Expr::Cast {
|
||||
kind: CastKind::DoubleColon,
|
||||
|
@ -5679,7 +5681,7 @@ fn parse_drop_trigger() {
|
|||
"DROP TRIGGER{} check_update ON table_name{}",
|
||||
if if_exists { " IF EXISTS" } else { "" },
|
||||
option
|
||||
.map(|o| format!(" {}", o))
|
||||
.map(|o| format!(" {o}"))
|
||||
.unwrap_or_else(|| "".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
|
@ -5773,8 +5775,7 @@ fn parse_trigger_related_functions() {
|
|||
// Now we parse the statements and check if they are parsed correctly.
|
||||
let mut statements = pg()
|
||||
.parse_sql_statements(&format!(
|
||||
"{}{}{}{}",
|
||||
sql_table_creation, sql_create_function, sql_create_trigger, sql_drop_trigger
|
||||
"{sql_table_creation}{sql_create_function}{sql_create_trigger}{sql_drop_trigger}"
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
|
@ -6201,3 +6202,153 @@ fn parse_alter_table_replica_identity() {
|
|||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ts_datatypes() {
|
||||
match pg_and_generic().verified_stmt("CREATE TABLE foo (x TSVECTOR)") {
|
||||
Statement::CreateTable(CreateTable { columns, .. }) => {
|
||||
assert_eq!(
|
||||
columns,
|
||||
vec![ColumnDef {
|
||||
name: "x".into(),
|
||||
data_type: DataType::TsVector,
|
||||
options: vec![],
|
||||
}]
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
match pg_and_generic().verified_stmt("CREATE TABLE foo (x TSQUERY)") {
|
||||
Statement::CreateTable(CreateTable { columns, .. }) => {
|
||||
assert_eq!(
|
||||
columns,
|
||||
vec![ColumnDef {
|
||||
name: "x".into(),
|
||||
data_type: DataType::TsQuery,
|
||||
options: vec![],
|
||||
}]
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_alter_table_constraint_not_valid() {
|
||||
match pg_and_generic().verified_stmt(
|
||||
"ALTER TABLE foo ADD CONSTRAINT bar FOREIGN KEY (baz) REFERENCES other(ref) NOT VALID",
|
||||
) {
|
||||
Statement::AlterTable { operations, .. } => {
|
||||
assert_eq!(
|
||||
operations,
|
||||
vec![AlterTableOperation::AddConstraint {
|
||||
constraint: TableConstraint::ForeignKey {
|
||||
name: Some("bar".into()),
|
||||
index_name: None,
|
||||
columns: vec!["baz".into()],
|
||||
foreign_table: ObjectName::from(vec!["other".into()]),
|
||||
referred_columns: vec!["ref".into()],
|
||||
on_delete: None,
|
||||
on_update: None,
|
||||
characteristics: None,
|
||||
},
|
||||
not_valid: true,
|
||||
}]
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_alter_table_validate_constraint() {
|
||||
match pg_and_generic().verified_stmt("ALTER TABLE foo VALIDATE CONSTRAINT bar") {
|
||||
Statement::AlterTable { operations, .. } => {
|
||||
assert_eq!(
|
||||
operations,
|
||||
vec![AlterTableOperation::ValidateConstraint { name: "bar".into() }]
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_server() {
|
||||
let test_cases = vec![
|
||||
(
|
||||
"CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw",
|
||||
CreateServerStatement {
|
||||
name: ObjectName::from(vec!["myserver".into()]),
|
||||
if_not_exists: false,
|
||||
server_type: None,
|
||||
version: None,
|
||||
foreign_data_wrapper: ObjectName::from(vec!["postgres_fdw".into()]),
|
||||
options: None,
|
||||
},
|
||||
),
|
||||
(
|
||||
"CREATE SERVER IF NOT EXISTS myserver TYPE 'server_type' VERSION 'server_version' FOREIGN DATA WRAPPER postgres_fdw",
|
||||
CreateServerStatement {
|
||||
name: ObjectName::from(vec!["myserver".into()]),
|
||||
if_not_exists: true,
|
||||
server_type: Some(Ident {
|
||||
value: "server_type".to_string(),
|
||||
quote_style: Some('\''),
|
||||
span: Span::empty(),
|
||||
}),
|
||||
version: Some(Ident {
|
||||
value: "server_version".to_string(),
|
||||
quote_style: Some('\''),
|
||||
span: Span::empty(),
|
||||
}),
|
||||
foreign_data_wrapper: ObjectName::from(vec!["postgres_fdw".into()]),
|
||||
options: None,
|
||||
}
|
||||
),
|
||||
(
|
||||
"CREATE SERVER myserver2 FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'foo', dbname 'foodb', port '5432')",
|
||||
CreateServerStatement {
|
||||
name: ObjectName::from(vec!["myserver2".into()]),
|
||||
if_not_exists: false,
|
||||
server_type: None,
|
||||
version: None,
|
||||
foreign_data_wrapper: ObjectName::from(vec!["postgres_fdw".into()]),
|
||||
options: Some(vec![
|
||||
CreateServerOption {
|
||||
key: "host".into(),
|
||||
value: Ident {
|
||||
value: "foo".to_string(),
|
||||
quote_style: Some('\''),
|
||||
span: Span::empty(),
|
||||
},
|
||||
},
|
||||
CreateServerOption {
|
||||
key: "dbname".into(),
|
||||
value: Ident {
|
||||
value: "foodb".to_string(),
|
||||
quote_style: Some('\''),
|
||||
span: Span::empty(),
|
||||
},
|
||||
},
|
||||
CreateServerOption {
|
||||
key: "port".into(),
|
||||
value: Ident {
|
||||
value: "5432".to_string(),
|
||||
quote_style: Some('\''),
|
||||
span: Span::empty(),
|
||||
},
|
||||
},
|
||||
]),
|
||||
}
|
||||
)
|
||||
];
|
||||
|
||||
for (sql, expected) in test_cases {
|
||||
let Statement::CreateServer(stmt) = pg_and_generic().verified_stmt(sql) else {
|
||||
unreachable!()
|
||||
};
|
||||
assert_eq!(stmt, expected);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -402,3 +402,8 @@ fn parse_extract_single_quotes() {
|
|||
fn parse_string_literal_backslash_escape() {
|
||||
redshift().one_statement_parses_to(r#"SELECT 'l\'auto'"#, "SELECT 'l''auto'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_utf8_multibyte_idents() {
|
||||
redshift().verified_stmt("SELECT 🚀.city AS 🎸 FROM customers AS 🚀");
|
||||
}
|
||||
|
|
|
@ -2528,10 +2528,7 @@ fn test_snowflake_stage_object_names_into_location() {
|
|||
.zip(allowed_object_names.iter_mut())
|
||||
{
|
||||
let (formatted_name, object_name) = it;
|
||||
let sql = format!(
|
||||
"COPY INTO {} FROM 'gcs://mybucket/./../a.csv'",
|
||||
formatted_name
|
||||
);
|
||||
let sql = format!("COPY INTO {formatted_name} FROM 'gcs://mybucket/./../a.csv'");
|
||||
match snowflake().verified_stmt(&sql) {
|
||||
Statement::CopyIntoSnowflake { into, .. } => {
|
||||
assert_eq!(into.0, object_name.0)
|
||||
|
@ -2554,10 +2551,7 @@ fn test_snowflake_stage_object_names_into_table() {
|
|||
.zip(allowed_object_names.iter_mut())
|
||||
{
|
||||
let (formatted_name, object_name) = it;
|
||||
let sql = format!(
|
||||
"COPY INTO {} FROM 'gcs://mybucket/./../a.csv'",
|
||||
formatted_name
|
||||
);
|
||||
let sql = format!("COPY INTO {formatted_name} FROM 'gcs://mybucket/./../a.csv'");
|
||||
match snowflake().verified_stmt(&sql) {
|
||||
Statement::CopyIntoSnowflake { into, .. } => {
|
||||
assert_eq!(into.0, object_name.0)
|
||||
|
@ -3038,7 +3032,7 @@ fn parse_use() {
|
|||
for object_name in &valid_object_names {
|
||||
// Test single identifier without quotes
|
||||
assert_eq!(
|
||||
snowflake().verified_stmt(&format!("USE {}", object_name)),
|
||||
snowflake().verified_stmt(&format!("USE {object_name}")),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::new(
|
||||
object_name.to_string()
|
||||
)])))
|
||||
|
@ -3046,7 +3040,7 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test single identifier with different type of quotes
|
||||
assert_eq!(
|
||||
snowflake().verified_stmt(&format!("USE {}{}{}", quote, object_name, quote)),
|
||||
snowflake().verified_stmt(&format!("USE {quote}{object_name}{quote}")),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
object_name.to_string(),
|
||||
|
@ -3058,7 +3052,9 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test double identifier with different type of quotes
|
||||
assert_eq!(
|
||||
snowflake().verified_stmt(&format!("USE {0}CATALOG{0}.{0}my_schema{0}", quote)),
|
||||
snowflake().verified_stmt(&format!(
|
||||
"USE {quote}CATALOG{quote}.{quote}my_schema{quote}"
|
||||
)),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![
|
||||
Ident::with_quote(quote, "CATALOG"),
|
||||
Ident::with_quote(quote, "my_schema")
|
||||
|
@ -3077,35 +3073,37 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test single and double identifier with keyword and different type of quotes
|
||||
assert_eq!(
|
||||
snowflake().verified_stmt(&format!("USE DATABASE {0}my_database{0}", quote)),
|
||||
snowflake().verified_stmt(&format!("USE DATABASE {quote}my_database{quote}")),
|
||||
Statement::Use(Use::Database(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
"my_database".to_string(),
|
||||
)])))
|
||||
);
|
||||
assert_eq!(
|
||||
snowflake().verified_stmt(&format!("USE SCHEMA {0}my_schema{0}", quote)),
|
||||
snowflake().verified_stmt(&format!("USE SCHEMA {quote}my_schema{quote}")),
|
||||
Statement::Use(Use::Schema(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
"my_schema".to_string(),
|
||||
)])))
|
||||
);
|
||||
assert_eq!(
|
||||
snowflake().verified_stmt(&format!("USE SCHEMA {0}CATALOG{0}.{0}my_schema{0}", quote)),
|
||||
snowflake().verified_stmt(&format!(
|
||||
"USE SCHEMA {quote}CATALOG{quote}.{quote}my_schema{quote}"
|
||||
)),
|
||||
Statement::Use(Use::Schema(ObjectName::from(vec![
|
||||
Ident::with_quote(quote, "CATALOG"),
|
||||
Ident::with_quote(quote, "my_schema")
|
||||
])))
|
||||
);
|
||||
assert_eq!(
|
||||
snowflake().verified_stmt(&format!("USE ROLE {0}my_role{0}", quote)),
|
||||
snowflake().verified_stmt(&format!("USE ROLE {quote}my_role{quote}")),
|
||||
Statement::Use(Use::Role(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
"my_role".to_string(),
|
||||
)])))
|
||||
);
|
||||
assert_eq!(
|
||||
snowflake().verified_stmt(&format!("USE WAREHOUSE {0}my_wh{0}", quote)),
|
||||
snowflake().verified_stmt(&format!("USE WAREHOUSE {quote}my_wh{quote}")),
|
||||
Statement::Use(Use::Warehouse(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
"my_wh".to_string(),
|
||||
|
@ -3142,7 +3140,7 @@ fn view_comment_option_should_be_after_column_list() {
|
|||
"CREATE OR REPLACE VIEW v (a COMMENT 'a comment', b, c COMMENT 'c comment') COMMENT = 'Comment' AS SELECT a FROM t",
|
||||
"CREATE OR REPLACE VIEW v (a COMMENT 'a comment', b, c COMMENT 'c comment') WITH (foo = bar) COMMENT = 'Comment' AS SELECT a FROM t",
|
||||
] {
|
||||
snowflake_and_generic()
|
||||
snowflake()
|
||||
.verified_stmt(sql);
|
||||
}
|
||||
}
|
||||
|
@ -3151,7 +3149,7 @@ fn view_comment_option_should_be_after_column_list() {
|
|||
fn parse_view_column_descriptions() {
|
||||
let sql = "CREATE OR REPLACE VIEW v (a COMMENT 'Comment', b) AS SELECT a, b FROM table1";
|
||||
|
||||
match snowflake_and_generic().verified_stmt(sql) {
|
||||
match snowflake().verified_stmt(sql) {
|
||||
Statement::CreateView { name, columns, .. } => {
|
||||
assert_eq!(name.to_string(), "v");
|
||||
assert_eq!(
|
||||
|
@ -3160,7 +3158,9 @@ fn parse_view_column_descriptions() {
|
|||
ViewColumnDef {
|
||||
name: Ident::new("a"),
|
||||
data_type: None,
|
||||
options: Some(vec![ColumnOption::Comment("Comment".to_string())]),
|
||||
options: Some(ColumnOptions::SpaceSeparated(vec![ColumnOption::Comment(
|
||||
"Comment".to_string()
|
||||
)])),
|
||||
},
|
||||
ViewColumnDef {
|
||||
name: Ident::new("b"),
|
||||
|
@ -3645,7 +3645,7 @@ fn test_alter_session_followed_by_statement() {
|
|||
.unwrap();
|
||||
match stmts[..] {
|
||||
[Statement::AlterSession { .. }, Statement::Query { .. }] => {}
|
||||
_ => panic!("Unexpected statements: {:?}", stmts),
|
||||
_ => panic!("Unexpected statements: {stmts:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4101,6 +4101,96 @@ fn parse_connect_by_root_operator() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_begin_exception_end() {
|
||||
for sql in [
|
||||
"BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE; END",
|
||||
"BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE EX_1; END",
|
||||
"BEGIN SELECT 1; EXCEPTION WHEN FOO THEN SELECT 2; WHEN OTHER THEN SELECT 3; RAISE; END",
|
||||
"BEGIN BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE; END; END",
|
||||
] {
|
||||
snowflake().verified_stmt(sql);
|
||||
}
|
||||
|
||||
let sql = r#"
|
||||
DECLARE
|
||||
EXCEPTION_1 EXCEPTION (-20001, 'I caught the expected exception.');
|
||||
EXCEPTION_2 EXCEPTION (-20002, 'Not the expected exception!');
|
||||
EXCEPTION_3 EXCEPTION (-20003, 'The worst exception...');
|
||||
BEGIN
|
||||
BEGIN
|
||||
SELECT 1;
|
||||
EXCEPTION
|
||||
WHEN EXCEPTION_1 THEN
|
||||
SELECT 1;
|
||||
WHEN EXCEPTION_2 OR EXCEPTION_3 THEN
|
||||
SELECT 2;
|
||||
SELECT 3;
|
||||
WHEN OTHER THEN
|
||||
SELECT 4;
|
||||
RAISE;
|
||||
END;
|
||||
END
|
||||
"#;
|
||||
|
||||
// Outer `BEGIN` of the two nested `BEGIN` statements.
|
||||
let Statement::StartTransaction { mut statements, .. } = snowflake()
|
||||
.parse_sql_statements(sql)
|
||||
.unwrap()
|
||||
.pop()
|
||||
.unwrap()
|
||||
else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
// Inner `BEGIN` of the two nested `BEGIN` statements.
|
||||
let Statement::StartTransaction {
|
||||
statements,
|
||||
exception,
|
||||
has_end_keyword,
|
||||
..
|
||||
} = statements.pop().unwrap()
|
||||
else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
assert_eq!(1, statements.len());
|
||||
assert!(has_end_keyword);
|
||||
|
||||
let exception = exception.unwrap();
|
||||
assert_eq!(3, exception.len());
|
||||
assert_eq!(1, exception[0].idents.len());
|
||||
assert_eq!(1, exception[0].statements.len());
|
||||
assert_eq!(2, exception[1].idents.len());
|
||||
assert_eq!(2, exception[1].statements.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_snowflake_fetch_clause_syntax() {
|
||||
let canonical = "SELECT c1 FROM fetch_test FETCH FIRST 2 ROWS ONLY";
|
||||
snowflake().verified_only_select_with_canonical("SELECT c1 FROM fetch_test FETCH 2", canonical);
|
||||
|
||||
snowflake()
|
||||
.verified_only_select_with_canonical("SELECT c1 FROM fetch_test FETCH FIRST 2", canonical);
|
||||
snowflake()
|
||||
.verified_only_select_with_canonical("SELECT c1 FROM fetch_test FETCH NEXT 2", canonical);
|
||||
|
||||
snowflake()
|
||||
.verified_only_select_with_canonical("SELECT c1 FROM fetch_test FETCH 2 ROW", canonical);
|
||||
|
||||
snowflake().verified_only_select_with_canonical(
|
||||
"SELECT c1 FROM fetch_test FETCH FIRST 2 ROWS",
|
||||
canonical,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_snowflake_create_view_with_multiple_column_options() {
|
||||
let create_view_with_tag =
|
||||
r#"CREATE VIEW X (COL WITH TAG (pii='email') COMMENT 'foobar') AS SELECT * FROM Y"#;
|
||||
snowflake().verified_stmt(create_view_with_tag);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_snowflake_create_view_with_composite_tag() {
|
||||
let create_view_with_tag =
|
||||
|
|
|
@ -324,7 +324,7 @@ fn parse_create_table_on_conflict_col() {
|
|||
Keyword::IGNORE,
|
||||
Keyword::REPLACE,
|
||||
] {
|
||||
let sql = format!("CREATE TABLE t1 (a INT, b INT ON CONFLICT {:?})", keyword);
|
||||
let sql = format!("CREATE TABLE t1 (a INT, b INT ON CONFLICT {keyword:?})");
|
||||
match sqlite_and_generic().verified_stmt(&sql) {
|
||||
Statement::CreateTable(CreateTable { columns, .. }) => {
|
||||
assert_eq!(
|
||||
|
@ -410,7 +410,7 @@ fn parse_window_function_with_filter() {
|
|||
"count",
|
||||
"user_defined_function",
|
||||
] {
|
||||
let sql = format!("SELECT {}(x) FILTER (WHERE y) OVER () FROM t", func_name);
|
||||
let sql = format!("SELECT {func_name}(x) FILTER (WHERE y) OVER () FROM t");
|
||||
let select = sqlite().verified_only_select(&sql);
|
||||
assert_eq!(select.to_string(), sql);
|
||||
assert_eq!(
|
||||
|
@ -444,7 +444,7 @@ fn parse_window_function_with_filter() {
|
|||
fn parse_attach_database() {
|
||||
let sql = "ATTACH DATABASE 'test.db' AS test";
|
||||
let verified_stmt = sqlite().verified_stmt(sql);
|
||||
assert_eq!(sql, format!("{}", verified_stmt));
|
||||
assert_eq!(sql, format!("{verified_stmt}"));
|
||||
match verified_stmt {
|
||||
Statement::AttachDatabase {
|
||||
schema_name,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue