Merge branch 'main' into no-whitespace-parser

This commit is contained in:
Luca Cappelletti 2025-11-26 11:33:03 +01:00 committed by GitHub
commit df0b9f2c1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 401 additions and 105 deletions

View file

@ -3099,8 +3099,12 @@ impl fmt::Display for CreateFunction {
if let Some(remote_connection) = &self.remote_connection {
write!(f, " REMOTE WITH CONNECTION {remote_connection}")?;
}
if let Some(CreateFunctionBody::AsBeforeOptions(function_body)) = &self.function_body {
write!(f, " AS {function_body}")?;
if let Some(CreateFunctionBody::AsBeforeOptions { body, link_symbol }) = &self.function_body
{
write!(f, " AS {body}")?;
if let Some(link_symbol) = link_symbol {
write!(f, ", {link_symbol}")?;
}
}
if let Some(CreateFunctionBody::Return(function_body)) = &self.function_body {
write!(f, " RETURN {function_body}")?;
@ -4188,3 +4192,62 @@ impl fmt::Display for OperatorPurpose {
}
}
}
/// `DROP OPERATOR` statement
/// See <https://www.postgresql.org/docs/current/sql-dropoperator.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 DropOperator {
/// `IF EXISTS` clause
pub if_exists: bool,
/// One or more operators to drop with their signatures
pub operators: Vec<DropOperatorSignature>,
/// `CASCADE or RESTRICT`
pub drop_behavior: Option<DropBehavior>,
}
/// Operator signature for a `DROP OPERATOR` statement
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct DropOperatorSignature {
/// Operator name
pub name: ObjectName,
/// Left operand type
pub left_type: Option<DataType>,
/// Right operand type
pub right_type: DataType,
}
impl fmt::Display for DropOperatorSignature {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} (", self.name)?;
if let Some(left_type) = &self.left_type {
write!(f, "{}", left_type)?;
} else {
write!(f, "NONE")?;
}
write!(f, ", {})", self.right_type)
}
}
impl fmt::Display for DropOperator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "DROP OPERATOR")?;
if self.if_exists {
write!(f, " IF EXISTS")?;
}
write!(f, " {}", display_comma_separated(&self.operators))?;
if let Some(drop_behavior) = &self.drop_behavior {
write!(f, " {}", drop_behavior)?;
}
Ok(())
}
}
impl Spanned for DropOperator {
fn span(&self) -> Span {
Span::empty()
}
}

View file

@ -67,14 +67,15 @@ pub use self::ddl::{
ColumnPolicyProperty, ConstraintCharacteristics, CreateConnector, CreateDomain,
CreateExtension, CreateFunction, CreateIndex, CreateOperator, CreateOperatorClass,
CreateOperatorFamily, CreateTable, CreateTrigger, CreateView, Deduplicate, DeferrableInitial,
DropBehavior, DropExtension, DropFunction, DropTrigger, GeneratedAs, GeneratedExpressionMode,
IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind,
IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, Msck,
NullsDistinctOption, OperatorArgTypes, OperatorClassItem, OperatorPurpose, Owner, Partition,
ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, TagsColumnOption,
TriggerObjectKind, Truncate, UserDefinedTypeCompositeAttributeDef,
UserDefinedTypeInternalLength, UserDefinedTypeRangeOption, UserDefinedTypeRepresentation,
UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, ViewColumnDef,
DropBehavior, DropExtension, DropFunction, DropOperator, DropOperatorSignature, DropTrigger,
GeneratedAs, GeneratedExpressionMode, IdentityParameters, IdentityProperty,
IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, IndexColumn,
IndexOption, IndexType, KeyOrIndexDisplay, Msck, NullsDistinctOption, OperatorArgTypes,
OperatorClassItem, OperatorPurpose, Owner, Partition, ProcedureParam, ReferentialAction,
RenameTableNameKind, ReplicaIdentity, TagsColumnOption, TriggerObjectKind, Truncate,
UserDefinedTypeCompositeAttributeDef, UserDefinedTypeInternalLength,
UserDefinedTypeRangeOption, UserDefinedTypeRepresentation, UserDefinedTypeSqlDefinitionOption,
UserDefinedTypeStorage, ViewColumnDef,
};
pub use self::dml::{Copy, Delete, Insert, Update};
pub use self::operator::{BinaryOperator, UnaryOperator};
@ -3560,6 +3561,12 @@ pub enum Statement {
/// <https://www.postgresql.org/docs/current/sql-dropextension.html>
DropExtension(DropExtension),
/// ```sql
/// DROP OPERATOR [ IF EXISTS ] name ( { left_type | NONE } , right_type ) [, ...] [ CASCADE | RESTRICT ]
/// ```
/// Note: this is a PostgreSQL-specific statement.
/// <https://www.postgresql.org/docs/current/sql-dropoperator.html>
DropOperator(DropOperator),
/// ```sql
/// FETCH
/// ```
/// Retrieve rows from a query using a cursor
@ -4786,6 +4793,7 @@ impl fmt::Display for Statement {
Statement::CreateIndex(create_index) => create_index.fmt(f),
Statement::CreateExtension(create_extension) => write!(f, "{create_extension}"),
Statement::DropExtension(drop_extension) => write!(f, "{drop_extension}"),
Statement::DropOperator(drop_operator) => write!(f, "{drop_operator}"),
Statement::CreateRole(create_role) => write!(f, "{create_role}"),
Statement::CreateSecret {
or_replace,
@ -9050,7 +9058,20 @@ pub enum CreateFunctionBody {
/// ```
///
/// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#syntax_11
AsBeforeOptions(Expr),
/// [PostgreSQL]: https://www.postgresql.org/docs/current/sql-createfunction.html
AsBeforeOptions {
/// The primary expression.
body: Expr,
/// Link symbol if the primary expression contains the name of shared library file.
///
/// Example:
/// ```sql
/// CREATE FUNCTION cas_in(input cstring) RETURNS cas
/// AS 'MODULE_PATHNAME', 'cas_in_wrapper'
/// ```
/// [PostgreSQL]: https://www.postgresql.org/docs/current/sql-createfunction.html
link_symbol: Option<Expr>,
},
/// A function body expression using the 'AS' keyword and shows up
/// after any `OPTIONS` clause.
///

View file

@ -368,6 +368,7 @@ impl Spanned for Statement {
Statement::CreateRole(create_role) => create_role.span(),
Statement::CreateExtension(create_extension) => create_extension.span(),
Statement::DropExtension(drop_extension) => drop_extension.span(),
Statement::DropOperator(drop_operator) => drop_operator.span(),
Statement::CreateSecret { .. } => Span::empty(),
Statement::CreateServer { .. } => Span::empty(),
Statement::CreateConnector { .. } => Span::empty(),

View file

@ -397,7 +397,7 @@ impl Dialect for SnowflakeDialect {
fn is_column_alias(&self, kw: &Keyword, parser: &mut Parser) -> bool {
match kw {
// The following keywords can be considered an alias as long as
// The following keywords can be considered an alias as long as
// they are not followed by other tokens that may change their meaning
// e.g. `SELECT * EXCEPT (col1) FROM tbl`
Keyword::EXCEPT
@ -412,7 +412,7 @@ impl Dialect for SnowflakeDialect {
Keyword::LIMIT | Keyword::OFFSET if peek_for_limit_options(parser) => false,
// `FETCH` can be considered an alias as long as it's not followed by `FIRST`` or `NEXT`
// which would give it a different meanings, for example:
// which would give it a different meanings, for example:
// `SELECT 1 FETCH FIRST 10 ROWS` - not an alias
// `SELECT 1 FETCH 10` - not an alias
Keyword::FETCH if parser.peek_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT]).is_some()
@ -421,8 +421,8 @@ impl Dialect for SnowflakeDialect {
false
}
// Reserved keywords by the Snowflake dialect, which seem to be less strictive
// than what is listed in `keywords::RESERVED_FOR_COLUMN_ALIAS`. The following
// Reserved keywords by the Snowflake dialect, which seem to be less strictive
// than what is listed in `keywords::RESERVED_FOR_COLUMN_ALIAS`. The following
// keywords were tested with the this statement: `SELECT 1 <KW>`.
Keyword::FROM
| Keyword::GROUP
@ -692,7 +692,7 @@ pub fn parse_create_table(
.iceberg(iceberg)
.global(global)
.dynamic(dynamic)
.hive_formats(Some(Default::default()));
.hive_formats(None);
// Snowflake does not enforce order of the parameters in the statement. The parser needs to
// parse the statement in a loop.

View file

@ -5140,9 +5140,7 @@ impl<'a> Parser<'a> {
}
if self.parse_keyword(Keyword::AS) {
ensure_not_set(&body.function_body, "AS")?;
body.function_body = Some(CreateFunctionBody::AsBeforeOptions(
self.parse_create_function_body_string()?,
));
body.function_body = Some(self.parse_create_function_body_string()?);
} else if self.parse_keyword(Keyword::LANGUAGE) {
ensure_not_set(&body.language, "LANGUAGE")?;
body.language = Some(self.parse_identifier()?);
@ -5234,7 +5232,7 @@ impl<'a> Parser<'a> {
let name = self.parse_object_name(false)?;
self.expect_keyword_is(Keyword::AS)?;
let as_ = self.parse_create_function_body_string()?;
let body = self.parse_create_function_body_string()?;
let using = self.parse_optional_create_function_using()?;
Ok(Statement::CreateFunction(CreateFunction {
@ -5242,7 +5240,7 @@ impl<'a> Parser<'a> {
or_replace,
temporary,
name,
function_body: Some(CreateFunctionBody::AsBeforeOptions(as_)),
function_body: Some(body),
using,
if_not_exists: false,
args: None,
@ -5304,7 +5302,10 @@ impl<'a> Parser<'a> {
let expr = self.parse_expr()?;
if options.is_none() {
options = self.maybe_parse_options(Keyword::OPTIONS)?;
Some(CreateFunctionBody::AsBeforeOptions(expr))
Some(CreateFunctionBody::AsBeforeOptions {
body: expr,
link_symbol: None,
})
} else {
Some(CreateFunctionBody::AsAfterOptions(expr))
}
@ -5767,15 +5768,19 @@ impl<'a> Parser<'a> {
let hive_distribution = self.parse_hive_distribution()?;
let hive_formats = self.parse_hive_formats()?;
let file_format = if let Some(ff) = &hive_formats.storage {
match ff {
HiveIOFormat::FileFormat { format } => Some(*format),
_ => None,
let file_format = if let Some(ref hf) = hive_formats {
if let Some(ref ff) = hf.storage {
match ff {
HiveIOFormat::FileFormat { format } => Some(*format),
_ => None,
}
} else {
None
}
} else {
None
};
let location = hive_formats.location.clone();
let location = hive_formats.as_ref().and_then(|hf| hf.location.clone());
let table_properties = self.parse_options(Keyword::TBLPROPERTIES)?;
let table_options = if !table_properties.is_empty() {
CreateTableOptions::TableProperties(table_properties)
@ -5786,7 +5791,7 @@ impl<'a> Parser<'a> {
.columns(columns)
.constraints(constraints)
.hive_distribution(hive_distribution)
.hive_formats(Some(hive_formats))
.hive_formats(hive_formats)
.table_options(table_options)
.or_replace(or_replace)
.if_not_exists(if_not_exists)
@ -6703,9 +6708,11 @@ impl<'a> Parser<'a> {
return self.parse_drop_trigger();
} else if self.parse_keyword(Keyword::EXTENSION) {
return self.parse_drop_extension();
} else if self.parse_keyword(Keyword::OPERATOR) {
return self.parse_drop_operator();
} else {
return self.expected(
"CONNECTOR, DATABASE, EXTENSION, FUNCTION, INDEX, POLICY, PROCEDURE, ROLE, SCHEMA, SECRET, SEQUENCE, STAGE, TABLE, TRIGGER, TYPE, VIEW, MATERIALIZED VIEW or USER after DROP",
"CONNECTOR, DATABASE, EXTENSION, FUNCTION, INDEX, OPERATOR, POLICY, PROCEDURE, ROLE, SCHEMA, SECRET, SEQUENCE, STAGE, TABLE, TRIGGER, TYPE, VIEW, MATERIALIZED VIEW or USER after DROP",
self.peek_token(),
);
};
@ -7461,6 +7468,46 @@ impl<'a> Parser<'a> {
}))
}
/// Parse a[Statement::DropOperator] statement.
///
pub fn parse_drop_operator(&mut self) -> Result<Statement, ParserError> {
let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);
let operators = self.parse_comma_separated(|p| p.parse_drop_operator_signature())?;
let drop_behavior = self.parse_optional_drop_behavior();
Ok(Statement::DropOperator(DropOperator {
if_exists,
operators,
drop_behavior,
}))
}
/// Parse an operator signature for a [Statement::DropOperator]
/// Format: `name ( { left_type | NONE } , right_type )`
fn parse_drop_operator_signature(&mut self) -> Result<DropOperatorSignature, ParserError> {
let name = self.parse_operator_name()?;
self.expect_token(&Token::LParen)?;
// Parse left operand type (or NONE for prefix operators)
let left_type = if self.parse_keyword(Keyword::NONE) {
None
} else {
Some(self.parse_data_type()?)
};
self.expect_token(&Token::Comma)?;
// Parse right operand type (always required)
let right_type = self.parse_data_type()?;
self.expect_token(&Token::RParen)?;
Ok(DropOperatorSignature {
name,
left_type,
right_type,
})
}
//TODO: Implement parsing for Skewed
pub fn parse_hive_distribution(&mut self) -> Result<HiveDistributionStyle, ParserError> {
if self.parse_keywords(&[Keyword::PARTITIONED, Keyword::BY]) {
@ -7473,8 +7520,8 @@ impl<'a> Parser<'a> {
}
}
pub fn parse_hive_formats(&mut self) -> Result<HiveFormat, ParserError> {
let mut hive_format = HiveFormat::default();
pub fn parse_hive_formats(&mut self) -> Result<Option<HiveFormat>, ParserError> {
let mut hive_format: Option<HiveFormat> = None;
loop {
match self.parse_one_of_keywords(&[
Keyword::ROW,
@ -7483,7 +7530,9 @@ impl<'a> Parser<'a> {
Keyword::WITH,
]) {
Some(Keyword::ROW) => {
hive_format.row_format = Some(self.parse_row_format()?);
hive_format
.get_or_insert_with(HiveFormat::default)
.row_format = Some(self.parse_row_format()?);
}
Some(Keyword::STORED) => {
self.expect_keyword_is(Keyword::AS)?;
@ -7491,24 +7540,29 @@ impl<'a> Parser<'a> {
let input_format = self.parse_expr()?;
self.expect_keyword_is(Keyword::OUTPUTFORMAT)?;
let output_format = self.parse_expr()?;
hive_format.storage = Some(HiveIOFormat::IOF {
input_format,
output_format,
});
hive_format.get_or_insert_with(HiveFormat::default).storage =
Some(HiveIOFormat::IOF {
input_format,
output_format,
});
} else {
let format = self.parse_file_format()?;
hive_format.storage = Some(HiveIOFormat::FileFormat { format });
hive_format.get_or_insert_with(HiveFormat::default).storage =
Some(HiveIOFormat::FileFormat { format });
}
}
Some(Keyword::LOCATION) => {
hive_format.location = Some(self.parse_literal_string()?);
hive_format.get_or_insert_with(HiveFormat::default).location =
Some(self.parse_literal_string()?);
}
Some(Keyword::WITH) => {
self.prev_token();
let properties = self
.parse_options_with_keywords(&[Keyword::WITH, Keyword::SERDEPROPERTIES])?;
if !properties.is_empty() {
hive_format.serde_properties = Some(properties);
hive_format
.get_or_insert_with(HiveFormat::default)
.serde_properties = Some(properties);
} else {
break;
}
@ -7723,7 +7777,7 @@ impl<'a> Parser<'a> {
.if_not_exists(if_not_exists)
.transient(transient)
.hive_distribution(hive_distribution)
.hive_formats(Some(hive_formats))
.hive_formats(hive_formats)
.global(global)
.query(query)
.without_rowid(without_rowid)
@ -10423,19 +10477,30 @@ impl<'a> Parser<'a> {
/// Parse the body of a `CREATE FUNCTION` specified as a string.
/// e.g. `CREATE FUNCTION ... AS $$ body $$`.
fn parse_create_function_body_string(&mut self) -> Result<Expr, ParserError> {
let peek_token = self.peek_token();
let span = peek_token.span;
match peek_token.token {
Token::DollarQuotedString(s) if dialect_of!(self is PostgreSqlDialect | GenericDialect) =>
{
self.next_token();
Ok(Expr::Value(Value::DollarQuotedString(s).with_span(span)))
fn parse_create_function_body_string(&mut self) -> Result<CreateFunctionBody, ParserError> {
let parse_string_expr = |parser: &mut Parser| -> Result<Expr, ParserError> {
let peek_token = parser.peek_token();
let span = peek_token.span;
match peek_token.token {
Token::DollarQuotedString(s) if dialect_of!(parser is PostgreSqlDialect | GenericDialect) =>
{
parser.next_token();
Ok(Expr::Value(Value::DollarQuotedString(s).with_span(span)))
}
_ => Ok(Expr::Value(
Value::SingleQuotedString(parser.parse_literal_string()?).with_span(span),
)),
}
_ => Ok(Expr::Value(
Value::SingleQuotedString(self.parse_literal_string()?).with_span(span),
)),
}
};
Ok(CreateFunctionBody::AsBeforeOptions {
body: parse_string_expr(self)?,
link_symbol: if self.consume_token(&Token::Comma) {
Some(parse_string_expr(self)?)
} else {
None
},
})
}
/// Parse a literal string

View file

@ -4724,6 +4724,17 @@ fn parse_create_external_table_lowercase() {
assert_matches!(ast, Statement::CreateTable(CreateTable { .. }));
}
#[test]
fn parse_create_table_hive_formats_none_when_no_options() {
let sql = "CREATE TABLE simple_table (id INT, name VARCHAR(100))";
match verified_stmt(sql) {
Statement::CreateTable(CreateTable { hive_formats, .. }) => {
assert_eq!(hive_formats, None);
}
_ => unreachable!(),
}
}
#[test]
fn parse_alter_table() {
let add_column = "ALTER TABLE tab ADD COLUMN foo TEXT;";

View file

@ -739,12 +739,7 @@ fn test_duckdb_union_datatype() {
],
constraints: Default::default(),
hive_distribution: HiveDistributionStyle::NONE,
hive_formats: Some(HiveFormat {
row_format: Default::default(),
serde_properties: Default::default(),
storage: Default::default(),
location: Default::default()
}),
hive_formats: None,
file_format: Default::default(),
location: Default::default(),
query: Default::default(),

View file

@ -408,10 +408,13 @@ fn parse_create_function() {
assert_eq!(name.to_string(), "mydb.myfunc");
assert_eq!(
function_body,
Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
(Value::SingleQuotedString("org.random.class.Name".to_string()))
.with_empty_span()
)))
Some(CreateFunctionBody::AsBeforeOptions {
body: Expr::Value(
(Value::SingleQuotedString("org.random.class.Name".to_string()))
.with_empty_span()
),
link_symbol: None,
})
);
assert_eq!(
using,

View file

@ -1881,12 +1881,7 @@ fn parse_create_table_with_valid_options() {
],
constraints: vec![],
hive_distribution: HiveDistributionStyle::NONE,
hive_formats: Some(HiveFormat {
row_format: None,
serde_properties: None,
storage: None,
location: None,
},),
hive_formats: None,
file_format: None,
location: None,
query: None,
@ -2053,12 +2048,7 @@ fn parse_create_table_with_identity_column() {
},],
constraints: vec![],
hive_distribution: HiveDistributionStyle::NONE,
hive_formats: Some(HiveFormat {
row_format: None,
serde_properties: None,
storage: None,
location: None,
},),
hive_formats: None,
file_format: None,
location: None,
query: None,

View file

@ -23,6 +23,7 @@
mod test_utils;
use helpers::attached_token::AttachedToken;
use sqlparser::ast::{DataType, DropBehavior, DropOperator, DropOperatorSignature};
use sqlparser::tokenizer::Span;
use test_utils::*;
@ -4281,9 +4282,12 @@ $$"#;
behavior: None,
called_on_null: None,
parallel: None,
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
(Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF str1 <> str2 THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span()
))),
function_body: Some(CreateFunctionBody::AsBeforeOptions {
body: Expr::Value(
(Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF str1 <> str2 THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span()
),
link_symbol: None,
}),
if_not_exists: false,
using: None,
determinism_specifier: None,
@ -4319,9 +4323,12 @@ $$"#;
behavior: None,
called_on_null: None,
parallel: None,
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
(Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF int1 <> 0 THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span()
))),
function_body: Some(CreateFunctionBody::AsBeforeOptions {
body: Expr::Value(
(Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF int1 <> 0 THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span()
),
link_symbol: None,
}),
if_not_exists: false,
using: None,
determinism_specifier: None,
@ -4361,9 +4368,12 @@ $$"#;
behavior: None,
called_on_null: None,
parallel: None,
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
(Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF a <> b THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span()
))),
function_body: Some(CreateFunctionBody::AsBeforeOptions {
body: Expr::Value(
(Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF a <> b THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span()
),
link_symbol: None,
}),
if_not_exists: false,
using: None,
determinism_specifier: None,
@ -4403,9 +4413,12 @@ $$"#;
behavior: None,
called_on_null: None,
parallel: None,
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
(Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF int1 <> int2 THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span()
))),
function_body: Some(CreateFunctionBody::AsBeforeOptions {
body: Expr::Value(
(Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF int1 <> int2 THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span()
),
link_symbol: None,
}),
if_not_exists: false,
using: None,
determinism_specifier: None,
@ -4438,13 +4451,16 @@ $$"#;
behavior: None,
called_on_null: None,
parallel: None,
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
(Value::DollarQuotedString(DollarQuotedString {
value: "\n BEGIN\n RETURN TRUE;\n END;\n ".to_owned(),
tag: None
}))
.with_empty_span()
))),
function_body: Some(CreateFunctionBody::AsBeforeOptions {
body: Expr::Value(
(Value::DollarQuotedString(DollarQuotedString {
value: "\n BEGIN\n RETURN TRUE;\n END;\n ".to_owned(),
tag: None
}))
.with_empty_span()
),
link_symbol: None,
}),
if_not_exists: false,
using: None,
determinism_specifier: None,
@ -4476,9 +4492,12 @@ fn parse_create_function() {
behavior: Some(FunctionBehavior::Immutable),
called_on_null: Some(FunctionCalledOnNull::Strict),
parallel: Some(FunctionParallel::Safe),
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
(Value::SingleQuotedString("select $1 + $2;".into())).with_empty_span()
))),
function_body: Some(CreateFunctionBody::AsBeforeOptions {
body: Expr::Value(
(Value::SingleQuotedString("select $1 + $2;".into())).with_empty_span()
),
link_symbol: None,
}),
if_not_exists: false,
using: None,
determinism_specifier: None,
@ -4509,6 +4528,52 @@ fn parse_incorrect_create_function_parallel() {
assert!(pg().parse_sql_statements(sql).is_err());
}
#[test]
fn parse_create_function_c_with_module_pathname() {
let sql = "CREATE FUNCTION cas_in(input cstring) RETURNS cas LANGUAGE c IMMUTABLE PARALLEL SAFE AS 'MODULE_PATHNAME', 'cas_in_wrapper'";
assert_eq!(
pg_and_generic().verified_stmt(sql),
Statement::CreateFunction(CreateFunction {
or_alter: false,
or_replace: false,
temporary: false,
name: ObjectName::from(vec![Ident::new("cas_in")]),
args: Some(vec![OperateFunctionArg::with_name(
"input",
DataType::Custom(ObjectName::from(vec![Ident::new("cstring")]), vec![]),
),]),
return_type: Some(DataType::Custom(
ObjectName::from(vec![Ident::new("cas")]),
vec![]
)),
language: Some("c".into()),
behavior: Some(FunctionBehavior::Immutable),
called_on_null: None,
parallel: Some(FunctionParallel::Safe),
function_body: Some(CreateFunctionBody::AsBeforeOptions {
body: Expr::Value(
(Value::SingleQuotedString("MODULE_PATHNAME".into())).with_empty_span()
),
link_symbol: Some(Expr::Value(
(Value::SingleQuotedString("cas_in_wrapper".into())).with_empty_span()
)),
}),
if_not_exists: false,
using: None,
determinism_specifier: None,
options: None,
remote_connection: None,
})
);
// Test that attribute order flexibility works (IMMUTABLE before LANGUAGE)
let sql_alt_order = "CREATE FUNCTION cas_in(input cstring) RETURNS cas IMMUTABLE PARALLEL SAFE LANGUAGE c AS 'MODULE_PATHNAME', 'cas_in_wrapper'";
pg_and_generic().one_statement_parses_to(
sql_alt_order,
"CREATE FUNCTION cas_in(input cstring) RETURNS cas LANGUAGE c IMMUTABLE PARALLEL SAFE AS 'MODULE_PATHNAME', 'cas_in_wrapper'"
);
}
#[test]
fn parse_drop_function() {
let sql = "DROP FUNCTION IF EXISTS test_func";
@ -6037,12 +6102,7 @@ fn parse_trigger_related_functions() {
],
constraints: vec![],
hive_distribution: HiveDistributionStyle::NONE,
hive_formats: Some(HiveFormat {
row_format: None,
serde_properties: None,
storage: None,
location: None
}),
hive_formats: None,
file_format: None,
location: None,
query: None,
@ -6096,8 +6156,8 @@ fn parse_trigger_related_functions() {
args: Some(vec![]),
return_type: Some(DataType::Trigger),
function_body: Some(
CreateFunctionBody::AsBeforeOptions(
Expr::Value((
CreateFunctionBody::AsBeforeOptions {
body: Expr::Value((
Value::DollarQuotedString(
DollarQuotedString {
value: "\n BEGIN\n -- Check that empname and salary are given\n IF NEW.empname IS NULL THEN\n RAISE EXCEPTION 'empname cannot be null';\n END IF;\n IF NEW.salary IS NULL THEN\n RAISE EXCEPTION '% cannot have null salary', NEW.empname;\n END IF;\n\n -- Who works for us when they must pay for it?\n IF NEW.salary < 0 THEN\n RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;\n END IF;\n\n -- Remember who changed the payroll when\n NEW.last_date := current_timestamp;\n NEW.last_user := current_user;\n RETURN NEW;\n END;\n ".to_owned(),
@ -6107,7 +6167,8 @@ fn parse_trigger_related_functions() {
},
)
).with_empty_span()),
),
link_symbol: None,
},
),
behavior: None,
called_on_null: None,
@ -6785,6 +6846,92 @@ fn parse_create_operator() {
assert!(pg().parse_sql_statements("CREATE OPERATOR > ())").is_err());
}
#[test]
fn parse_drop_operator() {
// Test DROP OPERATOR with NONE for prefix operator
let sql = "DROP OPERATOR ~ (NONE, BIT)";
assert_eq!(
pg_and_generic().verified_stmt(sql),
Statement::DropOperator(DropOperator {
if_exists: false,
operators: vec![DropOperatorSignature {
name: ObjectName::from(vec![Ident::new("~")]),
left_type: None,
right_type: DataType::Bit(None),
}],
drop_behavior: None,
})
);
for if_exist in [true, false] {
for cascading in [
None,
Some(DropBehavior::Cascade),
Some(DropBehavior::Restrict),
] {
for op in &["<", ">", "<=", ">=", "<>", "||", "&&", "<<", ">>"] {
let sql = format!(
"DROP OPERATOR{} {op} (INTEGER, INTEGER){}",
if if_exist { " IF EXISTS" } else { "" },
match cascading {
Some(cascading) => format!(" {cascading}"),
None => String::new(),
}
);
assert_eq!(
pg_and_generic().verified_stmt(&sql),
Statement::DropOperator(DropOperator {
if_exists: if_exist,
operators: vec![DropOperatorSignature {
name: ObjectName::from(vec![Ident::new(*op)]),
left_type: Some(DataType::Integer(None)),
right_type: DataType::Integer(None),
}],
drop_behavior: cascading,
})
);
}
}
}
// Test DROP OPERATOR with schema-qualified operator name
let sql = "DROP OPERATOR myschema.@@ (TEXT, TEXT)";
assert_eq!(
pg_and_generic().verified_stmt(sql),
Statement::DropOperator(DropOperator {
if_exists: false,
operators: vec![DropOperatorSignature {
name: ObjectName::from(vec![Ident::new("myschema"), Ident::new("@@")]),
left_type: Some(DataType::Text),
right_type: DataType::Text,
}],
drop_behavior: None,
})
);
// Test DROP OPERATOR with multiple operators, IF EXISTS and CASCADE
let sql = "DROP OPERATOR IF EXISTS + (INTEGER, INTEGER), - (INTEGER, INTEGER) CASCADE";
assert_eq!(
pg_and_generic().verified_stmt(sql),
Statement::DropOperator(DropOperator {
if_exists: true,
operators: vec![
DropOperatorSignature {
name: ObjectName::from(vec![Ident::new("+")]),
left_type: Some(DataType::Integer(None)),
right_type: DataType::Integer(None),
},
DropOperatorSignature {
name: ObjectName::from(vec![Ident::new("-")]),
left_type: Some(DataType::Integer(None)),
right_type: DataType::Integer(None),
}
],
drop_behavior: Some(DropBehavior::Cascade),
})
);
}
#[test]
fn parse_create_operator_family() {
for index_method in &["btree", "hash", "gist", "gin", "spgist", "brin"] {