From a3fa3b5660329ba6e5d7e27192f0149aa6a49ece Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Thu, 25 Jul 2024 19:10:54 +0200 Subject: [PATCH 01/51] Added tests for the TRIGGERs --- tests/sqlparser_postgres.rs | 128 ++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 5ac421da0..6f0a13fd4 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4441,3 +4441,131 @@ fn test_table_unnest_with_ordinality() { _ => panic!("Expecting TableFactor::UNNEST with ordinality"), } } + +#[test] +fn parse_create_trigger() { + let sql = "CREATE TRIGGER check_update BEFORE UPDATE ON accounts FOR EACH ROW EXECUTE FUNCTION check_account_update()"; + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: TriggerPeriod::Before, + event: vec![TriggerEvent::Update(vec![])], + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + condition: None, + exec_body: TriggerExecBody { + exec_type: ExecBodyType::Function, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: vec![] + } + } + } + ); + + let sql = "CREATE OR REPLACE TRIGGER check_update BEFORE UPDATE OF balance ON accounts FOR EACH ROW EXECUTE FUNCTION check_account_update()"; + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: true, + name: ObjectName(vec![Ident::new("check_update")]), + period: TriggerPeriod::Before, + event: vec![TriggerEvent::Update(vec![Ident::new("balance")])], + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + condition: None, + exec_body: TriggerExecBody { + exec_type: ExecBodyType::Function, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: vec![] + } + } + } + ); + + let sql = "CREATE TRIGGER check_update BEFORE UPDATE ON accounts FOR EACH ROW WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE FUNCTION check_account_update()"; + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: TriggerPeriod::Before, + event: vec![TriggerEvent::Update(vec![])], + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + condition: Some("OLD.balance IS DISTINCT FROM NEW.balance".into()), + exec_body: TriggerExecBody { + exec_type: ExecBodyType::Function, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: vec![] + } + } + } + ); + + let sql = "CREATE TRIGGER transfer_insert AFTER INSERT ON transfer REFERENCING NEW TABLE AS inserted FOR EACH STATEMENT EXECUTE FUNCTION check_transfer_balances_to_zero()"; + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("transfer_insert")]), + period: TriggerPeriod::After, + event: vec![TriggerEvent::Insert], + table_name: ObjectName(vec![Ident::new("transfer")]), + referencing: vec![TriggerReferencing { + refer_type: TriggerReferencingType::NewTable, + is_as: true, + transition_relation_name: ObjectName(vec![Ident::new("inserted")]) + }], + for_each: Some(TriggerObject::Statement), + condition: None, + exec_body: TriggerExecBody { + exec_type: ExecBodyType::Function, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_transfer_balances_to_zero")]), + args: vec![] + } + } + } + ); + + let sql = "CREATE TRIGGER paired_items_update AFTER UPDATE ON paired_items REFERENCING NEW TABLE AS newtab OLD TABLE AS oldtab FOR EACH ROW EXECUTE FUNCTION check_matching_pairs()"; + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("paired_items_update")]), + period: TriggerPeriod::After, + event: vec![TriggerEvent::Update(vec![])], + table_name: ObjectName(vec![Ident::new("paired_items")]), + referencing: vec![ + TriggerReferencing { + refer_type: TriggerReferencingType::NewTable, + is_as: true, + transition_relation_name: ObjectName(vec![Ident::new("newtab")]) + }, + TriggerReferencing { + refer_type: TriggerReferencingType::OldTable, + is_as: true, + transition_relation_name: ObjectName(vec![Ident::new("oldtab")]) + } + ], + for_each: Some(TriggerObject::Row), + condition: None, + exec_body: TriggerExecBody { + exec_type: ExecBodyType::Function, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_matching_pairs")]), + args: vec![] + } + } + } + ); +} From 308b1a7ee5eda61b58f74c6fecef5dbbb27c5974 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Thu, 25 Jul 2024 19:11:16 +0200 Subject: [PATCH 02/51] Added missing keywords needed for triggers --- src/keywords.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/keywords.rs b/src/keywords.rs index e59e49339..5af7827b2 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -108,6 +108,7 @@ define_keywords!( AVRO, BACKWARD, BASE64, + BEFORE, BEGIN, BEGIN_FRAME, BEGIN_PARTITION, @@ -375,6 +376,7 @@ define_keywords!( INSENSITIVE, INSERT, INSTALL, + INSTEAD, INT, INT128, INT16, @@ -679,6 +681,7 @@ define_keywords!( STABLE, STAGE, START, + STATEMENT, STATIC, STATISTICS, STATUS, From 764707f74fdf0fc46af5f8fc83834a533f5d6a81 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Thu, 25 Jul 2024 19:11:48 +0200 Subject: [PATCH 03/51] Created new module for the AST-related objects for the TRIGGERs --- src/ast/trigger.rs | 165 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 src/ast/trigger.rs diff --git a/src/ast/trigger.rs b/src/ast/trigger.rs new file mode 100644 index 000000000..5fa346dde --- /dev/null +++ b/src/ast/trigger.rs @@ -0,0 +1,165 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! SQL Abstract Syntax Tree (AST) for triggers. +use super::*; + +/// Function describe in DROP FUNCTION. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct FunctionDesc { + pub name: ObjectName, + pub args: Vec, +} + +impl fmt::Display for FunctionDesc { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}({})", self.name, display_comma_separated(&self.args)) + } +} + +/// This specifies whether the trigger function should be fired once for every row affected by the trigger event, or just once per SQL statement. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum TriggerObject { + Row, + Statement, +} + +impl fmt::Display for TriggerObject { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TriggerObject::Row => write!(f, "ROW"), + TriggerObject::Statement => write!(f, "STATEMENT"), + } + } +} + +/// This clause indicates whether the following relation name is for the before-image transition relation or the after-image transition relation +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum TriggerReferencingType { + OldTable, + NewTable, +} + +impl fmt::Display for TriggerReferencingType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TriggerReferencingType::OldTable => write!(f, "OLD TABLE"), + TriggerReferencingType::NewTable => write!(f, "NEW TABLE"), + } + } +} + +/// This keyword immediately precedes the declaration of one or two relation names that provide access to the transition relations of the triggering statement +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct TriggerReferencing { + pub refer_type: TriggerReferencingType, + pub is_as: bool, + pub transition_relation_name: ObjectName, +} + +impl fmt::Display for TriggerReferencing { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{refer_type}{is_as} {relation_name}", + refer_type = self.refer_type, + is_as = if self.is_as { " AS" } else { "" }, + relation_name = self.transition_relation_name + ) + } +} + +/// Used to describe trigger events +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum TriggerEvent { + Insert, + Update(Vec), + Delete, + Truncate, +} + +impl fmt::Display for TriggerEvent { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TriggerEvent::Insert => write!(f, "INSERT"), + TriggerEvent::Update(columns) => { + write!(f, "UPDATE")?; + if !columns.is_empty() { + write!(f, " OF")?; + write!(f, " {}", display_comma_separated(columns))?; + } + Ok(()) + } + TriggerEvent::Delete => write!(f, "DELETE"), + TriggerEvent::Truncate => write!(f, "TRUNCATE"), + } + } +} + +/// Trigger period +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum TriggerPeriod { + After, + Before, + InsteadOf, +} + +impl fmt::Display for TriggerPeriod { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TriggerPeriod::After => write!(f, "AFTER"), + TriggerPeriod::Before => write!(f, "BEFORE"), + TriggerPeriod::InsteadOf => write!(f, "INSTEAD OF"), + } + } +} + +/// Execute function or stored procedure +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum ExecBodyType { + Function, + Proceduer, +} + +impl fmt::Display for ExecBodyType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ExecBodyType::Function => write!(f, "FUNCTION"), + ExecBodyType::Proceduer => write!(f, "PROCEDURE"), + } + } +} +/// This keyword immediately precedes the declaration of one or two relation names that provide access to the transition relations of the triggering statement +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct TriggerExecBody { + pub exec_type: ExecBodyType, + pub func_desc: FunctionDesc, +} + +impl fmt::Display for TriggerExecBody { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{exec_type} {func_desc}", + exec_type = self.exec_type, + func_desc = self.func_desc + ) + } +} From 637c6320f19ca3ba331418c3a62d16a5b9668993 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Thu, 25 Jul 2024 19:12:26 +0200 Subject: [PATCH 04/51] Integrated TRIGGERS in AST enum --- src/ast/mod.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index cdc2e2049..f03162c43 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -53,6 +53,12 @@ pub use self::query::{ TableAlias, TableFactor, TableVersion, TableWithJoins, Top, TopQuantity, ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill, }; + +pub use self::trigger::{ + TriggerEvent, TriggerExecBody, TriggerObject, TriggerPeriod, TriggerReferencing, + TriggerReferencingType, ExecBodyType, FunctionDesc +}; + pub use self::value::{ escape_double_quote_string, escape_quoted_string, DateTimeField, DollarQuotedString, TrimWhereField, Value, @@ -71,6 +77,7 @@ mod dml; pub mod helpers; mod operator; mod query; +mod trigger; mod value; #[cfg(feature = "visitor")] @@ -2589,6 +2596,26 @@ pub enum Statement { /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_a_remote_function) remote_connection: Option, }, + /// CREATE TRIGGER + /// + /// Postgres: https://www.postgresql.org/docs/current/sql-createtrigger.html + CreateTrigger { + or_replace: bool, + name: ObjectName, + /// Determines whether the function is called before, after, or instead of the event. + period: TriggerPeriod, + /// Multiple events can be specified using OR + event: Vec, + table_name: ObjectName, + /// This keyword immediately precedes the declaration of one or two relation names that provide access to the transition relations of the triggering statement. + referencing: Vec, + /// This specifies whether the trigger function should be fired once for every row affected by the trigger event, or just once per SQL statement. + for_each: Option, + /// Triggering conditions + condition: Option, + /// Execute logic block + exec_body: TriggerExecBody, + }, /// ```sql /// CREATE PROCEDURE /// ``` @@ -3344,6 +3371,37 @@ impl fmt::Display for Statement { } Ok(()) } + Statement::CreateTrigger { + or_replace, + name, + period, + event, + table_name, + referencing, + for_each, + condition, + exec_body, + } => { + write!( + f, + "CREATE {or_replace}TRIGGER {name} {period}", + or_replace = if *or_replace { "OR REPLACE " } else { "" }, + )?; + if !event.is_empty() { + write!(f, " {}", display_separated(event, "or"))?; + } + write!(f, " ON {table_name}")?; + if !referencing.is_empty() { + write!(f, " REFERENCING {}", display_separated(referencing, " "))?; + } + if let Some(trigger_object) = for_each { + write!(f, " FOR EACH {trigger_object}")?; + } + if let Some(condition) = condition { + write!(f, " WHEN ({condition})")?; + } + write!(f, " EXECUTE {exec_body}") + } Statement::CreateProcedure { name, or_alter, From e760c070cb1f43854838f02c8667611ed060009c Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Thu, 25 Jul 2024 19:12:42 +0200 Subject: [PATCH 05/51] Added methods to parse triggers --- src/parser/mod.rs | 200 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f8267a7cb..81b25fd82 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -2019,6 +2019,25 @@ impl<'a> Parser<'a> { }) } + /// Parse a keyword-separated list of 1+ items accepted by `F` + pub fn parse_keyword_separated( + &mut self, + keyword: Keyword, + mut f: F, + ) -> Result, ParserError> + where + F: FnMut(&mut Parser<'a>) -> Result, + { + let mut values = vec![]; + loop { + values.push(f(self)?); + if !self.parse_keyword(keyword) { + break; + } + } + Ok(values) + } + /// Parse an `INTERVAL` expression. /// /// Some syntactically valid intervals: @@ -3549,6 +3568,8 @@ impl<'a> Parser<'a> { self.parse_create_external_table(or_replace) } else if self.parse_keyword(Keyword::FUNCTION) { self.parse_create_function(or_replace, temporary) + } else if self.parse_keyword(Keyword::TRIGGER) { + self.parse_create_trigger(or_replace) } else if self.parse_keyword(Keyword::MACRO) { self.parse_create_macro(or_replace, temporary) } else if self.parse_keyword(Keyword::SECRET) { @@ -4139,6 +4160,185 @@ impl<'a> Parser<'a> { }) } + pub fn parse_create_trigger(&mut self, or_replace: bool) -> Result { + if dialect_of!(self is PostgreSqlDialect) { + let name = self.parse_object_name(false)?; + let period = self.parse_trigger_period()?; + + let event = self.parse_keyword_separated(Keyword::OR, Parser::parse_trigger_event)?; + let table_name = if self.parse_keyword(Keyword::ON) { + self.parse_object_name(false)? + } else { + return self.expected("keyword `ON`", self.peek_token()); + }; + + let mut referencing = vec![]; + if self.parse_keyword(Keyword::REFERENCING) { + while let Some(refer) = self.parse_trigger_referencing()? { + referencing.push(refer); + } + } + + let for_each = if self.parse_keywords(&[Keyword::FOR]) { + let _ = self.parse_keyword(Keyword::EACH); + match self.parse_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT]) { + Some(Keyword::ROW) => Some(TriggerObject::Row), + Some(Keyword::STATEMENT) => Some(TriggerObject::Statement), + _ => { + return self.expected("an `ROW` OR `STATEMENT`", self.peek_token()); + } + } + } else { + None + }; + + let condition = if self.parse_keyword(Keyword::WHEN) { + self.expect_token(&Token::LParen)?; + let mut lparen_count = 1; + let mut condition_str = String::new(); + loop { + if lparen_count == 0 { + break; + } + if let Some(next_token) = self.next_token_no_skip() { + match &next_token.token { + Token::LParen => lparen_count += 1, + Token::RParen => lparen_count -= 1, + Token::EOF => { + return self.expected(" `)` ", TokenWithLocation::wrap(Token::EOF)); + } + _ => {} + } + if lparen_count == 0 { + break; + } + condition_str.push_str(next_token.token.to_string().as_str()); + } + } + Some(condition_str) + } else { + None + }; + + let exec_body = if self.parse_keyword(Keyword::EXECUTE) { + self.parse_trigger_exec_body()? + } else { + return self.expected("an `EXECUTE`", self.peek_token()); + }; + + Ok(Statement::CreateTrigger { + or_replace, + name, + period, + event, + table_name, + referencing, + for_each, + condition, + exec_body, + }) + } else { + self.prev_token(); + self.expected("an object type after CREATE", self.peek_token()) + } + } + + pub fn parse_trigger_period(&mut self) -> Result { + match self.parse_one_of_keywords(&[Keyword::BEFORE, Keyword::AFTER, Keyword::INSTEAD]) { + Some(Keyword::BEFORE) => Ok(TriggerPeriod::Before), + Some(Keyword::AFTER) => Ok(TriggerPeriod::After), + Some(Keyword::INSTEAD) => { + if self.parse_keyword(Keyword::OF) { + Ok(TriggerPeriod::InsteadOf) + } else { + self.expected("an `OF` after `INSTEAD` ", self.peek_token()) + } + } + _ => self.expected("an `BEFORE`, `AFTER` OR `INSTEAD OF`", self.peek_token()), + } + } + + pub fn parse_trigger_event(&mut self) -> Result { + match self.parse_one_of_keywords(&[ + Keyword::INSERT, + Keyword::UPDATE, + Keyword::DELETE, + Keyword::TRUNCATE, + ]) { + Some(Keyword::INSERT) => Ok(TriggerEvent::Insert), + Some(Keyword::UPDATE) => { + if self.parse_keyword(Keyword::OF) { + let cols = + self.parse_comma_separated(|ident| Parser::parse_identifier(ident, false))?; + Ok(TriggerEvent::Update(cols)) + } else { + Ok(TriggerEvent::Update(vec![])) + } + } + Some(Keyword::DELETE) => Ok(TriggerEvent::Delete), + Some(Keyword::TRUNCATE) => Ok(TriggerEvent::Truncate), + _ => self.expected( + "an `INSERT`, `UPDATE`, `DELETE` OR `TRUNCATE`", + self.peek_token(), + ), + } + } + + pub fn parse_trigger_referencing(&mut self) -> Result, ParserError> { + let refer_type = match self.parse_one_of_keywords(&[Keyword::OLD, Keyword::NEW]) { + Some(Keyword::OLD) if self.parse_keyword(Keyword::TABLE) => { + TriggerReferencingType::OldTable + } + Some(Keyword::NEW) if self.parse_keyword(Keyword::TABLE) => { + TriggerReferencingType::NewTable + } + _ => { + return Ok(None); + } + }; + + let is_as = self.parse_keyword(Keyword::AS); + let transition_relation_name = self.parse_object_name(false)?; + Ok(Some(TriggerReferencing { + refer_type, + is_as, + transition_relation_name, + })) + } + + pub fn parse_trigger_exec_body(&mut self) -> Result { + let exec_type = match self.parse_one_of_keywords(&[Keyword::FUNCTION, Keyword::PROCEDURE]) { + Some(Keyword::FUNCTION) => ExecBodyType::Function, + Some(Keyword::PROCEDURE) => ExecBodyType::Proceduer, + _ => { + return self.expected("an `FUNCTION` OR `PROCEDURE`", self.peek_token()); + } + }; + + let func_desc = self.parse_function_desc()?; + Ok(TriggerExecBody { + exec_type, + func_desc, + }) + } + + fn parse_function_desc(&mut self) -> Result { + let name = self.parse_object_name(false)?; + let args = if self.consume_token(&Token::LParen) { + if self.consume_token(&Token::RParen) { + vec![] + } else { + let args = self.parse_comma_separated(Parser::parse_function_arg)?; + self.expect_token(&Token::RParen)?; + args + } + } else { + vec![] + }; + + Ok(FunctionDesc { name, args }) + } + pub fn parse_create_macro( &mut self, or_replace: bool, From 7474836545c6d2adc56c30ff646cb1399aa768e4 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Thu, 25 Jul 2024 19:56:49 +0200 Subject: [PATCH 06/51] Extended testing and resolved some corner cases in triggers --- src/ast/mod.rs | 4 ++-- src/parser/mod.rs | 24 +-------------------- src/test_utils.rs | 1 + tests/sqlparser_postgres.rs | 42 ++++++++++++++++++++++++++++++++++++- 4 files changed, 45 insertions(+), 26 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index f03162c43..da68b6cd4 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2612,7 +2612,7 @@ pub enum Statement { /// This specifies whether the trigger function should be fired once for every row affected by the trigger event, or just once per SQL statement. for_each: Option, /// Triggering conditions - condition: Option, + condition: Option, /// Execute logic block exec_body: TriggerExecBody, }, @@ -3398,7 +3398,7 @@ impl fmt::Display for Statement { write!(f, " FOR EACH {trigger_object}")?; } if let Some(condition) = condition { - write!(f, " WHEN ({condition})")?; + write!(f, " WHEN {condition}")?; } write!(f, " EXECUTE {exec_body}") } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 81b25fd82..20af79563 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4193,29 +4193,7 @@ impl<'a> Parser<'a> { }; let condition = if self.parse_keyword(Keyword::WHEN) { - self.expect_token(&Token::LParen)?; - let mut lparen_count = 1; - let mut condition_str = String::new(); - loop { - if lparen_count == 0 { - break; - } - if let Some(next_token) = self.next_token_no_skip() { - match &next_token.token { - Token::LParen => lparen_count += 1, - Token::RParen => lparen_count -= 1, - Token::EOF => { - return self.expected(" `)` ", TokenWithLocation::wrap(Token::EOF)); - } - _ => {} - } - if lparen_count == 0 { - break; - } - condition_str.push_str(next_token.token.to_string().as_str()); - } - } - Some(condition_str) + Some(self.parse_expr()?) } else { None }; diff --git a/src/test_utils.rs b/src/test_utils.rs index 1f5300be1..6a9e083f3 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -124,6 +124,7 @@ impl TestedDialects { } let only_statement = statements.pop().unwrap(); + if !canonical.is_empty() { assert_eq!(canonical, only_statement.to_string()) } diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 6f0a13fd4..5a3a37035 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4499,7 +4499,47 @@ fn parse_create_trigger() { table_name: ObjectName(vec![Ident::new("accounts")]), referencing: vec![], for_each: Some(TriggerObject::Row), - condition: Some("OLD.balance IS DISTINCT FROM NEW.balance".into()), + condition: Some(Expr::Nested(Box::new(Expr::IsDistinctFrom( + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("OLD"), + Ident::new("balance") + ])), + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("NEW"), + Ident::new("balance") + ])), + )))), + exec_body: TriggerExecBody { + exec_type: ExecBodyType::Function, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: vec![] + } + } + } + ); + + let sql = "CREATE TRIGGER check_update BEFORE UPDATE ON accounts FOR EACH ROW WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE FUNCTION check_account_update()"; + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: TriggerPeriod::Before, + event: vec![TriggerEvent::Update(vec![])], + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + condition: Some(Expr::Nested(Box::new(Expr::IsNotDistinctFrom( + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("OLD"), + Ident::new("balance") + ])), + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("NEW"), + Ident::new("balance") + ])), + )))), exec_body: TriggerExecBody { exec_type: ExecBodyType::Function, func_desc: FunctionDesc { From 9fa36d9acea1b5be87a245a2d10d8f248a0f783b Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Fri, 26 Jul 2024 09:00:35 +0200 Subject: [PATCH 07/51] Formatted code --- src/ast/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index da68b6cd4..be846845e 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -55,8 +55,8 @@ pub use self::query::{ }; pub use self::trigger::{ - TriggerEvent, TriggerExecBody, TriggerObject, TriggerPeriod, TriggerReferencing, - TriggerReferencingType, ExecBodyType, FunctionDesc + ExecBodyType, FunctionDesc, TriggerEvent, TriggerExecBody, TriggerObject, TriggerPeriod, + TriggerReferencing, TriggerReferencingType, }; pub use self::value::{ From d6c3e51b0228d78625f3ea7ee7fe011498942bb8 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Fri, 26 Jul 2024 09:06:32 +0200 Subject: [PATCH 08/51] Derived visitor trait and resolved clippy code smells --- src/ast/trigger.rs | 8 ++++++++ src/keywords.rs | 2 +- src/test_utils.rs | 8 ++++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/ast/trigger.rs b/src/ast/trigger.rs index 5fa346dde..bfc82581f 100644 --- a/src/ast/trigger.rs +++ b/src/ast/trigger.rs @@ -16,6 +16,7 @@ use super::*; /// Function describe in DROP FUNCTION. #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct FunctionDesc { pub name: ObjectName, pub args: Vec, @@ -30,6 +31,7 @@ impl fmt::Display for FunctionDesc { /// This specifies whether the trigger function should be fired once for every row affected by the trigger event, or just once per SQL statement. #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum TriggerObject { Row, Statement, @@ -47,6 +49,7 @@ impl fmt::Display for TriggerObject { /// This clause indicates whether the following relation name is for the before-image transition relation or the after-image transition relation #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum TriggerReferencingType { OldTable, NewTable, @@ -64,6 +67,7 @@ impl fmt::Display for TriggerReferencingType { /// This keyword immediately precedes the declaration of one or two relation names that provide access to the transition relations of the triggering 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 TriggerReferencing { pub refer_type: TriggerReferencingType, pub is_as: bool, @@ -85,6 +89,7 @@ impl fmt::Display for TriggerReferencing { /// Used to describe trigger events #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum TriggerEvent { Insert, Update(Vec), @@ -113,6 +118,7 @@ impl fmt::Display for TriggerEvent { /// Trigger period #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum TriggerPeriod { After, Before, @@ -132,6 +138,7 @@ impl fmt::Display for TriggerPeriod { /// Execute function or stored procedure #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum ExecBodyType { Function, Proceduer, @@ -148,6 +155,7 @@ impl fmt::Display for ExecBodyType { /// This keyword immediately precedes the declaration of one or two relation names that provide access to the transition relations of the triggering 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 TriggerExecBody { pub exec_type: ExecBodyType, pub func_desc: FunctionDesc, diff --git a/src/keywords.rs b/src/keywords.rs index 5af7827b2..fb038894b 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -20,7 +20,7 @@ //! As a matter of fact, most of these keywords are not used at all //! and could be removed. //! 3) a `RESERVED_FOR_TABLE_ALIAS` array with keywords reserved in a -//! "table alias" context. +//! "table alias" context. #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; diff --git a/src/test_utils.rs b/src/test_utils.rs index 6a9e083f3..a7c829e36 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -111,10 +111,10 @@ impl TestedDialects { /// that: /// /// 1. parsing `sql` results in the same [`Statement`] as parsing - /// `canonical`. + /// `canonical`. /// /// 2. re-serializing the result of parsing `sql` produces the same - /// `canonical` sql string + /// `canonical` sql string pub fn one_statement_parses_to(&self, sql: &str, canonical: &str) -> Statement { let mut statements = self.parse_sql_statements(sql).expect(sql); assert_eq!(statements.len(), 1); @@ -181,10 +181,10 @@ impl TestedDialects { /// Ensures that `sql` parses as a single [`Select`], and that additionally: /// /// 1. parsing `sql` results in the same [`Statement`] as parsing - /// `canonical`. + /// `canonical`. /// /// 2. re-serializing the result of parsing `sql` produces the same - /// `canonical` sql string + /// `canonical` sql string pub fn verified_only_select_with_canonical(&self, query: &str, canonical: &str) -> Select { let q = match self.one_statement_parses_to(query, canonical) { Statement::Query(query) => *query, From 8104de9b88dcde55b67d90df8a02c009997f3c6c Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Fri, 26 Jul 2024 09:13:01 +0200 Subject: [PATCH 09/51] Resolved documentation issue and extended test coverage for Triggers --- src/ast/mod.rs | 51 +++++++++++++++++++++++++++++++++++-- tests/sqlparser_postgres.rs | 34 +++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index be846845e..049843255 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2597,15 +2597,62 @@ pub enum Statement { remote_connection: Option, }, /// CREATE TRIGGER + /// + /// Examples: + /// + /// ```sql + /// CREATE TRIGGER trigger_name + /// BEFORE INSERT ON table_name + /// FOR EACH ROW + /// EXECUTE FUNCTION trigger_function(); + /// ``` /// - /// Postgres: https://www.postgresql.org/docs/current/sql-createtrigger.html + /// Postgres: CreateTrigger { + /// The `OR REPLACE` clause is used to re-create the trigger if it already exists. + /// + /// Example: + /// ```sql + /// CREATE OR REPLACE TRIGGER trigger_name + /// AFTER INSERT ON table_name + /// FOR EACH ROW + /// EXECUTE FUNCTION trigger_function(); + /// ``` or_replace: bool, + /// The name of the trigger to be created. name: ObjectName, /// Determines whether the function is called before, after, or instead of the event. + /// + /// Example of BEFORE: + /// + /// ```sql + /// CREATE TRIGGER trigger_name + /// BEFORE INSERT ON table_name + /// FOR EACH ROW + /// EXECUTE FUNCTION trigger_function(); + /// ``` + /// + /// Example of AFTER: + /// + /// ```sql + /// CREATE TRIGGER trigger_name + /// AFTER INSERT ON table_name + /// FOR EACH ROW + /// EXECUTE FUNCTION trigger_function(); + /// ``` + /// + /// Example of INSTEAD OF: + /// + /// ```sql + /// CREATE TRIGGER trigger_name + /// INSTEAD OF INSERT ON table_name + /// FOR EACH ROW + /// EXECUTE FUNCTION trigger_function(); + /// ``` period: TriggerPeriod, - /// Multiple events can be specified using OR + /// Multiple events can be specified using OR, such as `INSERT`, `UPDATE`, `DELETE`, or `TRUNCATE`. event: Vec, + /// The table on which the trigger is to be created. table_name: ObjectName, /// This keyword immediately precedes the declaration of one or two relation names that provide access to the transition relations of the triggering statement. referencing: Vec, diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 5a3a37035..3eea649f3 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4576,6 +4576,40 @@ fn parse_create_trigger() { } ); + let sql = "CREATE TRIGGER instead_of_paired_items_update INSTEAD OF UPDATE ON paired_items REFERENCING NEW TABLE AS newtab OLD TABLE AS oldtab FOR EACH ROW EXECUTE FUNCTION check_matching_pairs()"; + + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("instead_of_paired_items_update")]), + period: TriggerPeriod::InsteadOf, + event: vec![TriggerEvent::Update(vec![])], + table_name: ObjectName(vec![Ident::new("paired_items")]), + referencing: vec![ + TriggerReferencing { + refer_type: TriggerReferencingType::NewTable, + is_as: true, + transition_relation_name: ObjectName(vec![Ident::new("newtab")]) + }, + TriggerReferencing { + refer_type: TriggerReferencingType::OldTable, + is_as: true, + transition_relation_name: ObjectName(vec![Ident::new("oldtab")]) + } + ], + for_each: Some(TriggerObject::Row), + condition: None, + exec_body: TriggerExecBody { + exec_type: ExecBodyType::Function, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_matching_pairs")]), + args: vec![] + } + } + } + ); + let sql = "CREATE TRIGGER paired_items_update AFTER UPDATE ON paired_items REFERENCING NEW TABLE AS newtab OLD TABLE AS oldtab FOR EACH ROW EXECUTE FUNCTION check_matching_pairs()"; assert_eq!( pg().verified_stmt(sql), From 9c72a623a8d83f847391f65eec42076645047771 Mon Sep 17 00:00:00 2001 From: Luca Cappelletti Date: Fri, 26 Jul 2024 12:22:27 +0200 Subject: [PATCH 10/51] Update src/parser/mod.rs Co-authored-by: hulk --- src/parser/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 20af79563..95897cc4f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4229,7 +4229,7 @@ impl<'a> Parser<'a> { if self.parse_keyword(Keyword::OF) { Ok(TriggerPeriod::InsteadOf) } else { - self.expected("an `OF` after `INSTEAD` ", self.peek_token()) + self.expected("an `OF` after `INSTEAD`", self.peek_token()) } } _ => self.expected("an `BEFORE`, `AFTER` OR `INSTEAD OF`", self.peek_token()), From 5a9a19ee244ee1256c12135a277243a64b8134a3 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Fri, 26 Jul 2024 12:32:02 +0200 Subject: [PATCH 11/51] Added support for optional "EACH" display and extended test suite --- src/ast/mod.rs | 11 +- src/parser/mod.rs | 24 ++- tests/sqlparser_postgres.rs | 367 +++++++++++++++++++----------------- 3 files changed, 213 insertions(+), 189 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 049843255..9683df92d 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2658,6 +2658,8 @@ pub enum Statement { referencing: Vec, /// This specifies whether the trigger function should be fired once for every row affected by the trigger event, or just once per SQL statement. for_each: Option, + /// Whether to include the `EACH` term of the `FOR EACH`, as it is optional syntax. + include_each: bool, /// Triggering conditions condition: Option, /// Execute logic block @@ -3427,6 +3429,7 @@ impl fmt::Display for Statement { referencing, for_each, condition, + include_each, exec_body, } => { write!( @@ -3435,14 +3438,18 @@ impl fmt::Display for Statement { or_replace = if *or_replace { "OR REPLACE " } else { "" }, )?; if !event.is_empty() { - write!(f, " {}", display_separated(event, "or"))?; + write!(f, " {}", display_separated(event, "OR"))?; } write!(f, " ON {table_name}")?; if !referencing.is_empty() { write!(f, " REFERENCING {}", display_separated(referencing, " "))?; } if let Some(trigger_object) = for_each { - write!(f, " FOR EACH {trigger_object}")?; + if *include_each { + write!(f, " FOR EACH {trigger_object}")?; + } else { + write!(f, " FOR {trigger_object}")?; + } } if let Some(condition) = condition { write!(f, " WHEN {condition}")?; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 95897cc4f..a204061e9 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4179,17 +4179,20 @@ impl<'a> Parser<'a> { } } - let for_each = if self.parse_keywords(&[Keyword::FOR]) { - let _ = self.parse_keyword(Keyword::EACH); - match self.parse_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT]) { - Some(Keyword::ROW) => Some(TriggerObject::Row), - Some(Keyword::STATEMENT) => Some(TriggerObject::Statement), - _ => { - return self.expected("an `ROW` OR `STATEMENT`", self.peek_token()); - } - } + let (for_each, include_each) = if self.parse_keywords(&[Keyword::FOR]) { + let include_each = self.parse_keyword(Keyword::EACH); + ( + match self.parse_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT]) { + Some(Keyword::ROW) => Some(TriggerObject::Row), + Some(Keyword::STATEMENT) => Some(TriggerObject::Statement), + _ => { + return self.expected("an `ROW` OR `STATEMENT`", self.peek_token()); + } + }, + include_each, + ) } else { - None + (None, false) }; let condition = if self.parse_keyword(Keyword::WHEN) { @@ -4212,6 +4215,7 @@ impl<'a> Parser<'a> { table_name, referencing, for_each, + include_each, condition, exec_body, }) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 3eea649f3..20456a626 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4444,202 +4444,215 @@ fn test_table_unnest_with_ordinality() { #[test] fn parse_create_trigger() { - let sql = "CREATE TRIGGER check_update BEFORE UPDATE ON accounts FOR EACH ROW EXECUTE FUNCTION check_account_update()"; - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: TriggerPeriod::Before, - event: vec![TriggerEvent::Update(vec![])], - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - condition: None, - exec_body: TriggerExecBody { - exec_type: ExecBodyType::Function, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: vec![] - } - } - } - ); + for include_each in [true, false] { + let for_each = if include_each { "FOR EACH" } else { "FOR" }; - let sql = "CREATE OR REPLACE TRIGGER check_update BEFORE UPDATE OF balance ON accounts FOR EACH ROW EXECUTE FUNCTION check_account_update()"; - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: true, - name: ObjectName(vec![Ident::new("check_update")]), - period: TriggerPeriod::Before, - event: vec![TriggerEvent::Update(vec![Ident::new("balance")])], - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - condition: None, - exec_body: TriggerExecBody { - exec_type: ExecBodyType::Function, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: vec![] + let sql = &format!( + "CREATE TRIGGER check_update BEFORE UPDATE ON accounts {for_each} ROW EXECUTE FUNCTION check_account_update()" + ); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: TriggerPeriod::Before, + event: vec![TriggerEvent::Update(vec![])], + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + include_each, + condition: None, + exec_body: TriggerExecBody { + exec_type: ExecBodyType::Function, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: vec![] + } } } - } - ); + ); - let sql = "CREATE TRIGGER check_update BEFORE UPDATE ON accounts FOR EACH ROW WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE FUNCTION check_account_update()"; - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: TriggerPeriod::Before, - event: vec![TriggerEvent::Update(vec![])], - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - condition: Some(Expr::Nested(Box::new(Expr::IsDistinctFrom( - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("OLD"), - Ident::new("balance") - ])), - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("NEW"), - Ident::new("balance") - ])), - )))), - exec_body: TriggerExecBody { - exec_type: ExecBodyType::Function, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: vec![] + let sql = &format!("CREATE OR REPLACE TRIGGER check_update BEFORE UPDATE OF balance ON accounts {for_each} ROW EXECUTE FUNCTION check_account_update()"); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: true, + name: ObjectName(vec![Ident::new("check_update")]), + period: TriggerPeriod::Before, + event: vec![TriggerEvent::Update(vec![Ident::new("balance")])], + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + include_each, + condition: None, + exec_body: TriggerExecBody { + exec_type: ExecBodyType::Function, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: vec![] + } } } - } - ); + ); - let sql = "CREATE TRIGGER check_update BEFORE UPDATE ON accounts FOR EACH ROW WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE FUNCTION check_account_update()"; - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: TriggerPeriod::Before, - event: vec![TriggerEvent::Update(vec![])], - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - condition: Some(Expr::Nested(Box::new(Expr::IsNotDistinctFrom( - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("OLD"), - Ident::new("balance") - ])), - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("NEW"), - Ident::new("balance") - ])), - )))), - exec_body: TriggerExecBody { - exec_type: ExecBodyType::Function, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: vec![] + let sql = &format!("CREATE TRIGGER check_update BEFORE UPDATE ON accounts {for_each} ROW WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE FUNCTION check_account_update()"); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: TriggerPeriod::Before, + event: vec![TriggerEvent::Update(vec![])], + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + include_each, + condition: Some(Expr::Nested(Box::new(Expr::IsDistinctFrom( + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("OLD"), + Ident::new("balance") + ])), + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("NEW"), + Ident::new("balance") + ])), + )))), + exec_body: TriggerExecBody { + exec_type: ExecBodyType::Function, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: vec![] + } } } - } - ); + ); - let sql = "CREATE TRIGGER transfer_insert AFTER INSERT ON transfer REFERENCING NEW TABLE AS inserted FOR EACH STATEMENT EXECUTE FUNCTION check_transfer_balances_to_zero()"; - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("transfer_insert")]), - period: TriggerPeriod::After, - event: vec![TriggerEvent::Insert], - table_name: ObjectName(vec![Ident::new("transfer")]), - referencing: vec![TriggerReferencing { - refer_type: TriggerReferencingType::NewTable, - is_as: true, - transition_relation_name: ObjectName(vec![Ident::new("inserted")]) - }], - for_each: Some(TriggerObject::Statement), - condition: None, - exec_body: TriggerExecBody { - exec_type: ExecBodyType::Function, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_transfer_balances_to_zero")]), - args: vec![] + let sql = &format!("CREATE TRIGGER check_update BEFORE UPDATE ON accounts {for_each} ROW WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE FUNCTION check_account_update()"); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: TriggerPeriod::Before, + event: vec![TriggerEvent::Update(vec![])], + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + include_each, + condition: Some(Expr::Nested(Box::new(Expr::IsNotDistinctFrom( + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("OLD"), + Ident::new("balance") + ])), + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("NEW"), + Ident::new("balance") + ])), + )))), + exec_body: TriggerExecBody { + exec_type: ExecBodyType::Function, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: vec![] + } } } - } - ); - - let sql = "CREATE TRIGGER instead_of_paired_items_update INSTEAD OF UPDATE ON paired_items REFERENCING NEW TABLE AS newtab OLD TABLE AS oldtab FOR EACH ROW EXECUTE FUNCTION check_matching_pairs()"; + ); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("instead_of_paired_items_update")]), - period: TriggerPeriod::InsteadOf, - event: vec![TriggerEvent::Update(vec![])], - table_name: ObjectName(vec![Ident::new("paired_items")]), - referencing: vec![ - TriggerReferencing { + let sql = &format!("CREATE TRIGGER transfer_insert AFTER INSERT ON transfer REFERENCING NEW TABLE AS inserted {for_each} STATEMENT EXECUTE FUNCTION check_transfer_balances_to_zero()"); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("transfer_insert")]), + period: TriggerPeriod::After, + event: vec![TriggerEvent::Insert], + table_name: ObjectName(vec![Ident::new("transfer")]), + referencing: vec![TriggerReferencing { refer_type: TriggerReferencingType::NewTable, is_as: true, - transition_relation_name: ObjectName(vec![Ident::new("newtab")]) - }, - TriggerReferencing { - refer_type: TriggerReferencingType::OldTable, - is_as: true, - transition_relation_name: ObjectName(vec![Ident::new("oldtab")]) - } - ], - for_each: Some(TriggerObject::Row), - condition: None, - exec_body: TriggerExecBody { - exec_type: ExecBodyType::Function, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_matching_pairs")]), - args: vec![] + transition_relation_name: ObjectName(vec![Ident::new("inserted")]) + }], + for_each: Some(TriggerObject::Statement), + include_each, + condition: None, + exec_body: TriggerExecBody { + exec_type: ExecBodyType::Function, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_transfer_balances_to_zero")]), + args: vec![] + } } } - } - ); + ); - let sql = "CREATE TRIGGER paired_items_update AFTER UPDATE ON paired_items REFERENCING NEW TABLE AS newtab OLD TABLE AS oldtab FOR EACH ROW EXECUTE FUNCTION check_matching_pairs()"; - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("paired_items_update")]), - period: TriggerPeriod::After, - event: vec![TriggerEvent::Update(vec![])], - table_name: ObjectName(vec![Ident::new("paired_items")]), - referencing: vec![ - TriggerReferencing { - refer_type: TriggerReferencingType::NewTable, - is_as: true, - transition_relation_name: ObjectName(vec![Ident::new("newtab")]) - }, - TriggerReferencing { - refer_type: TriggerReferencingType::OldTable, - is_as: true, - transition_relation_name: ObjectName(vec![Ident::new("oldtab")]) + let sql = &format!("CREATE TRIGGER instead_of_paired_items_update INSTEAD OF UPDATE ON paired_items REFERENCING NEW TABLE AS newtab OLD TABLE AS oldtab {for_each} ROW EXECUTE FUNCTION check_matching_pairs()"); + + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("instead_of_paired_items_update")]), + period: TriggerPeriod::InsteadOf, + event: vec![TriggerEvent::Update(vec![])], + table_name: ObjectName(vec![Ident::new("paired_items")]), + referencing: vec![ + TriggerReferencing { + refer_type: TriggerReferencingType::NewTable, + is_as: true, + transition_relation_name: ObjectName(vec![Ident::new("newtab")]) + }, + TriggerReferencing { + refer_type: TriggerReferencingType::OldTable, + is_as: true, + transition_relation_name: ObjectName(vec![Ident::new("oldtab")]) + } + ], + for_each: Some(TriggerObject::Row), + include_each, + condition: None, + exec_body: TriggerExecBody { + exec_type: ExecBodyType::Function, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_matching_pairs")]), + args: vec![] + } } - ], - for_each: Some(TriggerObject::Row), - condition: None, - exec_body: TriggerExecBody { - exec_type: ExecBodyType::Function, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_matching_pairs")]), - args: vec![] + } + ); + + let sql = &format!("CREATE TRIGGER paired_items_update AFTER UPDATE ON paired_items REFERENCING NEW TABLE AS newtab OLD TABLE AS oldtab {for_each} ROW EXECUTE FUNCTION check_matching_pairs()"); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("paired_items_update")]), + period: TriggerPeriod::After, + event: vec![TriggerEvent::Update(vec![])], + table_name: ObjectName(vec![Ident::new("paired_items")]), + referencing: vec![ + TriggerReferencing { + refer_type: TriggerReferencingType::NewTable, + is_as: true, + transition_relation_name: ObjectName(vec![Ident::new("newtab")]) + }, + TriggerReferencing { + refer_type: TriggerReferencingType::OldTable, + is_as: true, + transition_relation_name: ObjectName(vec![Ident::new("oldtab")]) + } + ], + for_each: Some(TriggerObject::Row), + include_each, + condition: None, + exec_body: TriggerExecBody { + exec_type: ExecBodyType::Function, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_matching_pairs")]), + args: vec![] + } } } - } - ); + ); + } } From d5a6af12fd8fae89d9e861dc143dafd5378a67ab Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Fri, 26 Jul 2024 12:39:31 +0200 Subject: [PATCH 12/51] Removed empty spaced with cargo fmt --- src/ast/mod.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 9683df92d..a314b6e7e 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2597,9 +2597,9 @@ pub enum Statement { remote_connection: Option, }, /// CREATE TRIGGER - /// + /// /// Examples: - /// + /// /// ```sql /// CREATE TRIGGER trigger_name /// BEFORE INSERT ON table_name @@ -2610,7 +2610,7 @@ pub enum Statement { /// Postgres: CreateTrigger { /// The `OR REPLACE` clause is used to re-create the trigger if it already exists. - /// + /// /// Example: /// ```sql /// CREATE OR REPLACE TRIGGER trigger_name @@ -2622,27 +2622,27 @@ pub enum Statement { /// The name of the trigger to be created. name: ObjectName, /// Determines whether the function is called before, after, or instead of the event. - /// + /// /// Example of BEFORE: - /// + /// /// ```sql /// CREATE TRIGGER trigger_name /// BEFORE INSERT ON table_name /// FOR EACH ROW /// EXECUTE FUNCTION trigger_function(); /// ``` - /// + /// /// Example of AFTER: - /// + /// /// ```sql /// CREATE TRIGGER trigger_name /// AFTER INSERT ON table_name /// FOR EACH ROW /// EXECUTE FUNCTION trigger_function(); /// ``` - /// + /// /// Example of INSTEAD OF: - /// + /// /// ```sql /// CREATE TRIGGER trigger_name /// INSTEAD OF INSERT ON table_name From e6294c11fccbfc15f56ff08d3affa99646a41395 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 27 Jul 2024 08:19:50 +0200 Subject: [PATCH 13/51] Renamed TRIGGER-related struct and fixed a copy-paste error and a typo --- src/ast/mod.rs | 2 +- src/ast/trigger.rs | 22 +++++++++++----------- src/parser/mod.rs | 8 ++++---- tests/sqlparser_postgres.rs | 28 ++++++++++++++-------------- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index a314b6e7e..49028d410 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -55,7 +55,7 @@ pub use self::query::{ }; pub use self::trigger::{ - ExecBodyType, FunctionDesc, TriggerEvent, TriggerExecBody, TriggerObject, TriggerPeriod, + TriggerExecBodyType, TriggerFunctionDesc, TriggerEvent, TriggerExecBody, TriggerObject, TriggerPeriod, TriggerReferencing, TriggerReferencingType, }; diff --git a/src/ast/trigger.rs b/src/ast/trigger.rs index bfc82581f..60e76f167 100644 --- a/src/ast/trigger.rs +++ b/src/ast/trigger.rs @@ -13,16 +13,16 @@ //! SQL Abstract Syntax Tree (AST) for triggers. use super::*; -/// Function describe in DROP FUNCTION. +/// Function argument description for trigger function. #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct FunctionDesc { +pub struct TriggerFunctionDesc { pub name: ObjectName, pub args: Vec, } -impl fmt::Display for FunctionDesc { +impl fmt::Display for TriggerFunctionDesc { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}({})", self.name, display_comma_separated(&self.args)) } @@ -135,20 +135,20 @@ impl fmt::Display for TriggerPeriod { } } -/// Execute function or stored procedure +/// Types of trigger body execution body. #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub enum ExecBodyType { +pub enum TriggerExecBodyType { Function, - Proceduer, + Procedure, } -impl fmt::Display for ExecBodyType { +impl fmt::Display for TriggerExecBodyType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - ExecBodyType::Function => write!(f, "FUNCTION"), - ExecBodyType::Proceduer => write!(f, "PROCEDURE"), + TriggerExecBodyType::Function => write!(f, "FUNCTION"), + TriggerExecBodyType::Procedure => write!(f, "PROCEDURE"), } } } @@ -157,8 +157,8 @@ impl fmt::Display for ExecBodyType { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct TriggerExecBody { - pub exec_type: ExecBodyType, - pub func_desc: FunctionDesc, + pub exec_type: TriggerExecBodyType, + pub func_desc: TriggerFunctionDesc, } impl fmt::Display for TriggerExecBody { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index a204061e9..cb692e356 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4290,8 +4290,8 @@ impl<'a> Parser<'a> { pub fn parse_trigger_exec_body(&mut self) -> Result { let exec_type = match self.parse_one_of_keywords(&[Keyword::FUNCTION, Keyword::PROCEDURE]) { - Some(Keyword::FUNCTION) => ExecBodyType::Function, - Some(Keyword::PROCEDURE) => ExecBodyType::Proceduer, + Some(Keyword::FUNCTION) => TriggerExecBodyType::Function, + Some(Keyword::PROCEDURE) => TriggerExecBodyType::Procedure, _ => { return self.expected("an `FUNCTION` OR `PROCEDURE`", self.peek_token()); } @@ -4304,7 +4304,7 @@ impl<'a> Parser<'a> { }) } - fn parse_function_desc(&mut self) -> Result { + fn parse_function_desc(&mut self) -> Result { let name = self.parse_object_name(false)?; let args = if self.consume_token(&Token::LParen) { if self.consume_token(&Token::RParen) { @@ -4318,7 +4318,7 @@ impl<'a> Parser<'a> { vec![] }; - Ok(FunctionDesc { name, args }) + Ok(TriggerFunctionDesc { name, args }) } pub fn parse_create_macro( diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 20456a626..bf17cabc2 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4463,8 +4463,8 @@ fn parse_create_trigger() { include_each, condition: None, exec_body: TriggerExecBody { - exec_type: ExecBodyType::Function, - func_desc: FunctionDesc { + exec_type: TriggerExecBodyType::Function, + func_desc: TriggerFunctionDesc { name: ObjectName(vec![Ident::new("check_account_update")]), args: vec![] } @@ -4486,8 +4486,8 @@ fn parse_create_trigger() { include_each, condition: None, exec_body: TriggerExecBody { - exec_type: ExecBodyType::Function, - func_desc: FunctionDesc { + exec_type: TriggerExecBodyType::Function, + func_desc: TriggerFunctionDesc { name: ObjectName(vec![Ident::new("check_account_update")]), args: vec![] } @@ -4518,8 +4518,8 @@ fn parse_create_trigger() { ])), )))), exec_body: TriggerExecBody { - exec_type: ExecBodyType::Function, - func_desc: FunctionDesc { + exec_type: TriggerExecBodyType::Function, + func_desc: TriggerFunctionDesc { name: ObjectName(vec![Ident::new("check_account_update")]), args: vec![] } @@ -4550,8 +4550,8 @@ fn parse_create_trigger() { ])), )))), exec_body: TriggerExecBody { - exec_type: ExecBodyType::Function, - func_desc: FunctionDesc { + exec_type: TriggerExecBodyType::Function, + func_desc: TriggerFunctionDesc { name: ObjectName(vec![Ident::new("check_account_update")]), args: vec![] } @@ -4577,8 +4577,8 @@ fn parse_create_trigger() { include_each, condition: None, exec_body: TriggerExecBody { - exec_type: ExecBodyType::Function, - func_desc: FunctionDesc { + exec_type: TriggerExecBodyType::Function, + func_desc: TriggerFunctionDesc { name: ObjectName(vec![Ident::new("check_transfer_balances_to_zero")]), args: vec![] } @@ -4612,8 +4612,8 @@ fn parse_create_trigger() { include_each, condition: None, exec_body: TriggerExecBody { - exec_type: ExecBodyType::Function, - func_desc: FunctionDesc { + exec_type: TriggerExecBodyType::Function, + func_desc: TriggerFunctionDesc { name: ObjectName(vec![Ident::new("check_matching_pairs")]), args: vec![] } @@ -4646,8 +4646,8 @@ fn parse_create_trigger() { include_each, condition: None, exec_body: TriggerExecBody { - exec_type: ExecBodyType::Function, - func_desc: FunctionDesc { + exec_type: TriggerExecBodyType::Function, + func_desc: TriggerFunctionDesc { name: ObjectName(vec![Ident::new("check_matching_pairs")]), args: vec![] } From 6f7c9c3301d95598702fd4672014be28bf3046dd Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 27 Jul 2024 08:20:24 +0200 Subject: [PATCH 14/51] Reformatted code< --- src/ast/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 49028d410..cb49cd9fa 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -55,8 +55,8 @@ pub use self::query::{ }; pub use self::trigger::{ - TriggerExecBodyType, TriggerFunctionDesc, TriggerEvent, TriggerExecBody, TriggerObject, TriggerPeriod, - TriggerReferencing, TriggerReferencingType, + TriggerEvent, TriggerExecBody, TriggerExecBodyType, TriggerFunctionDesc, TriggerObject, + TriggerPeriod, TriggerReferencing, TriggerReferencingType, }; pub use self::value::{ From 429b20224a358de3271cff4287906b15c7766db4 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 27 Jul 2024 08:30:29 +0200 Subject: [PATCH 15/51] Merged DropFunctionDesc and TriggerFunctionDesc into FunctionDesc --- src/ast/mod.rs | 12 ++++---- src/ast/trigger.rs | 17 +---------- src/parser/mod.rs | 25 +++------------- tests/sqlparser_postgres.rs | 58 ++++++++++++++++++------------------- 4 files changed, 40 insertions(+), 72 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index cb49cd9fa..885e3ae8c 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -55,8 +55,8 @@ pub use self::query::{ }; pub use self::trigger::{ - TriggerEvent, TriggerExecBody, TriggerExecBodyType, TriggerFunctionDesc, TriggerObject, - TriggerPeriod, TriggerReferencing, TriggerReferencingType, + TriggerEvent, TriggerExecBody, TriggerExecBodyType, TriggerObject, TriggerPeriod, + TriggerReferencing, TriggerReferencingType, }; pub use self::value::{ @@ -2260,7 +2260,7 @@ pub enum Statement { DropFunction { if_exists: bool, /// One or more function to drop - func_desc: Vec, + func_desc: Vec, /// `CASCADE` or `RESTRICT` option: Option, }, @@ -2270,7 +2270,7 @@ pub enum Statement { DropProcedure { if_exists: bool, /// One or more function to drop - proc_desc: Vec, + proc_desc: Vec, /// `CASCADE` or `RESTRICT` option: Option, }, @@ -6065,12 +6065,12 @@ impl fmt::Display for DropFunctionOption { #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct DropFunctionDesc { +pub struct FunctionDesc { pub name: ObjectName, pub args: Option>, } -impl fmt::Display for DropFunctionDesc { +impl fmt::Display for FunctionDesc { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.name)?; if let Some(args) = &self.args { diff --git a/src/ast/trigger.rs b/src/ast/trigger.rs index 60e76f167..de4664e17 100644 --- a/src/ast/trigger.rs +++ b/src/ast/trigger.rs @@ -13,21 +13,6 @@ //! SQL Abstract Syntax Tree (AST) for triggers. use super::*; -/// Function argument description for trigger function. -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct TriggerFunctionDesc { - pub name: ObjectName, - pub args: Vec, -} - -impl fmt::Display for TriggerFunctionDesc { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}({})", self.name, display_comma_separated(&self.args)) - } -} - /// This specifies whether the trigger function should be fired once for every row affected by the trigger event, or just once per SQL statement. #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -158,7 +143,7 @@ impl fmt::Display for TriggerExecBodyType { #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct TriggerExecBody { pub exec_type: TriggerExecBodyType, - pub func_desc: TriggerFunctionDesc, + pub func_desc: FunctionDesc, } impl fmt::Display for TriggerExecBody { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index cb692e356..a9cf6ae61 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4304,23 +4304,6 @@ impl<'a> Parser<'a> { }) } - fn parse_function_desc(&mut self) -> Result { - let name = self.parse_object_name(false)?; - let args = if self.consume_token(&Token::LParen) { - if self.consume_token(&Token::RParen) { - vec![] - } else { - let args = self.parse_comma_separated(Parser::parse_function_arg)?; - self.expect_token(&Token::RParen)?; - args - } - } else { - vec![] - }; - - Ok(TriggerFunctionDesc { name, args }) - } - pub fn parse_create_macro( &mut self, or_replace: bool, @@ -4810,7 +4793,7 @@ impl<'a> Parser<'a> { /// ``` fn parse_drop_function(&mut self) -> Result { let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); - let func_desc = self.parse_comma_separated(Parser::parse_drop_function_desc)?; + let func_desc = self.parse_comma_separated(Parser::parse_function_desc)?; let option = match self.parse_one_of_keywords(&[Keyword::CASCADE, Keyword::RESTRICT]) { Some(Keyword::CASCADE) => Some(ReferentialAction::Cascade), Some(Keyword::RESTRICT) => Some(ReferentialAction::Restrict), @@ -4829,7 +4812,7 @@ impl<'a> Parser<'a> { /// ``` fn parse_drop_procedure(&mut self) -> Result { let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); - let proc_desc = self.parse_comma_separated(Parser::parse_drop_function_desc)?; + let proc_desc = self.parse_comma_separated(Parser::parse_function_desc)?; let option = match self.parse_one_of_keywords(&[Keyword::CASCADE, Keyword::RESTRICT]) { Some(Keyword::CASCADE) => Some(ReferentialAction::Cascade), Some(Keyword::RESTRICT) => Some(ReferentialAction::Restrict), @@ -4843,7 +4826,7 @@ impl<'a> Parser<'a> { }) } - fn parse_drop_function_desc(&mut self) -> Result { + fn parse_function_desc(&mut self) -> Result { let name = self.parse_object_name(false)?; let args = if self.consume_token(&Token::LParen) { @@ -4858,7 +4841,7 @@ impl<'a> Parser<'a> { None }; - Ok(DropFunctionDesc { name, args }) + Ok(FunctionDesc { name, args }) } /// See [DuckDB Docs](https://duckdb.org/docs/sql/statements/create_secret.html) for more details. diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index bf17cabc2..bd99e5c7c 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -3621,7 +3621,7 @@ fn parse_drop_function() { pg().verified_stmt(sql), Statement::DropFunction { if_exists: true, - func_desc: vec![DropFunctionDesc { + func_desc: vec![FunctionDesc { name: ObjectName(vec![Ident { value: "test_func".to_string(), quote_style: None @@ -3637,7 +3637,7 @@ fn parse_drop_function() { pg().verified_stmt(sql), Statement::DropFunction { if_exists: true, - func_desc: vec![DropFunctionDesc { + func_desc: vec![FunctionDesc { name: ObjectName(vec![Ident { value: "test_func".to_string(), quote_style: None @@ -3662,7 +3662,7 @@ fn parse_drop_function() { Statement::DropFunction { if_exists: true, func_desc: vec![ - DropFunctionDesc { + FunctionDesc { name: ObjectName(vec![Ident { value: "test_func1".to_string(), quote_style: None @@ -3680,7 +3680,7 @@ fn parse_drop_function() { } ]), }, - DropFunctionDesc { + FunctionDesc { name: ObjectName(vec![Ident { value: "test_func2".to_string(), quote_style: None @@ -3711,7 +3711,7 @@ fn parse_drop_procedure() { pg().verified_stmt(sql), Statement::DropProcedure { if_exists: true, - proc_desc: vec![DropFunctionDesc { + proc_desc: vec![FunctionDesc { name: ObjectName(vec![Ident { value: "test_proc".to_string(), quote_style: None @@ -3727,7 +3727,7 @@ fn parse_drop_procedure() { pg().verified_stmt(sql), Statement::DropProcedure { if_exists: true, - proc_desc: vec![DropFunctionDesc { + proc_desc: vec![FunctionDesc { name: ObjectName(vec![Ident { value: "test_proc".to_string(), quote_style: None @@ -3752,7 +3752,7 @@ fn parse_drop_procedure() { Statement::DropProcedure { if_exists: true, proc_desc: vec![ - DropFunctionDesc { + FunctionDesc { name: ObjectName(vec![Ident { value: "test_proc1".to_string(), quote_style: None @@ -3770,7 +3770,7 @@ fn parse_drop_procedure() { } ]), }, - DropFunctionDesc { + FunctionDesc { name: ObjectName(vec![Ident { value: "test_proc2".to_string(), quote_style: None @@ -4448,7 +4448,7 @@ fn parse_create_trigger() { let for_each = if include_each { "FOR EACH" } else { "FOR" }; let sql = &format!( - "CREATE TRIGGER check_update BEFORE UPDATE ON accounts {for_each} ROW EXECUTE FUNCTION check_account_update()" + "CREATE TRIGGER check_update BEFORE UPDATE ON accounts {for_each} ROW EXECUTE FUNCTION check_account_update" ); assert_eq!( pg().verified_stmt(sql), @@ -4464,15 +4464,15 @@ fn parse_create_trigger() { condition: None, exec_body: TriggerExecBody { exec_type: TriggerExecBodyType::Function, - func_desc: TriggerFunctionDesc { + func_desc: FunctionDesc { name: ObjectName(vec![Ident::new("check_account_update")]), - args: vec![] + args: None } } } ); - let sql = &format!("CREATE OR REPLACE TRIGGER check_update BEFORE UPDATE OF balance ON accounts {for_each} ROW EXECUTE FUNCTION check_account_update()"); + let sql = &format!("CREATE OR REPLACE TRIGGER check_update BEFORE UPDATE OF balance ON accounts {for_each} ROW EXECUTE FUNCTION check_account_update"); assert_eq!( pg().verified_stmt(sql), Statement::CreateTrigger { @@ -4487,15 +4487,15 @@ fn parse_create_trigger() { condition: None, exec_body: TriggerExecBody { exec_type: TriggerExecBodyType::Function, - func_desc: TriggerFunctionDesc { + func_desc: FunctionDesc { name: ObjectName(vec![Ident::new("check_account_update")]), - args: vec![] + args: None } } } ); - let sql = &format!("CREATE TRIGGER check_update BEFORE UPDATE ON accounts {for_each} ROW WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE FUNCTION check_account_update()"); + let sql = &format!("CREATE TRIGGER check_update BEFORE UPDATE ON accounts {for_each} ROW WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE FUNCTION check_account_update"); assert_eq!( pg().verified_stmt(sql), Statement::CreateTrigger { @@ -4519,15 +4519,15 @@ fn parse_create_trigger() { )))), exec_body: TriggerExecBody { exec_type: TriggerExecBodyType::Function, - func_desc: TriggerFunctionDesc { + func_desc: FunctionDesc { name: ObjectName(vec![Ident::new("check_account_update")]), - args: vec![] + args: None } } } ); - let sql = &format!("CREATE TRIGGER check_update BEFORE UPDATE ON accounts {for_each} ROW WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE FUNCTION check_account_update()"); + let sql = &format!("CREATE TRIGGER check_update BEFORE UPDATE ON accounts {for_each} ROW WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE FUNCTION check_account_update"); assert_eq!( pg().verified_stmt(sql), Statement::CreateTrigger { @@ -4551,15 +4551,15 @@ fn parse_create_trigger() { )))), exec_body: TriggerExecBody { exec_type: TriggerExecBodyType::Function, - func_desc: TriggerFunctionDesc { + func_desc: FunctionDesc { name: ObjectName(vec![Ident::new("check_account_update")]), - args: vec![] + args: None } } } ); - let sql = &format!("CREATE TRIGGER transfer_insert AFTER INSERT ON transfer REFERENCING NEW TABLE AS inserted {for_each} STATEMENT EXECUTE FUNCTION check_transfer_balances_to_zero()"); + let sql = &format!("CREATE TRIGGER transfer_insert AFTER INSERT ON transfer REFERENCING NEW TABLE AS inserted {for_each} STATEMENT EXECUTE FUNCTION check_transfer_balances_to_zero"); assert_eq!( pg().verified_stmt(sql), Statement::CreateTrigger { @@ -4578,15 +4578,15 @@ fn parse_create_trigger() { condition: None, exec_body: TriggerExecBody { exec_type: TriggerExecBodyType::Function, - func_desc: TriggerFunctionDesc { + func_desc: FunctionDesc { name: ObjectName(vec![Ident::new("check_transfer_balances_to_zero")]), - args: vec![] + args: None } } } ); - let sql = &format!("CREATE TRIGGER instead_of_paired_items_update INSTEAD OF UPDATE ON paired_items REFERENCING NEW TABLE AS newtab OLD TABLE AS oldtab {for_each} ROW EXECUTE FUNCTION check_matching_pairs()"); + let sql = &format!("CREATE TRIGGER instead_of_paired_items_update INSTEAD OF UPDATE ON paired_items REFERENCING NEW TABLE AS newtab OLD TABLE AS oldtab {for_each} ROW EXECUTE FUNCTION check_matching_pairs"); assert_eq!( pg().verified_stmt(sql), @@ -4613,15 +4613,15 @@ fn parse_create_trigger() { condition: None, exec_body: TriggerExecBody { exec_type: TriggerExecBodyType::Function, - func_desc: TriggerFunctionDesc { + func_desc: FunctionDesc { name: ObjectName(vec![Ident::new("check_matching_pairs")]), - args: vec![] + args: None } } } ); - let sql = &format!("CREATE TRIGGER paired_items_update AFTER UPDATE ON paired_items REFERENCING NEW TABLE AS newtab OLD TABLE AS oldtab {for_each} ROW EXECUTE FUNCTION check_matching_pairs()"); + let sql = &format!("CREATE TRIGGER paired_items_update AFTER UPDATE ON paired_items REFERENCING NEW TABLE AS newtab OLD TABLE AS oldtab {for_each} ROW EXECUTE FUNCTION check_matching_pairs"); assert_eq!( pg().verified_stmt(sql), Statement::CreateTrigger { @@ -4647,9 +4647,9 @@ fn parse_create_trigger() { condition: None, exec_body: TriggerExecBody { exec_type: TriggerExecBodyType::Function, - func_desc: TriggerFunctionDesc { + func_desc: FunctionDesc { name: ObjectName(vec![Ident::new("check_matching_pairs")]), - args: vec![] + args: None } } } From dbaca3a0babd7bf3b6dfcb943711308ca39634dd Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 27 Jul 2024 08:32:56 +0200 Subject: [PATCH 16/51] Updated struct documentation --- src/ast/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 885e3ae8c..a0e8fa4b5 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -6061,7 +6061,7 @@ impl fmt::Display for DropFunctionOption { } } -/// Function describe in DROP FUNCTION. +/// Generic function description for DROP FUNCTION and CREATE TRIGGER. #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] From 3b26bc0ed945ec809cb676917870c4176e0c6601 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 27 Jul 2024 08:46:30 +0200 Subject: [PATCH 17/51] Refactored code to make use of expect_keyboard where appropriate --- src/parser/mod.rs | 116 ++++++++++++++++++++++------------------------ 1 file changed, 55 insertions(+), 61 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index a9cf6ae61..e63b43497 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4166,11 +4166,9 @@ impl<'a> Parser<'a> { let period = self.parse_trigger_period()?; let event = self.parse_keyword_separated(Keyword::OR, Parser::parse_trigger_event)?; - let table_name = if self.parse_keyword(Keyword::ON) { - self.parse_object_name(false)? - } else { - return self.expected("keyword `ON`", self.peek_token()); - }; + let table_name = self + .expect_keyword(Keyword::ON) + .and_then(|_| self.parse_object_name(false))?; let mut referencing = vec![]; if self.parse_keyword(Keyword::REFERENCING) { @@ -4182,30 +4180,27 @@ impl<'a> Parser<'a> { let (for_each, include_each) = if self.parse_keywords(&[Keyword::FOR]) { let include_each = self.parse_keyword(Keyword::EACH); ( - match self.parse_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT]) { - Some(Keyword::ROW) => Some(TriggerObject::Row), - Some(Keyword::STATEMENT) => Some(TriggerObject::Statement), - _ => { - return self.expected("an `ROW` OR `STATEMENT`", self.peek_token()); - } - }, + Some( + match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? { + Keyword::ROW => TriggerObject::Row, + Keyword::STATEMENT => TriggerObject::Statement, + _ => unreachable!(), + }, + ), include_each, ) } else { (None, false) }; - let condition = if self.parse_keyword(Keyword::WHEN) { - Some(self.parse_expr()?) - } else { - None - }; + let condition = self + .parse_keyword(Keyword::WHEN) + .then_some(self.parse_expr()) + .transpose()?; - let exec_body = if self.parse_keyword(Keyword::EXECUTE) { - self.parse_trigger_exec_body()? - } else { - return self.expected("an `EXECUTE`", self.peek_token()); - }; + let exec_body = self + .expect_keyword(Keyword::EXECUTE) + .and_then(|_| self.parse_trigger_exec_body())?; Ok(Statement::CreateTrigger { or_replace, @@ -4226,44 +4221,44 @@ impl<'a> Parser<'a> { } pub fn parse_trigger_period(&mut self) -> Result { - match self.parse_one_of_keywords(&[Keyword::BEFORE, Keyword::AFTER, Keyword::INSTEAD]) { - Some(Keyword::BEFORE) => Ok(TriggerPeriod::Before), - Some(Keyword::AFTER) => Ok(TriggerPeriod::After), - Some(Keyword::INSTEAD) => { - if self.parse_keyword(Keyword::OF) { - Ok(TriggerPeriod::InsteadOf) - } else { - self.expected("an `OF` after `INSTEAD`", self.peek_token()) - } - } - _ => self.expected("an `BEFORE`, `AFTER` OR `INSTEAD OF`", self.peek_token()), - } + self.expect_one_of_keywords(&[Keyword::BEFORE, Keyword::AFTER, Keyword::INSTEAD]) + .and_then(|keyword| { + Ok(match keyword { + Keyword::BEFORE => TriggerPeriod::Before, + Keyword::AFTER => TriggerPeriod::After, + Keyword::INSTEAD => self + .expect_keyword(Keyword::OF) + .map(|_| TriggerPeriod::InsteadOf)?, + _ => unreachable!(), + }) + }) } pub fn parse_trigger_event(&mut self) -> Result { - match self.parse_one_of_keywords(&[ + self.expect_one_of_keywords(&[ Keyword::INSERT, Keyword::UPDATE, Keyword::DELETE, Keyword::TRUNCATE, - ]) { - Some(Keyword::INSERT) => Ok(TriggerEvent::Insert), - Some(Keyword::UPDATE) => { - if self.parse_keyword(Keyword::OF) { - let cols = - self.parse_comma_separated(|ident| Parser::parse_identifier(ident, false))?; - Ok(TriggerEvent::Update(cols)) - } else { - Ok(TriggerEvent::Update(vec![])) + ]) + .and_then(|keyword| { + Ok(match keyword { + Keyword::INSERT => TriggerEvent::Insert, + Keyword::UPDATE => { + if self.parse_keyword(Keyword::OF) { + let cols = self.parse_comma_separated(|ident| { + Parser::parse_identifier(ident, false) + })?; + TriggerEvent::Update(cols) + } else { + TriggerEvent::Update(vec![]) + } } - } - Some(Keyword::DELETE) => Ok(TriggerEvent::Delete), - Some(Keyword::TRUNCATE) => Ok(TriggerEvent::Truncate), - _ => self.expected( - "an `INSERT`, `UPDATE`, `DELETE` OR `TRUNCATE`", - self.peek_token(), - ), - } + Keyword::DELETE => TriggerEvent::Delete, + Keyword::TRUNCATE => TriggerEvent::Truncate, + _ => unreachable!(), + }) + }) } pub fn parse_trigger_referencing(&mut self) -> Result, ParserError> { @@ -4289,18 +4284,17 @@ impl<'a> Parser<'a> { } pub fn parse_trigger_exec_body(&mut self) -> Result { - let exec_type = match self.parse_one_of_keywords(&[Keyword::FUNCTION, Keyword::PROCEDURE]) { - Some(Keyword::FUNCTION) => TriggerExecBodyType::Function, - Some(Keyword::PROCEDURE) => TriggerExecBodyType::Procedure, - _ => { - return self.expected("an `FUNCTION` OR `PROCEDURE`", self.peek_token()); - } - }; + let exec_type = self + .expect_one_of_keywords(&[Keyword::FUNCTION, Keyword::PROCEDURE]) + .map(|keyword| match keyword { + Keyword::FUNCTION => TriggerExecBodyType::Function, + Keyword::PROCEDURE => TriggerExecBodyType::Procedure, + _ => unreachable!(), + })?; - let func_desc = self.parse_function_desc()?; Ok(TriggerExecBody { exec_type, - func_desc, + func_desc: self.parse_function_desc()?, }) } From 552825cec413138e0befd909299a853ba64b4639 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 27 Jul 2024 09:13:32 +0200 Subject: [PATCH 18/51] Extended test suite for multiple events --- src/ast/mod.rs | 2 +- src/ast/trigger.rs | 2 +- src/parser/mod.rs | 4 +- tests/sqlparser_postgres.rs | 332 ++++++++++++++---------------------- 4 files changed, 132 insertions(+), 208 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index a0e8fa4b5..e04118f3c 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -3438,7 +3438,7 @@ impl fmt::Display for Statement { or_replace = if *or_replace { "OR REPLACE " } else { "" }, )?; if !event.is_empty() { - write!(f, " {}", display_separated(event, "OR"))?; + write!(f, " {}", display_separated(event, " OR "))?; } write!(f, " ON {table_name}")?; if !referencing.is_empty() { diff --git a/src/ast/trigger.rs b/src/ast/trigger.rs index de4664e17..940ae5f01 100644 --- a/src/ast/trigger.rs +++ b/src/ast/trigger.rs @@ -101,7 +101,7 @@ impl fmt::Display for TriggerEvent { } /// Trigger period -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum TriggerPeriod { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index e63b43497..b4fe6629d 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4177,7 +4177,7 @@ impl<'a> Parser<'a> { } } - let (for_each, include_each) = if self.parse_keywords(&[Keyword::FOR]) { + let (for_each, include_each) = if self.parse_keyword(Keyword::FOR) { let include_each = self.parse_keyword(Keyword::EACH); ( Some( @@ -4195,7 +4195,7 @@ impl<'a> Parser<'a> { let condition = self .parse_keyword(Keyword::WHEN) - .then_some(self.parse_expr()) + .then(|| self.parse_expr()) .transpose()?; let exec_body = self diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index bd99e5c7c..6755ce0f2 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4444,215 +4444,139 @@ fn test_table_unnest_with_ordinality() { #[test] fn parse_create_trigger() { - for include_each in [true, false] { - let for_each = if include_each { "FOR EACH" } else { "FOR" }; - - let sql = &format!( - "CREATE TRIGGER check_update BEFORE UPDATE ON accounts {for_each} ROW EXECUTE FUNCTION check_account_update" - ); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: TriggerPeriod::Before, - event: vec![TriggerEvent::Update(vec![])], - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - include_each, - condition: None, - exec_body: TriggerExecBody { - exec_type: TriggerExecBodyType::Function, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: None - } - } - } - ); - - let sql = &format!("CREATE OR REPLACE TRIGGER check_update BEFORE UPDATE OF balance ON accounts {for_each} ROW EXECUTE FUNCTION check_account_update"); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: true, - name: ObjectName(vec![Ident::new("check_update")]), - period: TriggerPeriod::Before, - event: vec![TriggerEvent::Update(vec![Ident::new("balance")])], - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - include_each, - condition: None, - exec_body: TriggerExecBody { - exec_type: TriggerExecBodyType::Function, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: None - } - } - } - ); - - let sql = &format!("CREATE TRIGGER check_update BEFORE UPDATE ON accounts {for_each} ROW WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE FUNCTION check_account_update"); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: TriggerPeriod::Before, - event: vec![TriggerEvent::Update(vec![])], - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - include_each, - condition: Some(Expr::Nested(Box::new(Expr::IsDistinctFrom( - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("OLD"), - Ident::new("balance") - ])), - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("NEW"), - Ident::new("balance") - ])), - )))), - exec_body: TriggerExecBody { - exec_type: TriggerExecBodyType::Function, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: None - } - } - } - ); - - let sql = &format!("CREATE TRIGGER check_update BEFORE UPDATE ON accounts {for_each} ROW WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE FUNCTION check_account_update"); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: TriggerPeriod::Before, - event: vec![TriggerEvent::Update(vec![])], - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - include_each, - condition: Some(Expr::Nested(Box::new(Expr::IsNotDistinctFrom( - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("OLD"), - Ident::new("balance") - ])), - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("NEW"), - Ident::new("balance") - ])), - )))), - exec_body: TriggerExecBody { - exec_type: TriggerExecBodyType::Function, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: None - } - } - } - ); - - let sql = &format!("CREATE TRIGGER transfer_insert AFTER INSERT ON transfer REFERENCING NEW TABLE AS inserted {for_each} STATEMENT EXECUTE FUNCTION check_transfer_balances_to_zero"); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("transfer_insert")]), - period: TriggerPeriod::After, - event: vec![TriggerEvent::Insert], - table_name: ObjectName(vec![Ident::new("transfer")]), - referencing: vec![TriggerReferencing { - refer_type: TriggerReferencingType::NewTable, - is_as: true, - transition_relation_name: ObjectName(vec![Ident::new("inserted")]) - }], - for_each: Some(TriggerObject::Statement), - include_each, - condition: None, - exec_body: TriggerExecBody { - exec_type: TriggerExecBodyType::Function, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_transfer_balances_to_zero")]), - args: None + for (event, event_string) in [ + (vec![TriggerEvent::Update(vec![])], "UPDATE"), + (vec![TriggerEvent::Insert], "INSERT"), + (vec![TriggerEvent::Delete], "DELETE"), + ( + vec![TriggerEvent::Update(vec![]), TriggerEvent::Insert], + "UPDATE OR INSERT", + ), + ( + vec![ + TriggerEvent::Update(vec![]), + TriggerEvent::Insert, + TriggerEvent::Delete, + ], + "UPDATE OR INSERT OR DELETE", + ), + ( + vec![TriggerEvent::Update(vec![Ident::new("balance")])], + "UPDATE OF balance", + ), + ( + vec![ + TriggerEvent::Update(vec![Ident::new("balance")]), + TriggerEvent::Insert, + ], + "UPDATE OF balance OR INSERT", + ), + ( + vec![ + TriggerEvent::Update(vec![Ident::new("balance")]), + TriggerEvent::Insert, + TriggerEvent::Delete, + ], + "UPDATE OF balance OR INSERT OR DELETE", + ), + ] { + for when in [ + TriggerPeriod::Before, + TriggerPeriod::After, + TriggerPeriod::InsteadOf, + ] { + for include_each in [true, false] { + let for_each = if include_each { "FOR EACH" } else { "FOR" }; + + let sql = &format!( + "CREATE TRIGGER check_update {when} {event_string} ON accounts {for_each} ROW EXECUTE FUNCTION check_account_update" + ); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: when, + event: event.clone(), + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + include_each, + condition: None, + exec_body: TriggerExecBody { + exec_type: TriggerExecBodyType::Function, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: None + } + } } - } - } - ); - - let sql = &format!("CREATE TRIGGER instead_of_paired_items_update INSTEAD OF UPDATE ON paired_items REFERENCING NEW TABLE AS newtab OLD TABLE AS oldtab {for_each} ROW EXECUTE FUNCTION check_matching_pairs"); + ); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("instead_of_paired_items_update")]), - period: TriggerPeriod::InsteadOf, - event: vec![TriggerEvent::Update(vec![])], - table_name: ObjectName(vec![Ident::new("paired_items")]), - referencing: vec![ - TriggerReferencing { - refer_type: TriggerReferencingType::NewTable, - is_as: true, - transition_relation_name: ObjectName(vec![Ident::new("newtab")]) - }, - TriggerReferencing { - refer_type: TriggerReferencingType::OldTable, - is_as: true, - transition_relation_name: ObjectName(vec![Ident::new("oldtab")]) - } - ], - for_each: Some(TriggerObject::Row), - include_each, - condition: None, - exec_body: TriggerExecBody { - exec_type: TriggerExecBodyType::Function, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_matching_pairs")]), - args: None + let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {for_each} ROW WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE FUNCTION check_account_update"); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: when, + event: event.clone(), + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + include_each, + condition: Some(Expr::Nested(Box::new(Expr::IsDistinctFrom( + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("OLD"), + Ident::new("balance") + ])), + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("NEW"), + Ident::new("balance") + ])), + )))), + exec_body: TriggerExecBody { + exec_type: TriggerExecBodyType::Function, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: None + } + } } - } - } - ); + ); - let sql = &format!("CREATE TRIGGER paired_items_update AFTER UPDATE ON paired_items REFERENCING NEW TABLE AS newtab OLD TABLE AS oldtab {for_each} ROW EXECUTE FUNCTION check_matching_pairs"); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("paired_items_update")]), - period: TriggerPeriod::After, - event: vec![TriggerEvent::Update(vec![])], - table_name: ObjectName(vec![Ident::new("paired_items")]), - referencing: vec![ - TriggerReferencing { - refer_type: TriggerReferencingType::NewTable, - is_as: true, - transition_relation_name: ObjectName(vec![Ident::new("newtab")]) - }, - TriggerReferencing { - refer_type: TriggerReferencingType::OldTable, - is_as: true, - transition_relation_name: ObjectName(vec![Ident::new("oldtab")]) - } - ], - for_each: Some(TriggerObject::Row), - include_each, - condition: None, - exec_body: TriggerExecBody { - exec_type: TriggerExecBodyType::Function, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_matching_pairs")]), - args: None + let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {for_each} ROW WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE FUNCTION check_account_update"); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: when, + event: event.clone(), + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + include_each, + condition: Some(Expr::Nested(Box::new(Expr::IsNotDistinctFrom( + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("OLD"), + Ident::new("balance") + ])), + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("NEW"), + Ident::new("balance") + ])), + )))), + exec_body: TriggerExecBody { + exec_type: TriggerExecBodyType::Function, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: None + } + } } - } + ); } - ); + } } } From 40993e0f3e4524e8f91ec5d9a2f2028f9eb0750b Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 27 Jul 2024 09:18:38 +0200 Subject: [PATCH 19/51] Added coverage for both function and procedure in triggers --- src/ast/trigger.rs | 2 +- tests/sqlparser_postgres.rs | 247 ++++++++++++++++++------------------ 2 files changed, 127 insertions(+), 122 deletions(-) diff --git a/src/ast/trigger.rs b/src/ast/trigger.rs index 940ae5f01..ba47c3c3b 100644 --- a/src/ast/trigger.rs +++ b/src/ast/trigger.rs @@ -121,7 +121,7 @@ impl fmt::Display for TriggerPeriod { } /// Types of trigger body execution body. -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum TriggerExecBodyType { diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 6755ce0f2..ce0c715b8 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4444,138 +4444,143 @@ fn test_table_unnest_with_ordinality() { #[test] fn parse_create_trigger() { - for (event, event_string) in [ - (vec![TriggerEvent::Update(vec![])], "UPDATE"), - (vec![TriggerEvent::Insert], "INSERT"), - (vec![TriggerEvent::Delete], "DELETE"), - ( - vec![TriggerEvent::Update(vec![]), TriggerEvent::Insert], - "UPDATE OR INSERT", - ), - ( - vec![ - TriggerEvent::Update(vec![]), - TriggerEvent::Insert, - TriggerEvent::Delete, - ], - "UPDATE OR INSERT OR DELETE", - ), - ( - vec![TriggerEvent::Update(vec![Ident::new("balance")])], - "UPDATE OF balance", - ), - ( - vec![ - TriggerEvent::Update(vec![Ident::new("balance")]), - TriggerEvent::Insert, - ], - "UPDATE OF balance OR INSERT", - ), - ( - vec![ - TriggerEvent::Update(vec![Ident::new("balance")]), - TriggerEvent::Insert, - TriggerEvent::Delete, - ], - "UPDATE OF balance OR INSERT OR DELETE", - ), + for exec_type in [ + TriggerExecBodyType::Function, + TriggerExecBodyType::Procedure, ] { - for when in [ - TriggerPeriod::Before, - TriggerPeriod::After, - TriggerPeriod::InsteadOf, + for (event, event_string) in [ + (vec![TriggerEvent::Update(vec![])], "UPDATE"), + (vec![TriggerEvent::Insert], "INSERT"), + (vec![TriggerEvent::Delete], "DELETE"), + ( + vec![TriggerEvent::Update(vec![]), TriggerEvent::Insert], + "UPDATE OR INSERT", + ), + ( + vec![ + TriggerEvent::Update(vec![]), + TriggerEvent::Insert, + TriggerEvent::Delete, + ], + "UPDATE OR INSERT OR DELETE", + ), + ( + vec![TriggerEvent::Update(vec![Ident::new("balance")])], + "UPDATE OF balance", + ), + ( + vec![ + TriggerEvent::Update(vec![Ident::new("balance")]), + TriggerEvent::Insert, + ], + "UPDATE OF balance OR INSERT", + ), + ( + vec![ + TriggerEvent::Update(vec![Ident::new("balance")]), + TriggerEvent::Insert, + TriggerEvent::Delete, + ], + "UPDATE OF balance OR INSERT OR DELETE", + ), ] { - for include_each in [true, false] { - let for_each = if include_each { "FOR EACH" } else { "FOR" }; - - let sql = &format!( - "CREATE TRIGGER check_update {when} {event_string} ON accounts {for_each} ROW EXECUTE FUNCTION check_account_update" + for when in [ + TriggerPeriod::Before, + TriggerPeriod::After, + TriggerPeriod::InsteadOf, + ] { + for include_each in [true, false] { + let for_each = if include_each { "FOR EACH" } else { "FOR" }; + + let sql = &format!( + "CREATE TRIGGER check_update {when} {event_string} ON accounts {for_each} ROW EXECUTE {exec_type} check_account_update" ); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: when, - event: event.clone(), - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - include_each, - condition: None, - exec_body: TriggerExecBody { - exec_type: TriggerExecBodyType::Function, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: None + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: when, + event: event.clone(), + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + include_each, + condition: None, + exec_body: TriggerExecBody { + exec_type, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: None + } } } - } - ); + ); - let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {for_each} ROW WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE FUNCTION check_account_update"); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: when, - event: event.clone(), - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - include_each, - condition: Some(Expr::Nested(Box::new(Expr::IsDistinctFrom( - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("OLD"), - Ident::new("balance") - ])), - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("NEW"), - Ident::new("balance") - ])), - )))), - exec_body: TriggerExecBody { - exec_type: TriggerExecBodyType::Function, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: None + let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {for_each} ROW WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE {exec_type} check_account_update"); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: when, + event: event.clone(), + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + include_each, + condition: Some(Expr::Nested(Box::new(Expr::IsDistinctFrom( + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("OLD"), + Ident::new("balance") + ])), + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("NEW"), + Ident::new("balance") + ])), + )))), + exec_body: TriggerExecBody { + exec_type, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: None + } } } - } - ); + ); - let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {for_each} ROW WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE FUNCTION check_account_update"); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: when, - event: event.clone(), - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - include_each, - condition: Some(Expr::Nested(Box::new(Expr::IsNotDistinctFrom( - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("OLD"), - Ident::new("balance") - ])), - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("NEW"), - Ident::new("balance") - ])), - )))), - exec_body: TriggerExecBody { - exec_type: TriggerExecBodyType::Function, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: None + let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {for_each} ROW WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE {exec_type} check_account_update"); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: when, + event: event.clone(), + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + include_each, + condition: Some(Expr::Nested(Box::new(Expr::IsNotDistinctFrom( + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("OLD"), + Ident::new("balance") + ])), + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("NEW"), + Ident::new("balance") + ])), + )))), + exec_body: TriggerExecBody { + exec_type, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: None + } } } - } - ); + ); + } } } } From e26ef31014c5aae55e808c682a60d92c04182a81 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 27 Jul 2024 09:24:32 +0200 Subject: [PATCH 20/51] Extended trigger testing which now includes also functions with multiple arguments --- tests/sqlparser_postgres.rs | 267 +++++++++++++++++++----------------- 1 file changed, 141 insertions(+), 126 deletions(-) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index ce0c715b8..6355655bd 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4444,142 +4444,157 @@ fn test_table_unnest_with_ordinality() { #[test] fn parse_create_trigger() { - for exec_type in [ - TriggerExecBodyType::Function, - TriggerExecBodyType::Procedure, + for func_desc in [ + FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: None, + }, + FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: Some(vec![OperateFunctionArg::unnamed(DataType::Int(None))]), + }, + FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: Some(vec![ + OperateFunctionArg::with_name("a", DataType::Int(None)), + OperateFunctionArg { + mode: Some(ArgMode::In), + name: Some("b".into()), + data_type: DataType::Int(None), + default_expr: Some(Expr::Value(Value::Number("1".parse().unwrap(), false))), + }, + ]), + }, ] { - for (event, event_string) in [ - (vec![TriggerEvent::Update(vec![])], "UPDATE"), - (vec![TriggerEvent::Insert], "INSERT"), - (vec![TriggerEvent::Delete], "DELETE"), - ( - vec![TriggerEvent::Update(vec![]), TriggerEvent::Insert], - "UPDATE OR INSERT", - ), - ( - vec![ - TriggerEvent::Update(vec![]), - TriggerEvent::Insert, - TriggerEvent::Delete, - ], - "UPDATE OR INSERT OR DELETE", - ), - ( - vec![TriggerEvent::Update(vec![Ident::new("balance")])], - "UPDATE OF balance", - ), - ( - vec![ - TriggerEvent::Update(vec![Ident::new("balance")]), - TriggerEvent::Insert, - ], - "UPDATE OF balance OR INSERT", - ), - ( - vec![ - TriggerEvent::Update(vec![Ident::new("balance")]), - TriggerEvent::Insert, - TriggerEvent::Delete, - ], - "UPDATE OF balance OR INSERT OR DELETE", - ), + for exec_type in [ + TriggerExecBodyType::Function, + TriggerExecBodyType::Procedure, ] { - for when in [ - TriggerPeriod::Before, - TriggerPeriod::After, - TriggerPeriod::InsteadOf, + for (event, event_string) in [ + (vec![TriggerEvent::Update(vec![])], "UPDATE"), + (vec![TriggerEvent::Insert], "INSERT"), + (vec![TriggerEvent::Delete], "DELETE"), + ( + vec![TriggerEvent::Update(vec![]), TriggerEvent::Insert], + "UPDATE OR INSERT", + ), + ( + vec![ + TriggerEvent::Update(vec![]), + TriggerEvent::Insert, + TriggerEvent::Delete, + ], + "UPDATE OR INSERT OR DELETE", + ), + ( + vec![TriggerEvent::Update(vec![Ident::new("balance")])], + "UPDATE OF balance", + ), + ( + vec![ + TriggerEvent::Update(vec![Ident::new("balance")]), + TriggerEvent::Insert, + ], + "UPDATE OF balance OR INSERT", + ), + ( + vec![ + TriggerEvent::Update(vec![Ident::new("balance")]), + TriggerEvent::Insert, + TriggerEvent::Delete, + ], + "UPDATE OF balance OR INSERT OR DELETE", + ), ] { - for include_each in [true, false] { - let for_each = if include_each { "FOR EACH" } else { "FOR" }; - - let sql = &format!( - "CREATE TRIGGER check_update {when} {event_string} ON accounts {for_each} ROW EXECUTE {exec_type} check_account_update" + for when in [ + TriggerPeriod::Before, + TriggerPeriod::After, + TriggerPeriod::InsteadOf, + ] { + for include_each in [true, false] { + let for_each = if include_each { "FOR EACH" } else { "FOR" }; + + let sql = &format!( + "CREATE TRIGGER check_update {when} {event_string} ON accounts {for_each} ROW EXECUTE {exec_type} {func_desc}" ); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: when, - event: event.clone(), - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - include_each, - condition: None, - exec_body: TriggerExecBody { - exec_type, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: None + + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: when, + event: event.clone(), + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + include_each, + condition: None, + exec_body: TriggerExecBody { + exec_type, + func_desc: func_desc.clone() } } - } - ); - - let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {for_each} ROW WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE {exec_type} check_account_update"); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: when, - event: event.clone(), - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - include_each, - condition: Some(Expr::Nested(Box::new(Expr::IsDistinctFrom( - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("OLD"), - Ident::new("balance") - ])), - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("NEW"), - Ident::new("balance") - ])), - )))), - exec_body: TriggerExecBody { - exec_type, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: None + ); + + let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {for_each} ROW WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: when, + event: event.clone(), + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + include_each, + condition: Some(Expr::Nested(Box::new(Expr::IsDistinctFrom( + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("OLD"), + Ident::new("balance") + ])), + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("NEW"), + Ident::new("balance") + ])), + )))), + exec_body: TriggerExecBody { + exec_type, + func_desc: func_desc.clone() } } - } - ); - - let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {for_each} ROW WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE {exec_type} check_account_update"); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: when, - event: event.clone(), - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - include_each, - condition: Some(Expr::Nested(Box::new(Expr::IsNotDistinctFrom( - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("OLD"), - Ident::new("balance") - ])), - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("NEW"), - Ident::new("balance") - ])), - )))), - exec_body: TriggerExecBody { - exec_type, - func_desc: FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: None + ); + + let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {for_each} ROW WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: when, + event: event.clone(), + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + include_each, + condition: Some(Expr::Nested(Box::new(Expr::IsNotDistinctFrom( + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("OLD"), + Ident::new("balance") + ])), + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("NEW"), + Ident::new("balance") + ])), + )))), + exec_body: TriggerExecBody { + exec_type, + func_desc: func_desc.clone() } } - } - ); + ); + } } } } From bf9da2ac64fa474bc8cb15dfa9d94286b6e168fb Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 27 Jul 2024 09:25:01 +0200 Subject: [PATCH 21/51] Formatted code --- tests/sqlparser_postgres.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 6355655bd..69a01ca98 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4518,7 +4518,7 @@ fn parse_create_trigger() { "CREATE TRIGGER check_update {when} {event_string} ON accounts {for_each} ROW EXECUTE {exec_type} {func_desc}" ); - assert_eq!( + assert_eq!( pg().verified_stmt(sql), Statement::CreateTrigger { or_replace: false, From c83dd34f20766842a851a894e2b24245e1c2cfd7 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 27 Jul 2024 10:22:25 +0200 Subject: [PATCH 22/51] Added support for deferrability in TRIGGERS and extended test suite --- src/ast/ddl.rs | 83 ++++++---- src/ast/mod.rs | 10 +- src/parser/mod.rs | 51 ++++-- tests/sqlparser_common.rs | 30 ++-- tests/sqlparser_postgres.rs | 322 ++++++++++++++++++++---------------- 5 files changed, 296 insertions(+), 200 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 5cc671cf5..636b53dea 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -1154,16 +1154,50 @@ fn display_option_spaced(option: &Option) -> impl fmt::Displ /// ` = [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] [ ENFORCED | NOT ENFORCED ]` /// /// Used in UNIQUE and foreign key constraints. The individual settings may occur in any order. -#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[derive(Debug, Copy, Clone, Default, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct ConstraintCharacteristics { + /// `[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]` + pub deferrable: DeferrableCharacteristics, + /// `[ ENFORCED | NOT ENFORCED ]` + pub enforced: Option, +} + +/// ` = [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]` +/// +/// Used in TRIGGER, UNIQUE and foreign key constraints. The individual settings may occur in any order. +#[derive(Debug, Copy, Clone, Default, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct DeferrableCharacteristics { /// `[ DEFERRABLE | NOT DEFERRABLE ]` pub deferrable: Option, /// `[ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]` pub initially: Option, - /// `[ ENFORCED | NOT ENFORCED ]` - pub enforced: Option, +} + +impl fmt::Display for DeferrableCharacteristics { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(deferrable) = self.deferrable { + write!( + f, + "{}", + if deferrable { + "DEFERRABLE" + } else { + "NOT DEFERRABLE" + } + )?; + } + if let Some(initially) = self.initially { + if self.deferrable.is_some() { + f.write_str(" ")?; + } + write!(f, "{}", initially)?; + } + Ok(()) + } } #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] @@ -1176,25 +1210,16 @@ pub enum DeferrableInitial { Deferred, } -impl ConstraintCharacteristics { - fn deferrable_text(&self) -> Option<&'static str> { - self.deferrable.map(|deferrable| { - if deferrable { - "DEFERRABLE" - } else { - "NOT DEFERRABLE" - } +impl fmt::Display for DeferrableInitial { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match self { + DeferrableInitial::Immediate => "INITIALLY IMMEDIATE", + DeferrableInitial::Deferred => "INITIALLY DEFERRED", }) } +} - fn initially_immediate_text(&self) -> Option<&'static str> { - self.initially - .map(|initially_immediate| match initially_immediate { - DeferrableInitial::Immediate => "INITIALLY IMMEDIATE", - DeferrableInitial::Deferred => "INITIALLY DEFERRED", - }) - } - +impl ConstraintCharacteristics { fn enforced_text(&self) -> Option<&'static str> { self.enforced.map( |enforced| { @@ -1210,22 +1235,12 @@ impl ConstraintCharacteristics { impl fmt::Display for ConstraintCharacteristics { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let deferrable = self.deferrable_text(); - let initially_immediate = self.initially_immediate_text(); - let enforced = self.enforced_text(); - - match (deferrable, initially_immediate, enforced) { - (None, None, None) => Ok(()), - (None, None, Some(enforced)) => write!(f, "{enforced}"), - (None, Some(initial), None) => write!(f, "{initial}"), - (None, Some(initial), Some(enforced)) => write!(f, "{initial} {enforced}"), - (Some(deferrable), None, None) => write!(f, "{deferrable}"), - (Some(deferrable), None, Some(enforced)) => write!(f, "{deferrable} {enforced}"), - (Some(deferrable), Some(initial), None) => write!(f, "{deferrable} {initial}"), - (Some(deferrable), Some(initial), Some(enforced)) => { - write!(f, "{deferrable} {initial} {enforced}") - } + if self.deferrable.deferrable.is_some() || self.deferrable.initially.is_some() { + write!(f, "{}{}", self.deferrable, display_option_spaced(&self.enforced_text()))?; + } else if let Some(enforced) = self.enforced_text() { + write!(f, "{}", enforced)?; } + Ok(()) } } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index e04118f3c..2bd866325 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -33,7 +33,7 @@ pub use self::data_type::{ pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue}; pub use self::ddl::{ AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption, - ColumnOptionDef, ConstraintCharacteristics, DeferrableInitial, GeneratedAs, + ColumnOptionDef, ConstraintCharacteristics, DeferrableCharacteristics, DeferrableInitial, GeneratedAs, GeneratedExpressionMode, IndexOption, IndexType, KeyOrIndexDisplay, Owner, Partition, ProcedureParam, ReferentialAction, TableConstraint, UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef, @@ -2664,6 +2664,8 @@ pub enum Statement { condition: Option, /// Execute logic block exec_body: TriggerExecBody, + /// The characteristic of the trigger, which include whether the trigger is `DEFERRABLE`, `INITIALLY DEFERRED`, or `INITIALLY IMMEDIATE`, + characteristics: Option, }, /// ```sql /// CREATE PROCEDURE @@ -3431,6 +3433,7 @@ impl fmt::Display for Statement { condition, include_each, exec_body, + characteristics, } => { write!( f, @@ -3444,6 +3447,11 @@ impl fmt::Display for Statement { if !referencing.is_empty() { write!(f, " REFERENCING {}", display_separated(referencing, " "))?; } + + if let Some(characteristics) = characteristics { + write!(f, " {characteristics}")?; + } + if let Some(trigger_object) = for_each { if *include_each { write!(f, " FOR EACH {trigger_object}")?; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index b4fe6629d..ec4ca4bbf 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4170,6 +4170,29 @@ impl<'a> Parser<'a> { .expect_keyword(Keyword::ON) .and_then(|_| self.parse_object_name(false))?; + // [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ] + let mut deferrable: Option = None; + + if self.parse_keyword(Keyword::NOT) { + self.expect_keyword(Keyword::DEFERRABLE)?; + deferrable = Some(false); + } else if self.parse_keyword(Keyword::DEFERRABLE) { + deferrable = Some(true); + }; + + let initially: Option = (deferrable.is_some() + && self.parse_keyword(Keyword::INITIALLY)) + .then(|| { + Ok::<_, ParserError>( + match self.expect_one_of_keywords(&[Keyword::IMMEDIATE, Keyword::DEFERRED])? { + Keyword::IMMEDIATE => DeferrableInitial::Immediate, + Keyword::DEFERRED => DeferrableInitial::Deferred, + _ => unreachable!(), + }, + ) + }) + .transpose()?; + let mut referencing = vec![]; if self.parse_keyword(Keyword::REFERENCING) { while let Some(refer) = self.parse_trigger_referencing()? { @@ -4213,6 +4236,12 @@ impl<'a> Parser<'a> { include_each, condition, exec_body, + characteristics: (deferrable.is_some() || initially.is_some()).then(|| { + DeferrableCharacteristics { + deferrable, + initially, + } + }), }) } else { self.prev_token(); @@ -6120,23 +6149,19 @@ impl<'a> Parser<'a> { pub fn parse_constraint_characteristics( &mut self, ) -> Result, ParserError> { - let mut cc = ConstraintCharacteristics { - deferrable: None, - initially: None, - enforced: None, - }; + let mut cc = ConstraintCharacteristics::default(); loop { - if cc.deferrable.is_none() && self.parse_keywords(&[Keyword::NOT, Keyword::DEFERRABLE]) + if cc.deferrable.deferrable.is_none() && self.parse_keywords(&[Keyword::NOT, Keyword::DEFERRABLE]) { - cc.deferrable = Some(false); - } else if cc.deferrable.is_none() && self.parse_keyword(Keyword::DEFERRABLE) { - cc.deferrable = Some(true); - } else if cc.initially.is_none() && self.parse_keyword(Keyword::INITIALLY) { + cc.deferrable.deferrable = Some(false); + } else if cc.deferrable.deferrable.is_none() && self.parse_keyword(Keyword::DEFERRABLE) { + cc.deferrable.deferrable = Some(true); + } else if cc.deferrable.initially.is_none() && self.parse_keyword(Keyword::INITIALLY) { if self.parse_keyword(Keyword::DEFERRED) { - cc.initially = Some(DeferrableInitial::Deferred); + cc.deferrable.initially = Some(DeferrableInitial::Deferred); } else if self.parse_keyword(Keyword::IMMEDIATE) { - cc.initially = Some(DeferrableInitial::Immediate); + cc.deferrable.initially = Some(DeferrableInitial::Immediate); } else { self.expected("one of DEFERRED or IMMEDIATE", self.peek_token())?; } @@ -6151,7 +6176,7 @@ impl<'a> Parser<'a> { } } - if cc.deferrable.is_some() || cc.initially.is_some() || cc.enforced.is_some() { + if cc.deferrable.deferrable.is_some() || cc.deferrable.initially.is_some() || cc.enforced.is_some() { Ok(Some(cc)) } else { Ok(None) diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index dd3ed0515..71270ecfc 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -3031,8 +3031,10 @@ fn parse_create_table_with_constraint_characteristics() { on_delete: Some(ReferentialAction::Restrict), on_update: None, characteristics: Some(ConstraintCharacteristics { - deferrable: Some(true), - initially: Some(DeferrableInitial::Deferred), + deferrable: DeferrableCharacteristics { + deferrable: Some(true), + initially: Some(DeferrableInitial::Deferred) + }, enforced: None }), }, @@ -3044,8 +3046,10 @@ fn parse_create_table_with_constraint_characteristics() { on_delete: Some(ReferentialAction::NoAction), on_update: Some(ReferentialAction::Restrict), characteristics: Some(ConstraintCharacteristics { - deferrable: Some(true), - initially: Some(DeferrableInitial::Immediate), + deferrable: DeferrableCharacteristics { + deferrable: Some(true), + initially: Some(DeferrableInitial::Immediate) + }, enforced: None, }), }, @@ -3057,8 +3061,10 @@ fn parse_create_table_with_constraint_characteristics() { on_delete: Some(ReferentialAction::Cascade), on_update: Some(ReferentialAction::SetDefault), characteristics: Some(ConstraintCharacteristics { - deferrable: Some(false), - initially: Some(DeferrableInitial::Deferred), + deferrable: DeferrableCharacteristics { + deferrable: Some(false), + initially: Some(DeferrableInitial::Deferred) + }, enforced: Some(false), }), }, @@ -3070,8 +3076,10 @@ fn parse_create_table_with_constraint_characteristics() { on_delete: None, on_update: Some(ReferentialAction::SetNull), characteristics: Some(ConstraintCharacteristics { - deferrable: Some(false), - initially: Some(DeferrableInitial::Immediate), + deferrable: DeferrableCharacteristics { + deferrable: Some(false), + initially: Some(DeferrableInitial::Immediate) + }, enforced: Some(true), }), }, @@ -3135,8 +3143,10 @@ fn parse_create_table_column_constraint_characteristics() { let expected_value = if deferrable.is_some() || initially.is_some() || enforced.is_some() { Some(ConstraintCharacteristics { - deferrable, - initially, + deferrable: DeferrableCharacteristics { + deferrable, + initially, + }, enforced, }) } else { diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 69a01ca98..96f2f8edc 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4444,156 +4444,194 @@ fn test_table_unnest_with_ordinality() { #[test] fn parse_create_trigger() { - for func_desc in [ - FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: None, - }, - FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: Some(vec![OperateFunctionArg::unnamed(DataType::Int(None))]), - }, - FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: Some(vec![ - OperateFunctionArg::with_name("a", DataType::Int(None)), - OperateFunctionArg { - mode: Some(ArgMode::In), - name: Some("b".into()), - data_type: DataType::Int(None), - default_expr: Some(Expr::Value(Value::Number("1".parse().unwrap(), false))), - }, - ]), - }, + for (characteristics, characteristics_text) in [ + ( + None, + "", + ), + ( + Some(DeferrableCharacteristics { + deferrable: Some(true), + initially: Some(DeferrableInitial::Deferred), + }), + "DEFERRABLE INITIALLY DEFERRED ", + ), + ( + Some(DeferrableCharacteristics { + deferrable: Some(false), + initially: Some(DeferrableInitial::Immediate), + }), + "NOT DEFERRABLE INITIALLY IMMEDIATE ", + ), + ( + Some(DeferrableCharacteristics { + deferrable: Some(true), + initially: Some(DeferrableInitial::Immediate), + }), + "DEFERRABLE INITIALLY IMMEDIATE ", + ), + ( + Some(DeferrableCharacteristics { + deferrable: Some(true), + initially: Some(DeferrableInitial::Deferred), + }), + "DEFERRABLE INITIALLY DEFERRED ", + ) ] { - for exec_type in [ - TriggerExecBodyType::Function, - TriggerExecBodyType::Procedure, + for func_desc in [ + FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: None, + }, + FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: Some(vec![OperateFunctionArg::unnamed(DataType::Int(None))]), + }, + FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: Some(vec![ + OperateFunctionArg::with_name("a", DataType::Int(None)), + OperateFunctionArg { + mode: Some(ArgMode::In), + name: Some("b".into()), + data_type: DataType::Int(None), + default_expr: Some(Expr::Value(Value::Number("1".parse().unwrap(), false))), + }, + ]), + }, ] { - for (event, event_string) in [ - (vec![TriggerEvent::Update(vec![])], "UPDATE"), - (vec![TriggerEvent::Insert], "INSERT"), - (vec![TriggerEvent::Delete], "DELETE"), - ( - vec![TriggerEvent::Update(vec![]), TriggerEvent::Insert], - "UPDATE OR INSERT", - ), - ( - vec![ - TriggerEvent::Update(vec![]), - TriggerEvent::Insert, - TriggerEvent::Delete, - ], - "UPDATE OR INSERT OR DELETE", - ), - ( - vec![TriggerEvent::Update(vec![Ident::new("balance")])], - "UPDATE OF balance", - ), - ( - vec![ - TriggerEvent::Update(vec![Ident::new("balance")]), - TriggerEvent::Insert, - ], - "UPDATE OF balance OR INSERT", - ), - ( - vec![ - TriggerEvent::Update(vec![Ident::new("balance")]), - TriggerEvent::Insert, - TriggerEvent::Delete, - ], - "UPDATE OF balance OR INSERT OR DELETE", - ), + for exec_type in [ + TriggerExecBodyType::Function, + TriggerExecBodyType::Procedure, ] { - for when in [ - TriggerPeriod::Before, - TriggerPeriod::After, - TriggerPeriod::InsteadOf, + for (event, event_string) in [ + (vec![TriggerEvent::Update(vec![])], "UPDATE"), + (vec![TriggerEvent::Insert], "INSERT"), + (vec![TriggerEvent::Delete], "DELETE"), + ( + vec![TriggerEvent::Update(vec![]), TriggerEvent::Insert], + "UPDATE OR INSERT", + ), + ( + vec![ + TriggerEvent::Update(vec![]), + TriggerEvent::Insert, + TriggerEvent::Delete, + ], + "UPDATE OR INSERT OR DELETE", + ), + ( + vec![TriggerEvent::Update(vec![Ident::new("balance")])], + "UPDATE OF balance", + ), + ( + vec![ + TriggerEvent::Update(vec![Ident::new("balance")]), + TriggerEvent::Insert, + ], + "UPDATE OF balance OR INSERT", + ), + ( + vec![ + TriggerEvent::Update(vec![Ident::new("balance")]), + TriggerEvent::Insert, + TriggerEvent::Delete, + ], + "UPDATE OF balance OR INSERT OR DELETE", + ), ] { - for include_each in [true, false] { - let for_each = if include_each { "FOR EACH" } else { "FOR" }; - - let sql = &format!( - "CREATE TRIGGER check_update {when} {event_string} ON accounts {for_each} ROW EXECUTE {exec_type} {func_desc}" - ); + for when in [ + TriggerPeriod::Before, + TriggerPeriod::After, + TriggerPeriod::InsteadOf, + ] { + for include_each in [true, false] { + let for_each = if include_each { "FOR EACH" } else { "FOR" }; + + let sql = &format!( + "CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{for_each} ROW EXECUTE {exec_type} {func_desc}" + ); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: when, - event: event.clone(), - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - include_each, - condition: None, - exec_body: TriggerExecBody { - exec_type, - func_desc: func_desc.clone() + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: when, + event: event.clone(), + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + include_each, + condition: None, + exec_body: TriggerExecBody { + exec_type, + func_desc: func_desc.clone() + }, + characteristics } - } - ); - - let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {for_each} ROW WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: when, - event: event.clone(), - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - include_each, - condition: Some(Expr::Nested(Box::new(Expr::IsDistinctFrom( - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("OLD"), - Ident::new("balance") - ])), - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("NEW"), - Ident::new("balance") - ])), - )))), - exec_body: TriggerExecBody { - exec_type, - func_desc: func_desc.clone() + ); + + let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{for_each} ROW WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: when, + event: event.clone(), + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + include_each, + condition: Some(Expr::Nested(Box::new(Expr::IsDistinctFrom( + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("OLD"), + Ident::new("balance") + ])), + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("NEW"), + Ident::new("balance") + ])), + )))), + exec_body: TriggerExecBody { + exec_type, + func_desc: func_desc.clone() + }, + characteristics } - } - ); - - let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {for_each} ROW WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: when, - event: event.clone(), - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - include_each, - condition: Some(Expr::Nested(Box::new(Expr::IsNotDistinctFrom( - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("OLD"), - Ident::new("balance") - ])), - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("NEW"), - Ident::new("balance") - ])), - )))), - exec_body: TriggerExecBody { - exec_type, - func_desc: func_desc.clone() + ); + + let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{for_each} ROW WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: when, + event: event.clone(), + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(TriggerObject::Row), + include_each, + condition: Some(Expr::Nested(Box::new(Expr::IsNotDistinctFrom( + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("OLD"), + Ident::new("balance") + ])), + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("NEW"), + Ident::new("balance") + ])), + )))), + exec_body: TriggerExecBody { + exec_type, + func_desc: func_desc.clone() + }, + characteristics } - } - ); + ); + } } } } From 4dcee7f91fb4d94b5bea40fe14a057bf61d4ec45 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 27 Jul 2024 10:33:10 +0200 Subject: [PATCH 23/51] Added combinatorial option for Row/Statement --- src/ast/trigger.rs | 2 +- tests/sqlparser_postgres.rs | 368 ++++++++++++++++++------------------ 2 files changed, 188 insertions(+), 182 deletions(-) diff --git a/src/ast/trigger.rs b/src/ast/trigger.rs index ba47c3c3b..eaa4356e6 100644 --- a/src/ast/trigger.rs +++ b/src/ast/trigger.rs @@ -14,7 +14,7 @@ use super::*; /// This specifies whether the trigger function should be fired once for every row affected by the trigger event, or just once per SQL statement. -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum TriggerObject { diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 96f2f8edc..a209cbe2e 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4444,193 +4444,199 @@ fn test_table_unnest_with_ordinality() { #[test] fn parse_create_trigger() { - for (characteristics, characteristics_text) in [ - ( - None, - "", - ), - ( - Some(DeferrableCharacteristics { - deferrable: Some(true), - initially: Some(DeferrableInitial::Deferred), - }), - "DEFERRABLE INITIALLY DEFERRED ", - ), - ( - Some(DeferrableCharacteristics { - deferrable: Some(false), - initially: Some(DeferrableInitial::Immediate), - }), - "NOT DEFERRABLE INITIALLY IMMEDIATE ", - ), - ( - Some(DeferrableCharacteristics { - deferrable: Some(true), - initially: Some(DeferrableInitial::Immediate), - }), - "DEFERRABLE INITIALLY IMMEDIATE ", - ), - ( - Some(DeferrableCharacteristics { - deferrable: Some(true), - initially: Some(DeferrableInitial::Deferred), - }), - "DEFERRABLE INITIALLY DEFERRED ", - ) - ] { - for func_desc in [ - FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: None, - }, - FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: Some(vec![OperateFunctionArg::unnamed(DataType::Int(None))]), - }, - FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: Some(vec![ - OperateFunctionArg::with_name("a", DataType::Int(None)), - OperateFunctionArg { - mode: Some(ArgMode::In), - name: Some("b".into()), - data_type: DataType::Int(None), - default_expr: Some(Expr::Value(Value::Number("1".parse().unwrap(), false))), - }, - ]), - }, + for for_each_subject in [TriggerObject::Row, TriggerObject::Statement] { + for (characteristics, characteristics_text) in [ + (None, ""), + ( + Some(DeferrableCharacteristics { + deferrable: Some(true), + initially: Some(DeferrableInitial::Deferred), + }), + "DEFERRABLE INITIALLY DEFERRED ", + ), + ( + Some(DeferrableCharacteristics { + deferrable: Some(false), + initially: Some(DeferrableInitial::Immediate), + }), + "NOT DEFERRABLE INITIALLY IMMEDIATE ", + ), + ( + Some(DeferrableCharacteristics { + deferrable: Some(true), + initially: Some(DeferrableInitial::Immediate), + }), + "DEFERRABLE INITIALLY IMMEDIATE ", + ), + ( + Some(DeferrableCharacteristics { + deferrable: Some(true), + initially: Some(DeferrableInitial::Deferred), + }), + "DEFERRABLE INITIALLY DEFERRED ", + ), ] { - for exec_type in [ - TriggerExecBodyType::Function, - TriggerExecBodyType::Procedure, + for func_desc in [ + FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: None, + }, + FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: Some(vec![OperateFunctionArg::unnamed(DataType::Int(None))]), + }, + FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: Some(vec![ + OperateFunctionArg::with_name("a", DataType::Int(None)), + OperateFunctionArg { + mode: Some(ArgMode::In), + name: Some("b".into()), + data_type: DataType::Int(None), + default_expr: Some(Expr::Value(Value::Number( + "1".parse().unwrap(), + false, + ))), + }, + ]), + }, ] { - for (event, event_string) in [ - (vec![TriggerEvent::Update(vec![])], "UPDATE"), - (vec![TriggerEvent::Insert], "INSERT"), - (vec![TriggerEvent::Delete], "DELETE"), - ( - vec![TriggerEvent::Update(vec![]), TriggerEvent::Insert], - "UPDATE OR INSERT", - ), - ( - vec![ - TriggerEvent::Update(vec![]), - TriggerEvent::Insert, - TriggerEvent::Delete, - ], - "UPDATE OR INSERT OR DELETE", - ), - ( - vec![TriggerEvent::Update(vec![Ident::new("balance")])], - "UPDATE OF balance", - ), - ( - vec![ - TriggerEvent::Update(vec![Ident::new("balance")]), - TriggerEvent::Insert, - ], - "UPDATE OF balance OR INSERT", - ), - ( - vec![ - TriggerEvent::Update(vec![Ident::new("balance")]), - TriggerEvent::Insert, - TriggerEvent::Delete, - ], - "UPDATE OF balance OR INSERT OR DELETE", - ), + for exec_type in [ + TriggerExecBodyType::Function, + TriggerExecBodyType::Procedure, ] { - for when in [ - TriggerPeriod::Before, - TriggerPeriod::After, - TriggerPeriod::InsteadOf, + for (event, event_string) in [ + (vec![TriggerEvent::Update(vec![])], "UPDATE"), + (vec![TriggerEvent::Insert], "INSERT"), + (vec![TriggerEvent::Delete], "DELETE"), + ( + vec![TriggerEvent::Update(vec![]), TriggerEvent::Insert], + "UPDATE OR INSERT", + ), + ( + vec![ + TriggerEvent::Update(vec![]), + TriggerEvent::Insert, + TriggerEvent::Delete, + ], + "UPDATE OR INSERT OR DELETE", + ), + ( + vec![TriggerEvent::Update(vec![Ident::new("balance")])], + "UPDATE OF balance", + ), + ( + vec![ + TriggerEvent::Update(vec![Ident::new("balance")]), + TriggerEvent::Insert, + ], + "UPDATE OF balance OR INSERT", + ), + ( + vec![ + TriggerEvent::Update(vec![Ident::new("balance")]), + TriggerEvent::Insert, + TriggerEvent::Delete, + ], + "UPDATE OF balance OR INSERT OR DELETE", + ), ] { - for include_each in [true, false] { - let for_each = if include_each { "FOR EACH" } else { "FOR" }; - - let sql = &format!( - "CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{for_each} ROW EXECUTE {exec_type} {func_desc}" + for when in [ + TriggerPeriod::Before, + TriggerPeriod::After, + TriggerPeriod::InsteadOf, + ] { + for include_each in [true, false] { + let for_each = if include_each { "FOR EACH" } else { "FOR" }; + + let sql = &format!( + "CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{for_each} {for_each_subject} EXECUTE {exec_type} {func_desc}" ); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: when, - event: event.clone(), - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - include_each, - condition: None, - exec_body: TriggerExecBody { - exec_type, - func_desc: func_desc.clone() - }, - characteristics - } - ); - - let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{for_each} ROW WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: when, - event: event.clone(), - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - include_each, - condition: Some(Expr::Nested(Box::new(Expr::IsDistinctFrom( - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("OLD"), - Ident::new("balance") - ])), - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("NEW"), - Ident::new("balance") - ])), - )))), - exec_body: TriggerExecBody { - exec_type, - func_desc: func_desc.clone() - }, - characteristics - } - ); - - let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{for_each} ROW WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: when, - event: event.clone(), - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(TriggerObject::Row), - include_each, - condition: Some(Expr::Nested(Box::new(Expr::IsNotDistinctFrom( - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("OLD"), - Ident::new("balance") - ])), - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("NEW"), - Ident::new("balance") - ])), - )))), - exec_body: TriggerExecBody { - exec_type, - func_desc: func_desc.clone() - }, - characteristics - } - ); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: when, + event: event.clone(), + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(for_each_subject), + include_each, + condition: None, + exec_body: TriggerExecBody { + exec_type, + func_desc: func_desc.clone() + }, + characteristics + } + ); + + let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{for_each} {for_each_subject} WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: when, + event: event.clone(), + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(for_each_subject), + include_each, + condition: Some(Expr::Nested(Box::new( + Expr::IsDistinctFrom( + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("OLD"), + Ident::new("balance") + ])), + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("NEW"), + Ident::new("balance") + ])), + ) + ))), + exec_body: TriggerExecBody { + exec_type, + func_desc: func_desc.clone() + }, + characteristics + } + ); + + let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{for_each} {for_each_subject} WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: when, + event: event.clone(), + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: vec![], + for_each: Some(for_each_subject), + include_each, + condition: Some(Expr::Nested(Box::new( + Expr::IsNotDistinctFrom( + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("OLD"), + Ident::new("balance") + ])), + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("NEW"), + Ident::new("balance") + ])), + ) + ))), + exec_body: TriggerExecBody { + exec_type, + func_desc: func_desc.clone() + }, + characteristics + } + ); + } } } } From 5f4de6b5a2d0327ade9830db34dd2184932be94b Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 27 Jul 2024 10:53:29 +0200 Subject: [PATCH 24/51] Added testing for referencing tables in TRIGGERs --- src/ast/ddl.rs | 7 +- src/ast/mod.rs | 15 +- src/ast/trigger.rs | 2 +- src/parser/mod.rs | 17 +- tests/sqlparser_postgres.rs | 430 ++++++++++++++++++++---------------- 5 files changed, 270 insertions(+), 201 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 636b53dea..0a8bbc5d8 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -1236,7 +1236,12 @@ impl ConstraintCharacteristics { impl fmt::Display for ConstraintCharacteristics { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.deferrable.deferrable.is_some() || self.deferrable.initially.is_some() { - write!(f, "{}{}", self.deferrable, display_option_spaced(&self.enforced_text()))?; + write!( + f, + "{}{}", + self.deferrable, + display_option_spaced(&self.enforced_text()) + )?; } else if let Some(enforced) = self.enforced_text() { write!(f, "{}", enforced)?; } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 2bd866325..415cb49c8 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -33,10 +33,10 @@ pub use self::data_type::{ pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue}; pub use self::ddl::{ AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption, - ColumnOptionDef, ConstraintCharacteristics, DeferrableCharacteristics, DeferrableInitial, GeneratedAs, - GeneratedExpressionMode, IndexOption, IndexType, KeyOrIndexDisplay, Owner, Partition, - ProcedureParam, ReferentialAction, TableConstraint, UserDefinedTypeCompositeAttributeDef, - UserDefinedTypeRepresentation, ViewColumnDef, + ColumnOptionDef, ConstraintCharacteristics, DeferrableCharacteristics, DeferrableInitial, + GeneratedAs, GeneratedExpressionMode, IndexOption, IndexType, KeyOrIndexDisplay, Owner, + Partition, ProcedureParam, ReferentialAction, TableConstraint, + UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef, }; pub use self::dml::{CreateIndex, CreateTable, Delete, Insert}; pub use self::operator::{BinaryOperator, UnaryOperator}; @@ -3444,14 +3444,15 @@ impl fmt::Display for Statement { write!(f, " {}", display_separated(event, " OR "))?; } write!(f, " ON {table_name}")?; - if !referencing.is_empty() { - write!(f, " REFERENCING {}", display_separated(referencing, " "))?; - } if let Some(characteristics) = characteristics { write!(f, " {characteristics}")?; } + if !referencing.is_empty() { + write!(f, " REFERENCING {}", display_separated(referencing, " "))?; + } + if let Some(trigger_object) = for_each { if *include_each { write!(f, " FOR EACH {trigger_object}")?; diff --git a/src/ast/trigger.rs b/src/ast/trigger.rs index eaa4356e6..a0913db94 100644 --- a/src/ast/trigger.rs +++ b/src/ast/trigger.rs @@ -32,7 +32,7 @@ impl fmt::Display for TriggerObject { } /// This clause indicates whether the following relation name is for the before-image transition relation or the after-image transition relation -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum TriggerReferencingType { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ec4ca4bbf..e26932caf 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4236,12 +4236,12 @@ impl<'a> Parser<'a> { include_each, condition, exec_body, - characteristics: (deferrable.is_some() || initially.is_some()).then(|| { + characteristics: (deferrable.is_some() || initially.is_some()).then_some( DeferrableCharacteristics { deferrable, initially, - } - }), + }, + ), }) } else { self.prev_token(); @@ -6152,10 +6152,12 @@ impl<'a> Parser<'a> { let mut cc = ConstraintCharacteristics::default(); loop { - if cc.deferrable.deferrable.is_none() && self.parse_keywords(&[Keyword::NOT, Keyword::DEFERRABLE]) + if cc.deferrable.deferrable.is_none() + && self.parse_keywords(&[Keyword::NOT, Keyword::DEFERRABLE]) { cc.deferrable.deferrable = Some(false); - } else if cc.deferrable.deferrable.is_none() && self.parse_keyword(Keyword::DEFERRABLE) { + } else if cc.deferrable.deferrable.is_none() && self.parse_keyword(Keyword::DEFERRABLE) + { cc.deferrable.deferrable = Some(true); } else if cc.deferrable.initially.is_none() && self.parse_keyword(Keyword::INITIALLY) { if self.parse_keyword(Keyword::DEFERRED) { @@ -6176,7 +6178,10 @@ impl<'a> Parser<'a> { } } - if cc.deferrable.deferrable.is_some() || cc.deferrable.initially.is_some() || cc.enforced.is_some() { + if cc.deferrable.deferrable.is_some() + || cc.deferrable.initially.is_some() + || cc.enforced.is_some() + { Ok(Some(cc)) } else { Ok(None) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index a209cbe2e..7c026e78c 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4444,198 +4444,256 @@ fn test_table_unnest_with_ordinality() { #[test] fn parse_create_trigger() { - for for_each_subject in [TriggerObject::Row, TriggerObject::Statement] { - for (characteristics, characteristics_text) in [ - (None, ""), - ( - Some(DeferrableCharacteristics { - deferrable: Some(true), - initially: Some(DeferrableInitial::Deferred), - }), - "DEFERRABLE INITIALLY DEFERRED ", - ), - ( - Some(DeferrableCharacteristics { - deferrable: Some(false), - initially: Some(DeferrableInitial::Immediate), - }), - "NOT DEFERRABLE INITIALLY IMMEDIATE ", - ), - ( - Some(DeferrableCharacteristics { - deferrable: Some(true), - initially: Some(DeferrableInitial::Immediate), - }), - "DEFERRABLE INITIALLY IMMEDIATE ", - ), - ( - Some(DeferrableCharacteristics { - deferrable: Some(true), - initially: Some(DeferrableInitial::Deferred), - }), - "DEFERRABLE INITIALLY DEFERRED ", - ), - ] { - for func_desc in [ - FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: None, - }, - FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: Some(vec![OperateFunctionArg::unnamed(DataType::Int(None))]), - }, - FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: Some(vec![ - OperateFunctionArg::with_name("a", DataType::Int(None)), - OperateFunctionArg { - mode: Some(ArgMode::In), - name: Some("b".into()), - data_type: DataType::Int(None), - default_expr: Some(Expr::Value(Value::Number( - "1".parse().unwrap(), - false, - ))), - }, - ]), - }, + for referencing in [ + vec![], + vec![TriggerReferencing { + refer_type: TriggerReferencingType::NewTable, + is_as: false, + transition_relation_name: ObjectName(vec![Ident::new("new_table")]), + }], + vec![TriggerReferencing { + refer_type: TriggerReferencingType::OldTable, + is_as: false, + transition_relation_name: ObjectName(vec![Ident::new("old_table")]), + }], + vec![ + TriggerReferencing { + refer_type: TriggerReferencingType::NewTable, + is_as: false, + transition_relation_name: ObjectName(vec![Ident::new("new_table")]), + }, + TriggerReferencing { + refer_type: TriggerReferencingType::OldTable, + is_as: false, + transition_relation_name: ObjectName(vec![Ident::new("old_table")]), + }, + ], + vec![ + TriggerReferencing { + refer_type: TriggerReferencingType::OldTable, + is_as: false, + transition_relation_name: ObjectName(vec![Ident::new("old_table")]), + }, + TriggerReferencing { + refer_type: TriggerReferencingType::NewTable, + is_as: false, + transition_relation_name: ObjectName(vec![Ident::new("new_table")]), + }, + ], + ] { + for for_each_subject in [TriggerObject::Row, TriggerObject::Statement] { + for (characteristics, characteristics_text) in [ + (None, ""), + ( + Some(DeferrableCharacteristics { + deferrable: Some(true), + initially: Some(DeferrableInitial::Deferred), + }), + "DEFERRABLE INITIALLY DEFERRED ", + ), + ( + Some(DeferrableCharacteristics { + deferrable: Some(false), + initially: Some(DeferrableInitial::Immediate), + }), + "NOT DEFERRABLE INITIALLY IMMEDIATE ", + ), + ( + Some(DeferrableCharacteristics { + deferrable: Some(true), + initially: Some(DeferrableInitial::Immediate), + }), + "DEFERRABLE INITIALLY IMMEDIATE ", + ), + ( + Some(DeferrableCharacteristics { + deferrable: Some(true), + initially: Some(DeferrableInitial::Deferred), + }), + "DEFERRABLE INITIALLY DEFERRED ", + ), ] { - for exec_type in [ - TriggerExecBodyType::Function, - TriggerExecBodyType::Procedure, + for func_desc in [ + FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: None, + }, + FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: Some(vec![OperateFunctionArg::unnamed(DataType::Int(None))]), + }, + FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: Some(vec![ + OperateFunctionArg::with_name("a", DataType::Int(None)), + OperateFunctionArg { + mode: Some(ArgMode::In), + name: Some("b".into()), + data_type: DataType::Int(None), + default_expr: Some(Expr::Value(Value::Number( + "1".parse().unwrap(), + false, + ))), + }, + ]), + }, ] { - for (event, event_string) in [ - (vec![TriggerEvent::Update(vec![])], "UPDATE"), - (vec![TriggerEvent::Insert], "INSERT"), - (vec![TriggerEvent::Delete], "DELETE"), - ( - vec![TriggerEvent::Update(vec![]), TriggerEvent::Insert], - "UPDATE OR INSERT", - ), - ( - vec![ - TriggerEvent::Update(vec![]), - TriggerEvent::Insert, - TriggerEvent::Delete, - ], - "UPDATE OR INSERT OR DELETE", - ), - ( - vec![TriggerEvent::Update(vec![Ident::new("balance")])], - "UPDATE OF balance", - ), - ( - vec![ - TriggerEvent::Update(vec![Ident::new("balance")]), - TriggerEvent::Insert, - ], - "UPDATE OF balance OR INSERT", - ), - ( - vec![ - TriggerEvent::Update(vec![Ident::new("balance")]), - TriggerEvent::Insert, - TriggerEvent::Delete, - ], - "UPDATE OF balance OR INSERT OR DELETE", - ), + for exec_type in [ + TriggerExecBodyType::Function, + TriggerExecBodyType::Procedure, ] { - for when in [ - TriggerPeriod::Before, - TriggerPeriod::After, - TriggerPeriod::InsteadOf, + for (event, event_string) in [ + (vec![TriggerEvent::Update(vec![])], "UPDATE"), + (vec![TriggerEvent::Insert], "INSERT"), + (vec![TriggerEvent::Delete], "DELETE"), + ( + vec![TriggerEvent::Update(vec![]), TriggerEvent::Insert], + "UPDATE OR INSERT", + ), + ( + vec![ + TriggerEvent::Update(vec![]), + TriggerEvent::Insert, + TriggerEvent::Delete, + ], + "UPDATE OR INSERT OR DELETE", + ), + ( + vec![TriggerEvent::Update(vec![Ident::new("balance")])], + "UPDATE OF balance", + ), + ( + vec![ + TriggerEvent::Update(vec![Ident::new("balance")]), + TriggerEvent::Insert, + ], + "UPDATE OF balance OR INSERT", + ), + ( + vec![ + TriggerEvent::Update(vec![Ident::new("balance")]), + TriggerEvent::Insert, + TriggerEvent::Delete, + ], + "UPDATE OF balance OR INSERT OR DELETE", + ), ] { - for include_each in [true, false] { - let for_each = if include_each { "FOR EACH" } else { "FOR" }; - - let sql = &format!( - "CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{for_each} {for_each_subject} EXECUTE {exec_type} {func_desc}" + for when in [ + TriggerPeriod::Before, + TriggerPeriod::After, + TriggerPeriod::InsteadOf, + ] { + for include_each in [true, false] { + let referencing_text = if referencing.is_empty() { + "".to_string() + } else { + format!( + "REFERENCING {} ", + referencing + .iter() + .map(|r| { + format!( + "{} {}{}", + r.refer_type, + if r.is_as { "AS " } else { "" }, + r.transition_relation_name + ) + }) + .collect::>() + .join(" ") + ) + }; + + let for_each = if include_each { "FOR EACH" } else { "FOR" }; + + let sql = &format!( + "CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{referencing_text}{for_each} {for_each_subject} EXECUTE {exec_type} {func_desc}" ); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: when, - event: event.clone(), - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(for_each_subject), - include_each, - condition: None, - exec_body: TriggerExecBody { - exec_type, - func_desc: func_desc.clone() - }, - characteristics - } - ); - - let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{for_each} {for_each_subject} WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: when, - event: event.clone(), - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(for_each_subject), - include_each, - condition: Some(Expr::Nested(Box::new( - Expr::IsDistinctFrom( - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("OLD"), - Ident::new("balance") - ])), - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("NEW"), - Ident::new("balance") - ])), - ) - ))), - exec_body: TriggerExecBody { - exec_type, - func_desc: func_desc.clone() - }, - characteristics - } - ); - - let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{for_each} {for_each_subject} WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period: when, - event: event.clone(), - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: vec![], - for_each: Some(for_each_subject), - include_each, - condition: Some(Expr::Nested(Box::new( - Expr::IsNotDistinctFrom( - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("OLD"), - Ident::new("balance") - ])), - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("NEW"), - Ident::new("balance") - ])), - ) - ))), - exec_body: TriggerExecBody { - exec_type, - func_desc: func_desc.clone() - }, - characteristics - } - ); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: when, + event: event.clone(), + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: referencing.clone(), + for_each: Some(for_each_subject), + include_each, + condition: None, + exec_body: TriggerExecBody { + exec_type, + func_desc: func_desc.clone() + }, + characteristics + } + ); + + let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{referencing_text}{for_each} {for_each_subject} WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: when, + event: event.clone(), + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: referencing.clone(), + for_each: Some(for_each_subject), + include_each, + condition: Some(Expr::Nested(Box::new( + Expr::IsDistinctFrom( + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("OLD"), + Ident::new("balance") + ])), + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("NEW"), + Ident::new("balance") + ])), + ) + ))), + exec_body: TriggerExecBody { + exec_type, + func_desc: func_desc.clone() + }, + characteristics + } + ); + + let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{referencing_text}{for_each} {for_each_subject} WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); + assert_eq!( + pg().verified_stmt(sql), + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("check_update")]), + period: when, + event: event.clone(), + table_name: ObjectName(vec![Ident::new("accounts")]), + referencing: referencing.clone(), + for_each: Some(for_each_subject), + include_each, + condition: Some(Expr::Nested(Box::new( + Expr::IsNotDistinctFrom( + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("OLD"), + Ident::new("balance") + ])), + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("NEW"), + Ident::new("balance") + ])), + ) + ))), + exec_body: TriggerExecBody { + exec_type, + func_desc: func_desc.clone() + }, + characteristics + } + ); + } } } } From eeb2eb0e5208f9e83d4ac46378603875f8568fbf Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 27 Jul 2024 11:01:34 +0200 Subject: [PATCH 25/51] Added some negative testig --- tests/sqlparser_postgres.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 7c026e78c..9ee8d767b 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4702,3 +4702,33 @@ fn parse_create_trigger() { } } } + +#[test] +/// While in the parse_create_trigger test we test the full syntax of the CREATE TRIGGER statement, +/// here we test the invalid cases of the CREATE TRIGGER statement which should cause an appropriate +/// error to be returned. +fn parse_create_trigger_invalid_cases() { + // Test invalid cases for the CREATE TRIGGER statement + let invalid_cases = vec![ + ( + "CREATE TRIGGER check_update BEFORE UPDATE ON accounts FUNCTION check_account_update", + "Expected: EXECUTE, found: FUNCTION" + ), + ( + "CREATE TRIGGER check_update TOMORROW UPDATE ON accounts EXECUTE FUNCTION check_account_update", + "Expected: one of BEFORE or AFTER or INSTEAD, found: TOMORROW" + ), + ( + "CREATE TRIGGER check_update BEFORE SAVE ON accounts EXECUTE FUNCTION check_account_update", + "Expected: one of INSERT or UPDATE or DELETE or TRUNCATE, found: SAVE" + ) + ]; + + for (sql, expected_error) in invalid_cases { + let res = pg().parse_sql_statements(sql); + assert_eq!( + format!("sql parser error: {expected_error}"), + res.unwrap_err().to_string() + ); + } +} From b23c5fd12ce20ae31c4a5b2583811039f933dfb3 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 27 Jul 2024 11:23:57 +0200 Subject: [PATCH 26/51] Added support for DropTrigger --- src/ast/mod.rs | 29 +++++++++++++++++++ src/parser/mod.rs | 34 ++++++++++++++++++++++- tests/sqlparser_postgres.rs | 55 +++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 1 deletion(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 415cb49c8..4431135c4 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2667,6 +2667,19 @@ pub enum Statement { /// The characteristic of the trigger, which include whether the trigger is `DEFERRABLE`, `INITIALLY DEFERRED`, or `INITIALLY IMMEDIATE`, characteristics: Option, }, + /// DROP TRIGGER + /// + /// ```sql + /// DROP TRIGGER [ IF EXISTS ] name ON table_name [ CASCADE | RESTRICT ] + /// ``` + /// + DropTrigger { + if_exists: bool, + trigger_name: ObjectName, + table_name: ObjectName, + /// `CASCADE` or `RESTRICT` + option: Option, + }, /// ```sql /// CREATE PROCEDURE /// ``` @@ -3465,6 +3478,22 @@ impl fmt::Display for Statement { } write!(f, " EXECUTE {exec_body}") } + Statement::DropTrigger { + if_exists, + trigger_name, + table_name, + option, + } => { + write!(f, "DROP TRIGGER")?; + if *if_exists { + write!(f, " IF EXISTS")?; + } + write!(f, " {trigger_name} ON {table_name}")?; + if let Some(option) = option { + write!(f, " {option}")?; + } + Ok(()) + } Statement::CreateProcedure { name, or_alter, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index e26932caf..b6efb8816 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4160,6 +4160,36 @@ impl<'a> Parser<'a> { }) } + /// Parse statements of the DropTrigger type such as: + /// + /// ```sql + /// DROP TRIGGER [ IF EXISTS ] name ON table_name [ CASCADE | RESTRICT ] + /// ``` + pub fn parse_drop_trigger(&mut self) -> Result { + if dialect_of!(self is PostgreSqlDialect) { + let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); + let trigger_name = self.parse_object_name(false)?; + self.expect_keyword(Keyword::ON)?; + let table_name = self.parse_object_name(false)?; + let option = self + .parse_one_of_keywords(&[Keyword::CASCADE, Keyword::RESTRICT]) + .map(|keyword| match keyword { + Keyword::CASCADE => ReferentialAction::Cascade, + Keyword::RESTRICT => ReferentialAction::Restrict, + _ => unreachable!(), + }); + Ok(Statement::DropTrigger { + if_exists, + trigger_name, + table_name, + option, + }) + } else { + self.prev_token(); + self.expected("an object type after CREATE", self.peek_token()) + } + } + pub fn parse_create_trigger(&mut self, or_replace: bool) -> Result { if dialect_of!(self is PostgreSqlDialect) { let name = self.parse_object_name(false)?; @@ -4775,9 +4805,11 @@ impl<'a> Parser<'a> { return self.parse_drop_procedure(); } else if self.parse_keyword(Keyword::SECRET) { return self.parse_drop_secret(temporary, persistent); + } else if self.parse_keyword(Keyword::TRIGGER) { + return self.parse_drop_trigger(); } else { return self.expected( - "TABLE, VIEW, INDEX, ROLE, SCHEMA, FUNCTION, PROCEDURE, STAGE or SEQUENCE after DROP", + "TABLE, VIEW, INDEX, ROLE, SCHEMA, FUNCTION, PROCEDURE, STAGE, TRIGGER, SECRET or SEQUENCE after DROP", self.peek_token(), ); }; diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 9ee8d767b..fb42f55ab 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4732,3 +4732,58 @@ fn parse_create_trigger_invalid_cases() { ); } } + +#[test] +fn parse_drop_trigger() { + for if_exists in [true, false] { + for option in [ + None, + Some(ReferentialAction::Cascade), + Some(ReferentialAction::Restrict), + ] { + let sql = &format!( + "DROP TRIGGER{} check_update ON table_name{}", + if if_exists { " IF EXISTS" } else { "" }, + option + .map(|o| format!(" {}", o)) + .unwrap_or_else(|| "".to_string()) + ); + assert_eq!( + pg().verified_stmt(sql), + Statement::DropTrigger { + if_exists, + trigger_name: ObjectName(vec![Ident::new("check_update")]), + table_name: ObjectName(vec![Ident::new("table_name")]), + option + } + ); + } + } +} + +#[test] +fn parse_drop_trigger_invalid_cases() { + // Test invalid cases for the DROP TRIGGER statement + let invalid_cases = vec![ + ( + "DROP TRIGGER check_update ON table_name CASCADE RESTRICT", + "Expected: end of statement, found: RESTRICT", + ), + ( + "DROP TRIGGER check_update ON table_name CASCADE CASCADE", + "Expected: end of statement, found: CASCADE", + ), + ( + "DROP TRIGGER check_update ON table_name CASCADE CASCADE CASCADE", + "Expected: end of statement, found: CASCADE", + ), + ]; + + for (sql, expected_error) in invalid_cases { + let res = pg().parse_sql_statements(sql); + assert_eq!( + format!("sql parser error: {expected_error}"), + res.unwrap_err().to_string() + ); + } +} From 2555c1e893e6f51f1015581e39bb1c210890afed Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 27 Jul 2024 16:03:57 +0200 Subject: [PATCH 27/51] Removed optionality of TriggerObject as it must be defined --- src/ast/mod.rs | 17 ++++++++--------- src/parser/mod.rs | 24 +++++++++--------------- tests/sqlparser_postgres.rs | 16 ++++++++-------- 3 files changed, 25 insertions(+), 32 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 4431135c4..51a887e84 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2656,8 +2656,9 @@ pub enum Statement { table_name: ObjectName, /// This keyword immediately precedes the declaration of one or two relation names that provide access to the transition relations of the triggering statement. referencing: Vec, - /// This specifies whether the trigger function should be fired once for every row affected by the trigger event, or just once per SQL statement. - for_each: Option, + /// This specifies whether the trigger function should be fired once for + /// every row affected by the trigger event, or just once per SQL statement. + trigger_object: TriggerObject, /// Whether to include the `EACH` term of the `FOR EACH`, as it is optional syntax. include_each: bool, /// Triggering conditions @@ -3442,7 +3443,7 @@ impl fmt::Display for Statement { event, table_name, referencing, - for_each, + trigger_object, condition, include_each, exec_body, @@ -3466,12 +3467,10 @@ impl fmt::Display for Statement { write!(f, " REFERENCING {}", display_separated(referencing, " "))?; } - if let Some(trigger_object) = for_each { - if *include_each { - write!(f, " FOR EACH {trigger_object}")?; - } else { - write!(f, " FOR {trigger_object}")?; - } + if *include_each { + write!(f, " FOR EACH {trigger_object}")?; + } else { + write!(f, " FOR {trigger_object}")?; } if let Some(condition) = condition { write!(f, " WHEN {condition}")?; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index b6efb8816..16786f6cb 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4230,21 +4230,15 @@ impl<'a> Parser<'a> { } } - let (for_each, include_each) = if self.parse_keyword(Keyword::FOR) { + let (trigger_object, include_each) = self.expect_keyword(Keyword::FOR).and_then(|_| { let include_each = self.parse_keyword(Keyword::EACH); - ( - Some( - match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? { - Keyword::ROW => TriggerObject::Row, - Keyword::STATEMENT => TriggerObject::Statement, - _ => unreachable!(), - }, - ), - include_each, - ) - } else { - (None, false) - }; + let trigger_object = match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? { + Keyword::ROW => TriggerObject::Row, + Keyword::STATEMENT => TriggerObject::Statement, + _ => unreachable!(), + }; + Ok((trigger_object, include_each)) + })?; let condition = self .parse_keyword(Keyword::WHEN) @@ -4262,7 +4256,7 @@ impl<'a> Parser<'a> { event, table_name, referencing, - for_each, + trigger_object, include_each, condition, exec_body, diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index fb42f55ab..f47008cc2 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4481,7 +4481,7 @@ fn parse_create_trigger() { }, ], ] { - for for_each_subject in [TriggerObject::Row, TriggerObject::Statement] { + for trigger_object in [TriggerObject::Row, TriggerObject::Statement] { for (characteristics, characteristics_text) in [ (None, ""), ( @@ -4607,7 +4607,7 @@ fn parse_create_trigger() { let for_each = if include_each { "FOR EACH" } else { "FOR" }; let sql = &format!( - "CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{referencing_text}{for_each} {for_each_subject} EXECUTE {exec_type} {func_desc}" + "CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{referencing_text}{for_each} {trigger_object} EXECUTE {exec_type} {func_desc}" ); assert_eq!( @@ -4619,7 +4619,7 @@ fn parse_create_trigger() { event: event.clone(), table_name: ObjectName(vec![Ident::new("accounts")]), referencing: referencing.clone(), - for_each: Some(for_each_subject), + trigger_object, include_each, condition: None, exec_body: TriggerExecBody { @@ -4630,7 +4630,7 @@ fn parse_create_trigger() { } ); - let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{referencing_text}{for_each} {for_each_subject} WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); + let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{referencing_text}{for_each} {trigger_object} WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); assert_eq!( pg().verified_stmt(sql), Statement::CreateTrigger { @@ -4640,7 +4640,7 @@ fn parse_create_trigger() { event: event.clone(), table_name: ObjectName(vec![Ident::new("accounts")]), referencing: referencing.clone(), - for_each: Some(for_each_subject), + trigger_object, include_each, condition: Some(Expr::Nested(Box::new( Expr::IsDistinctFrom( @@ -4662,7 +4662,7 @@ fn parse_create_trigger() { } ); - let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{referencing_text}{for_each} {for_each_subject} WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); + let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{referencing_text}{for_each} {trigger_object} WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); assert_eq!( pg().verified_stmt(sql), Statement::CreateTrigger { @@ -4672,7 +4672,7 @@ fn parse_create_trigger() { event: event.clone(), table_name: ObjectName(vec![Ident::new("accounts")]), referencing: referencing.clone(), - for_each: Some(for_each_subject), + trigger_object, include_each, condition: Some(Expr::Nested(Box::new( Expr::IsNotDistinctFrom( @@ -4712,7 +4712,7 @@ fn parse_create_trigger_invalid_cases() { let invalid_cases = vec![ ( "CREATE TRIGGER check_update BEFORE UPDATE ON accounts FUNCTION check_account_update", - "Expected: EXECUTE, found: FUNCTION" + "Expected: FOR, found: FUNCTION" ), ( "CREATE TRIGGER check_update TOMORROW UPDATE ON accounts EXECUTE FUNCTION check_account_update", From 8cb0ac9e66dccdc85a04a197812df46065751d96 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 27 Jul 2024 16:06:46 +0200 Subject: [PATCH 28/51] Formatted code --- src/parser/mod.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 16786f6cb..ba9ca8940 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4230,15 +4230,17 @@ impl<'a> Parser<'a> { } } - let (trigger_object, include_each) = self.expect_keyword(Keyword::FOR).and_then(|_| { - let include_each = self.parse_keyword(Keyword::EACH); - let trigger_object = match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? { - Keyword::ROW => TriggerObject::Row, - Keyword::STATEMENT => TriggerObject::Statement, - _ => unreachable!(), - }; - Ok((trigger_object, include_each)) - })?; + let (trigger_object, include_each) = + self.expect_keyword(Keyword::FOR).and_then(|_| { + let include_each = self.parse_keyword(Keyword::EACH); + let trigger_object = + match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? { + Keyword::ROW => TriggerObject::Row, + Keyword::STATEMENT => TriggerObject::Statement, + _ => unreachable!(), + }; + Ok((trigger_object, include_each)) + })?; let condition = self .parse_keyword(Keyword::WHEN) From 47c2f409855cbb55425514c0f40728828d0d8f31 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 27 Jul 2024 16:42:22 +0200 Subject: [PATCH 29/51] Changed attribute event to events as it refers to a vector of events --- src/ast/mod.rs | 8 ++++---- src/parser/mod.rs | 4 ++-- tests/sqlparser_postgres.rs | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 51a887e84..dbe8ebcc6 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2651,7 +2651,7 @@ pub enum Statement { /// ``` period: TriggerPeriod, /// Multiple events can be specified using OR, such as `INSERT`, `UPDATE`, `DELETE`, or `TRUNCATE`. - event: Vec, + events: Vec, /// The table on which the trigger is to be created. table_name: ObjectName, /// This keyword immediately precedes the declaration of one or two relation names that provide access to the transition relations of the triggering statement. @@ -3440,7 +3440,7 @@ impl fmt::Display for Statement { or_replace, name, period, - event, + events, table_name, referencing, trigger_object, @@ -3454,8 +3454,8 @@ impl fmt::Display for Statement { "CREATE {or_replace}TRIGGER {name} {period}", or_replace = if *or_replace { "OR REPLACE " } else { "" }, )?; - if !event.is_empty() { - write!(f, " {}", display_separated(event, " OR "))?; + if !events.is_empty() { + write!(f, " {}", display_separated(events, " OR "))?; } write!(f, " ON {table_name}")?; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ba9ca8940..6f68d811c 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4195,7 +4195,7 @@ impl<'a> Parser<'a> { let name = self.parse_object_name(false)?; let period = self.parse_trigger_period()?; - let event = self.parse_keyword_separated(Keyword::OR, Parser::parse_trigger_event)?; + let events = self.parse_keyword_separated(Keyword::OR, Parser::parse_trigger_event)?; let table_name = self .expect_keyword(Keyword::ON) .and_then(|_| self.parse_object_name(false))?; @@ -4255,7 +4255,7 @@ impl<'a> Parser<'a> { or_replace, name, period, - event, + events, table_name, referencing, trigger_object, diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index f47008cc2..09838e40a 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4542,7 +4542,7 @@ fn parse_create_trigger() { TriggerExecBodyType::Function, TriggerExecBodyType::Procedure, ] { - for (event, event_string) in [ + for (events, event_string) in [ (vec![TriggerEvent::Update(vec![])], "UPDATE"), (vec![TriggerEvent::Insert], "INSERT"), (vec![TriggerEvent::Delete], "DELETE"), @@ -4616,7 +4616,7 @@ fn parse_create_trigger() { or_replace: false, name: ObjectName(vec![Ident::new("check_update")]), period: when, - event: event.clone(), + events: events.clone(), table_name: ObjectName(vec![Ident::new("accounts")]), referencing: referencing.clone(), trigger_object, @@ -4637,7 +4637,7 @@ fn parse_create_trigger() { or_replace: false, name: ObjectName(vec![Ident::new("check_update")]), period: when, - event: event.clone(), + events: events.clone(), table_name: ObjectName(vec![Ident::new("accounts")]), referencing: referencing.clone(), trigger_object, @@ -4669,7 +4669,7 @@ fn parse_create_trigger() { or_replace: false, name: ObjectName(vec![Ident::new("check_update")]), period: when, - event: event.clone(), + events: events.clone(), table_name: ObjectName(vec![Ident::new("accounts")]), referencing: referencing.clone(), trigger_object, From e81a58cd88a55db26f16c035aac04ed4e10c1c59 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 27 Jul 2024 18:44:00 +0200 Subject: [PATCH 30/51] Extended the test suite --- tests/sqlparser_postgres.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 09838e40a..c3fff37dc 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4574,8 +4574,38 @@ fn parse_create_trigger() { TriggerEvent::Update(vec![Ident::new("balance")]), TriggerEvent::Insert, TriggerEvent::Delete, + TriggerEvent::Update(vec![Ident::new("name")]), ], - "UPDATE OF balance OR INSERT OR DELETE", + "UPDATE OF balance OR INSERT OR DELETE OR UPDATE OF name", + ), + ( + vec![ + TriggerEvent::Update(vec![Ident::new("balance"), Ident::new("name"), Ident::new("name2"), Ident::new("name3")]), + TriggerEvent::Update(vec![Ident::new("name")]), + ], + "UPDATE OF balance, name, name2, name3 OR UPDATE OF name", + ), + ( + vec![ + TriggerEvent::Update(vec![Ident::new("balance"), Ident::new("name")]), + TriggerEvent::Insert, + ], + "UPDATE OF balance, name OR INSERT", + ), + ( + vec![ + TriggerEvent::Update(vec![Ident::new("balance"), Ident::new("name")]), + TriggerEvent::Delete, + ], + "UPDATE OF balance, name OR DELETE", + ), + ( + vec![ + TriggerEvent::Update(vec![Ident::new("balance"), Ident::new("name")]), + TriggerEvent::Insert, + TriggerEvent::Delete, + ], + "UPDATE OF balance, name OR INSERT OR DELETE", ), ] { for when in [ From c3a96e0be51b7057e6f597fc56eab1876fe6a1c4 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sun, 28 Jul 2024 07:22:53 +0200 Subject: [PATCH 31/51] Added support for the TRIGGER return type --- src/ast/data_type.rs | 5 + src/parser/mod.rs | 1 + tests/sqlparser_postgres.rs | 243 +++++++++++++++++++++++++++++++++++- 3 files changed, 245 insertions(+), 4 deletions(-) diff --git a/src/ast/data_type.rs b/src/ast/data_type.rs index e6477f56b..ff2a3ad04 100644 --- a/src/ast/data_type.rs +++ b/src/ast/data_type.rs @@ -319,6 +319,10 @@ pub enum DataType { /// [`SQLiteDialect`](crate::dialect::SQLiteDialect), from statements such /// as `CREATE TABLE t1 (a)`. Unspecified, + /// Trigger data type, returned by functions associated with triggers + /// + /// [postgresql]: https://www.postgresql.org/docs/current/plpgsql-trigger.html + Trigger, } impl fmt::Display for DataType { @@ -543,6 +547,7 @@ impl fmt::Display for DataType { write!(f, "Nested({})", display_comma_separated(fields)) } DataType::Unspecified => Ok(()), + DataType::Trigger => write!(f, "TRIGGER"), } } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6f68d811c..1fbf5abad 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -7555,6 +7555,7 @@ impl<'a> Parser<'a> { let field_defs = self.parse_click_house_tuple_def()?; Ok(DataType::Tuple(field_defs)) } + Keyword::TRIGGER => Ok(DataType::Trigger), _ => { self.prev_token(); let type_name = self.parse_object_name(false)?; diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index c3fff37dc..e9597ca44 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4580,28 +4580,42 @@ fn parse_create_trigger() { ), ( vec![ - TriggerEvent::Update(vec![Ident::new("balance"), Ident::new("name"), Ident::new("name2"), Ident::new("name3")]), + TriggerEvent::Update(vec![ + Ident::new("balance"), + Ident::new("name"), + Ident::new("name2"), + Ident::new("name3"), + ]), TriggerEvent::Update(vec![Ident::new("name")]), ], "UPDATE OF balance, name, name2, name3 OR UPDATE OF name", ), ( vec![ - TriggerEvent::Update(vec![Ident::new("balance"), Ident::new("name")]), + TriggerEvent::Update(vec![ + Ident::new("balance"), + Ident::new("name"), + ]), TriggerEvent::Insert, ], "UPDATE OF balance, name OR INSERT", ), ( vec![ - TriggerEvent::Update(vec![Ident::new("balance"), Ident::new("name")]), + TriggerEvent::Update(vec![ + Ident::new("balance"), + Ident::new("name"), + ]), TriggerEvent::Delete, ], "UPDATE OF balance, name OR DELETE", ), ( vec![ - TriggerEvent::Update(vec![Ident::new("balance"), Ident::new("name")]), + TriggerEvent::Update(vec![ + Ident::new("balance"), + Ident::new("name"), + ]), TriggerEvent::Insert, TriggerEvent::Delete, ], @@ -4817,3 +4831,224 @@ fn parse_drop_trigger_invalid_cases() { ); } } + +#[test] +fn parse_trigger_related_functions() { + // First we define all parts of the trigger definition, + // including the table creation, the function creation, the trigger creation and the trigger drop. + // The following example is taken from the PostgreSQL documentation + + let sql_table_creation = r#" + CREATE TABLE emp ( + empname text, + salary integer, + last_date timestamp, + last_user text + ); + "#; + + let sql_create_function = r#" + CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$ + BEGIN + -- Check that empname and salary are given + IF NEW.empname IS NULL THEN + RAISE EXCEPTION 'empname cannot be null'; + END IF; + IF NEW.salary IS NULL THEN + RAISE EXCEPTION '% cannot have null salary', NEW.empname; + END IF; + + -- Who works for us when they must pay for it? + IF NEW.salary < 0 THEN + RAISE EXCEPTION '% cannot have a negative salary', NEW.empname; + END IF; + + -- Remember who changed the payroll when + NEW.last_date := current_timestamp; + NEW.last_user := current_user; + RETURN NEW; + END; + $emp_stamp$ LANGUAGE plpgsql; + "#; + + let sql_create_trigger = r#" + CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp + FOR EACH ROW EXECUTE FUNCTION emp_stamp(); + "#; + + let sql_drop_trigger = r#" + DROP TRIGGER emp_stamp ON emp; + "#; + + // 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 + )) + .unwrap(); + + assert_eq!(statements.len(), 4); + let drop_trigger = statements.pop().unwrap(); + let create_trigger = statements.pop().unwrap(); + let create_function = statements.pop().unwrap(); + let create_table = statements.pop().unwrap(); + + // Check the first statement + let create_table = match create_table { + Statement::CreateTable(create_table) => create_table, + _ => panic!("Expected CreateTable statement"), + }; + + assert_eq!( + create_table, + CreateTable { + or_replace: false, + temporary: false, + external: false, + global: None, + if_not_exists: false, + transient: false, + volatile: false, + name: ObjectName(vec![Ident::new("emp")]), + columns: vec![ + ColumnDef { + name: "empname".into(), + data_type: DataType::Text, + collation: None, + options: vec![], + }, + ColumnDef { + name: "salary".into(), + data_type: DataType::Integer(None), + collation: None, + options: vec![], + }, + ColumnDef { + name: "last_date".into(), + data_type: DataType::Timestamp(None, TimezoneInfo::None), + collation: None, + options: vec![], + }, + ColumnDef { + name: "last_user".into(), + data_type: DataType::Text, + collation: None, + options: vec![], + }, + ], + constraints: vec![], + hive_distribution: HiveDistributionStyle::NONE, + hive_formats: Some(HiveFormat { + row_format: None, + serde_properties: None, + storage: None, + location: None + }), + table_properties: vec![], + with_options: vec![], + file_format: None, + location: None, + query: None, + without_rowid: false, + like: None, + clone: None, + engine: None, + comment: None, + auto_increment_offset: None, + default_charset: None, + collation: None, + on_commit: None, + on_cluster: None, + primary_key: None, + order_by: None, + partition_by: None, + cluster_by: None, + options: None, + strict: false, + copy_grants: false, + enable_schema_evolution: None, + change_tracking: None, + data_retention_time_in_days: None, + max_data_extension_time_in_days: None, + default_ddl_collation: None, + with_aggregation_policy: None, + with_row_access_policy: None, + with_tags: None, + } + ); + + // Check the second statement + + dbg!(&create_function); + + assert_eq!( + create_function, + Statement::CreateFunction { + or_replace: false, + temporary: false, + if_not_exists: false, + name: ObjectName(vec![Ident::new("emp_stamp")]), + args: None, + return_type: Some(DataType::Trigger), + function_body: Some( + CreateFunctionBody::AsBeforeOptions( + 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(), + tag: Some( + "emp_stamp".to_owned(), + ), + }, + ), + ), + ), + ), + behavior: None, + called_on_null: None, + parallel: None, + using: None, + language: Some(Ident::new("plpgsql")), + determinism_specifier: None, + options: None, + remote_connection: None + } + ); + + // Check the third statement + + assert_eq!( + create_trigger, + Statement::CreateTrigger { + or_replace: false, + name: ObjectName(vec![Ident::new("emp_stamp")]), + period: TriggerPeriod::Before, + events: vec![TriggerEvent::Insert, TriggerEvent::Update(vec![])], + table_name: ObjectName(vec![Ident::new("emp")]), + referencing: vec![], + trigger_object: TriggerObject::Row, + include_each: true, + condition: None, + exec_body: TriggerExecBody { + exec_type: TriggerExecBodyType::Function, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("emp_stamp")]), + args: None, + } + }, + characteristics: None + } + ); + + // Check the fourth statement + assert_eq!( + drop_trigger, + Statement::DropTrigger { + if_exists: false, + trigger_name: ObjectName(vec![Ident::new("emp_stamp")]), + table_name: ObjectName(vec![Ident::new("emp")]), + option: None + } + ); +} From 430a1635d9ea0824b304fd496bb65271f1437c5f Mon Sep 17 00:00:00 2001 From: Luca Cappelletti Date: Sun, 28 Jul 2024 08:00:12 +0200 Subject: [PATCH 32/51] Update tests/sqlparser_postgres.rs Co-authored-by: Ifeanyi Ubah --- tests/sqlparser_postgres.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index e9597ca44..4a2526644 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4622,7 +4622,7 @@ fn parse_create_trigger() { "UPDATE OF balance, name OR INSERT OR DELETE", ), ] { - for when in [ + for period in [ TriggerPeriod::Before, TriggerPeriod::After, TriggerPeriod::InsteadOf, From 498aadaac44c7edc2518b497150884529493a502 Mon Sep 17 00:00:00 2001 From: Luca Cappelletti Date: Sun, 28 Jul 2024 08:07:27 +0200 Subject: [PATCH 33/51] Update src/parser/mod.rs Co-authored-by: Ifeanyi Ubah --- src/parser/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 1fbf5abad..14c915031 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4166,7 +4166,10 @@ impl<'a> Parser<'a> { /// DROP TRIGGER [ IF EXISTS ] name ON table_name [ CASCADE | RESTRICT ] /// ``` pub fn parse_drop_trigger(&mut self) -> Result { - if dialect_of!(self is PostgreSqlDialect) { + if !dialect_of!(self is PostgreSqlDialect | GenericDialect) { + return self.expected(...) + } + ... let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); let trigger_name = self.parse_object_name(false)?; self.expect_keyword(Keyword::ON)?; From ba5d47399067b6e4513befc661b48080844c9165 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sun, 28 Jul 2024 08:15:06 +0200 Subject: [PATCH 34/51] Finished refactoring dialect check to simplify code --- src/parser/mod.rs | 190 +++++++++++++++++++++++----------------------- 1 file changed, 93 insertions(+), 97 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 14c915031..d282937c6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4167,115 +4167,111 @@ impl<'a> Parser<'a> { /// ``` pub fn parse_drop_trigger(&mut self) -> Result { if !dialect_of!(self is PostgreSqlDialect | GenericDialect) { - return self.expected(...) - } - ... - let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); - let trigger_name = self.parse_object_name(false)?; - self.expect_keyword(Keyword::ON)?; - let table_name = self.parse_object_name(false)?; - let option = self - .parse_one_of_keywords(&[Keyword::CASCADE, Keyword::RESTRICT]) - .map(|keyword| match keyword { - Keyword::CASCADE => ReferentialAction::Cascade, - Keyword::RESTRICT => ReferentialAction::Restrict, - _ => unreachable!(), - }); - Ok(Statement::DropTrigger { - if_exists, - trigger_name, - table_name, - option, - }) - } else { self.prev_token(); - self.expected("an object type after CREATE", self.peek_token()) + return self.expected("an object type after DROP", self.peek_token()); } + let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); + let trigger_name = self.parse_object_name(false)?; + self.expect_keyword(Keyword::ON)?; + let table_name = self.parse_object_name(false)?; + let option = self + .parse_one_of_keywords(&[Keyword::CASCADE, Keyword::RESTRICT]) + .map(|keyword| match keyword { + Keyword::CASCADE => ReferentialAction::Cascade, + Keyword::RESTRICT => ReferentialAction::Restrict, + _ => unreachable!(), + }); + Ok(Statement::DropTrigger { + if_exists, + trigger_name, + table_name, + option, + }) } pub fn parse_create_trigger(&mut self, or_replace: bool) -> Result { - if dialect_of!(self is PostgreSqlDialect) { - let name = self.parse_object_name(false)?; - let period = self.parse_trigger_period()?; - - let events = self.parse_keyword_separated(Keyword::OR, Parser::parse_trigger_event)?; - let table_name = self - .expect_keyword(Keyword::ON) - .and_then(|_| self.parse_object_name(false))?; - - // [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ] - let mut deferrable: Option = None; - - if self.parse_keyword(Keyword::NOT) { - self.expect_keyword(Keyword::DEFERRABLE)?; - deferrable = Some(false); - } else if self.parse_keyword(Keyword::DEFERRABLE) { - deferrable = Some(true); - }; + if !dialect_of!(self is PostgreSqlDialect | GenericDialect) { + self.prev_token(); + return self.expected("an object type after CREATE", self.peek_token()); + } - let initially: Option = (deferrable.is_some() - && self.parse_keyword(Keyword::INITIALLY)) - .then(|| { - Ok::<_, ParserError>( - match self.expect_one_of_keywords(&[Keyword::IMMEDIATE, Keyword::DEFERRED])? { - Keyword::IMMEDIATE => DeferrableInitial::Immediate, - Keyword::DEFERRED => DeferrableInitial::Deferred, - _ => unreachable!(), - }, - ) - }) - .transpose()?; + let name = self.parse_object_name(false)?; + let period = self.parse_trigger_period()?; + + let events = self.parse_keyword_separated(Keyword::OR, Parser::parse_trigger_event)?; + let table_name = self + .expect_keyword(Keyword::ON) + .and_then(|_| self.parse_object_name(false))?; + + // [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ] + let mut deferrable: Option = None; + + if self.parse_keyword(Keyword::NOT) { + self.expect_keyword(Keyword::DEFERRABLE)?; + deferrable = Some(false); + } else if self.parse_keyword(Keyword::DEFERRABLE) { + deferrable = Some(true); + }; + + let initially: Option = (deferrable.is_some() + && self.parse_keyword(Keyword::INITIALLY)) + .then(|| { + Ok::<_, ParserError>( + match self.expect_one_of_keywords(&[Keyword::IMMEDIATE, Keyword::DEFERRED])? { + Keyword::IMMEDIATE => DeferrableInitial::Immediate, + Keyword::DEFERRED => DeferrableInitial::Deferred, + _ => unreachable!(), + }, + ) + }) + .transpose()?; - let mut referencing = vec![]; - if self.parse_keyword(Keyword::REFERENCING) { - while let Some(refer) = self.parse_trigger_referencing()? { - referencing.push(refer); - } + let mut referencing = vec![]; + if self.parse_keyword(Keyword::REFERENCING) { + while let Some(refer) = self.parse_trigger_referencing()? { + referencing.push(refer); } + } - let (trigger_object, include_each) = - self.expect_keyword(Keyword::FOR).and_then(|_| { - let include_each = self.parse_keyword(Keyword::EACH); - let trigger_object = - match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? { - Keyword::ROW => TriggerObject::Row, - Keyword::STATEMENT => TriggerObject::Statement, - _ => unreachable!(), - }; - Ok((trigger_object, include_each)) - })?; + let (trigger_object, include_each) = + self.expect_keyword(Keyword::FOR).and_then(|_| { + let include_each = self.parse_keyword(Keyword::EACH); + let trigger_object = + match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? { + Keyword::ROW => TriggerObject::Row, + Keyword::STATEMENT => TriggerObject::Statement, + _ => unreachable!(), + }; + Ok((trigger_object, include_each)) + })?; - let condition = self - .parse_keyword(Keyword::WHEN) - .then(|| self.parse_expr()) - .transpose()?; + let condition = self + .parse_keyword(Keyword::WHEN) + .then(|| self.parse_expr()) + .transpose()?; - let exec_body = self - .expect_keyword(Keyword::EXECUTE) - .and_then(|_| self.parse_trigger_exec_body())?; + let exec_body = self + .expect_keyword(Keyword::EXECUTE) + .and_then(|_| self.parse_trigger_exec_body())?; - Ok(Statement::CreateTrigger { - or_replace, - name, - period, - events, - table_name, - referencing, - trigger_object, - include_each, - condition, - exec_body, - characteristics: (deferrable.is_some() || initially.is_some()).then_some( - DeferrableCharacteristics { - deferrable, - initially, - }, - ), - }) - } else { - self.prev_token(); - self.expected("an object type after CREATE", self.peek_token()) - } + Ok(Statement::CreateTrigger { + or_replace, + name, + period, + events, + table_name, + referencing, + trigger_object, + include_each, + condition, + exec_body, + characteristics: (deferrable.is_some() || initially.is_some()).then_some( + DeferrableCharacteristics { + deferrable, + initially, + }, + ), + }) } pub fn parse_trigger_period(&mut self) -> Result { From a27cf339263acac401bbfa5879b39566584709a3 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sun, 28 Jul 2024 08:16:08 +0200 Subject: [PATCH 35/51] Finished test semplification changing `when` to `period` --- tests/sqlparser_postgres.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 4a2526644..aa3f695be 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4651,7 +4651,7 @@ fn parse_create_trigger() { let for_each = if include_each { "FOR EACH" } else { "FOR" }; let sql = &format!( - "CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{referencing_text}{for_each} {trigger_object} EXECUTE {exec_type} {func_desc}" + "CREATE TRIGGER check_update {period} {event_string} ON accounts {characteristics_text}{referencing_text}{for_each} {trigger_object} EXECUTE {exec_type} {func_desc}" ); assert_eq!( @@ -4659,7 +4659,7 @@ fn parse_create_trigger() { Statement::CreateTrigger { or_replace: false, name: ObjectName(vec![Ident::new("check_update")]), - period: when, + period, events: events.clone(), table_name: ObjectName(vec![Ident::new("accounts")]), referencing: referencing.clone(), @@ -4674,13 +4674,13 @@ fn parse_create_trigger() { } ); - let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{referencing_text}{for_each} {trigger_object} WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); + let sql = &format!("CREATE TRIGGER check_update {period} {event_string} ON accounts {characteristics_text}{referencing_text}{for_each} {trigger_object} WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); assert_eq!( pg().verified_stmt(sql), Statement::CreateTrigger { or_replace: false, name: ObjectName(vec![Ident::new("check_update")]), - period: when, + period, events: events.clone(), table_name: ObjectName(vec![Ident::new("accounts")]), referencing: referencing.clone(), @@ -4706,13 +4706,13 @@ fn parse_create_trigger() { } ); - let sql = &format!("CREATE TRIGGER check_update {when} {event_string} ON accounts {characteristics_text}{referencing_text}{for_each} {trigger_object} WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); + let sql = &format!("CREATE TRIGGER check_update {period} {event_string} ON accounts {characteristics_text}{referencing_text}{for_each} {trigger_object} WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); assert_eq!( pg().verified_stmt(sql), Statement::CreateTrigger { or_replace: false, name: ObjectName(vec![Ident::new("check_update")]), - period: when, + period, events: events.clone(), table_name: ObjectName(vec![Ident::new("accounts")]), referencing: referencing.clone(), From 3887809b291adb691bb189708283f3b50ba76aae Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sun, 28 Jul 2024 08:16:30 +0200 Subject: [PATCH 36/51] Formatted code --- src/parser/mod.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index d282937c6..79f46e9d5 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4233,17 +4233,16 @@ impl<'a> Parser<'a> { } } - let (trigger_object, include_each) = - self.expect_keyword(Keyword::FOR).and_then(|_| { - let include_each = self.parse_keyword(Keyword::EACH); - let trigger_object = - match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? { - Keyword::ROW => TriggerObject::Row, - Keyword::STATEMENT => TriggerObject::Statement, - _ => unreachable!(), - }; - Ok((trigger_object, include_each)) - })?; + let (trigger_object, include_each) = self.expect_keyword(Keyword::FOR).and_then(|_| { + let include_each = self.parse_keyword(Keyword::EACH); + let trigger_object = + match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? { + Keyword::ROW => TriggerObject::Row, + Keyword::STATEMENT => TriggerObject::Statement, + _ => unreachable!(), + }; + Ok((trigger_object, include_each)) + })?; let condition = self .parse_keyword(Keyword::WHEN) From 459c111abacf4b52f1459c907ec0ebf5499b060c Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sun, 28 Jul 2024 12:35:25 +0200 Subject: [PATCH 37/51] Removed part of the `and_then` to adequate to codebase style --- src/parser/mod.rs | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 79f46e9d5..95e1d3ca7 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4199,9 +4199,8 @@ impl<'a> Parser<'a> { let period = self.parse_trigger_period()?; let events = self.parse_keyword_separated(Keyword::OR, Parser::parse_trigger_event)?; - let table_name = self - .expect_keyword(Keyword::ON) - .and_then(|_| self.parse_object_name(false))?; + self.expect_keyword(Keyword::ON)?; + let table_name = self.parse_object_name(false)?; // [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ] let mut deferrable: Option = None; @@ -4233,25 +4232,24 @@ impl<'a> Parser<'a> { } } - let (trigger_object, include_each) = self.expect_keyword(Keyword::FOR).and_then(|_| { - let include_each = self.parse_keyword(Keyword::EACH); - let trigger_object = - match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? { - Keyword::ROW => TriggerObject::Row, - Keyword::STATEMENT => TriggerObject::Statement, - _ => unreachable!(), - }; - Ok((trigger_object, include_each)) - })?; + self.expect_keyword(Keyword::FOR)?; + let include_each = self.parse_keyword(Keyword::EACH); + let trigger_object = + match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? { + Keyword::ROW => TriggerObject::Row, + Keyword::STATEMENT => TriggerObject::Statement, + _ => unreachable!(), + }; let condition = self .parse_keyword(Keyword::WHEN) .then(|| self.parse_expr()) .transpose()?; - let exec_body = self - .expect_keyword(Keyword::EXECUTE) - .and_then(|_| self.parse_trigger_exec_body())?; + self + .expect_keyword(Keyword::EXECUTE)?; + + let exec_body =self.parse_trigger_exec_body()?; Ok(Statement::CreateTrigger { or_replace, From c043b583c091dc5d1b8bf0d4cff53cc399ae20d5 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sun, 28 Jul 2024 12:35:51 +0200 Subject: [PATCH 38/51] Formatted code --- src/parser/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 95e1d3ca7..6d5273d46 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4246,10 +4246,9 @@ impl<'a> Parser<'a> { .then(|| self.parse_expr()) .transpose()?; - self - .expect_keyword(Keyword::EXECUTE)?; + self.expect_keyword(Keyword::EXECUTE)?; - let exec_body =self.parse_trigger_exec_body()?; + let exec_body = self.parse_trigger_exec_body()?; Ok(Statement::CreateTrigger { or_replace, From 3fc268497f4bc1b3d1b4590b51e13b81d01df533 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sun, 28 Jul 2024 18:49:55 +0200 Subject: [PATCH 39/51] Removed more uses of and_then and then --- src/parser/mod.rs | 80 +++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6d5273d46..e5d0f266b 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4212,18 +4212,18 @@ impl<'a> Parser<'a> { deferrable = Some(true); }; - let initially: Option = (deferrable.is_some() - && self.parse_keyword(Keyword::INITIALLY)) - .then(|| { - Ok::<_, ParserError>( - match self.expect_one_of_keywords(&[Keyword::IMMEDIATE, Keyword::DEFERRED])? { - Keyword::IMMEDIATE => DeferrableInitial::Immediate, - Keyword::DEFERRED => DeferrableInitial::Deferred, - _ => unreachable!(), - }, - ) - }) - .transpose()?; + let initially: Option = + if deferrable.is_some() && self.parse_keyword(Keyword::INITIALLY) { + Some( + match self.expect_one_of_keywords(&[Keyword::IMMEDIATE, Keyword::DEFERRED])? { + Keyword::IMMEDIATE => DeferrableInitial::Immediate, + Keyword::DEFERRED => DeferrableInitial::Deferred, + _ => unreachable!(), + }, + ) + } else { + None + }; let mut referencing = vec![]; if self.parse_keyword(Keyword::REFERENCING) { @@ -4271,28 +4271,30 @@ impl<'a> Parser<'a> { } pub fn parse_trigger_period(&mut self) -> Result { - self.expect_one_of_keywords(&[Keyword::BEFORE, Keyword::AFTER, Keyword::INSTEAD]) - .and_then(|keyword| { - Ok(match keyword { - Keyword::BEFORE => TriggerPeriod::Before, - Keyword::AFTER => TriggerPeriod::After, - Keyword::INSTEAD => self - .expect_keyword(Keyword::OF) - .map(|_| TriggerPeriod::InsteadOf)?, - _ => unreachable!(), - }) - }) + Ok( + match self.expect_one_of_keywords(&[ + Keyword::BEFORE, + Keyword::AFTER, + Keyword::INSTEAD, + ])? { + Keyword::BEFORE => TriggerPeriod::Before, + Keyword::AFTER => TriggerPeriod::After, + Keyword::INSTEAD => self + .expect_keyword(Keyword::OF) + .map(|_| TriggerPeriod::InsteadOf)?, + _ => unreachable!(), + }, + ) } pub fn parse_trigger_event(&mut self) -> Result { - self.expect_one_of_keywords(&[ - Keyword::INSERT, - Keyword::UPDATE, - Keyword::DELETE, - Keyword::TRUNCATE, - ]) - .and_then(|keyword| { - Ok(match keyword { + Ok( + match self.expect_one_of_keywords(&[ + Keyword::INSERT, + Keyword::UPDATE, + Keyword::DELETE, + Keyword::TRUNCATE, + ])? { Keyword::INSERT => TriggerEvent::Insert, Keyword::UPDATE => { if self.parse_keyword(Keyword::OF) { @@ -4307,8 +4309,8 @@ impl<'a> Parser<'a> { Keyword::DELETE => TriggerEvent::Delete, Keyword::TRUNCATE => TriggerEvent::Truncate, _ => unreachable!(), - }) - }) + }, + ) } pub fn parse_trigger_referencing(&mut self) -> Result, ParserError> { @@ -4334,16 +4336,14 @@ impl<'a> Parser<'a> { } pub fn parse_trigger_exec_body(&mut self) -> Result { - let exec_type = self - .expect_one_of_keywords(&[Keyword::FUNCTION, Keyword::PROCEDURE]) - .map(|keyword| match keyword { + Ok(TriggerExecBody { + exec_type: match self + .expect_one_of_keywords(&[Keyword::FUNCTION, Keyword::PROCEDURE])? + { Keyword::FUNCTION => TriggerExecBodyType::Function, Keyword::PROCEDURE => TriggerExecBodyType::Procedure, _ => unreachable!(), - })?; - - Ok(TriggerExecBody { - exec_type, + }, func_desc: self.parse_function_desc()?, }) } From 09581e74360d56eec40b7b34cf1767e35b1af702 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sun, 28 Jul 2024 19:03:13 +0200 Subject: [PATCH 40/51] Restored and made use in triggers of original ConstraintCharacteristics --- src/ast/ddl.rs | 65 +++++++++++-------------------------- src/ast/mod.rs | 10 +++--- src/parser/mod.rs | 51 ++++++----------------------- tests/sqlparser_common.rs | 30 ++++++----------- tests/sqlparser_postgres.rs | 12 ++++--- 5 files changed, 52 insertions(+), 116 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 0a8bbc5d8..2933e1f38 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -1158,46 +1158,12 @@ fn display_option_spaced(option: &Option) -> impl fmt::Displ #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct ConstraintCharacteristics { - /// `[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]` - pub deferrable: DeferrableCharacteristics, - /// `[ ENFORCED | NOT ENFORCED ]` - pub enforced: Option, -} - -/// ` = [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]` -/// -/// Used in TRIGGER, UNIQUE and foreign key constraints. The individual settings may occur in any order. -#[derive(Debug, Copy, Clone, Default, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct DeferrableCharacteristics { /// `[ DEFERRABLE | NOT DEFERRABLE ]` pub deferrable: Option, /// `[ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]` pub initially: Option, -} - -impl fmt::Display for DeferrableCharacteristics { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(deferrable) = self.deferrable { - write!( - f, - "{}", - if deferrable { - "DEFERRABLE" - } else { - "NOT DEFERRABLE" - } - )?; - } - if let Some(initially) = self.initially { - if self.deferrable.is_some() { - f.write_str(" ")?; - } - write!(f, "{}", initially)?; - } - Ok(()) - } + /// `[ ENFORCED | NOT ENFORCED ]` + pub enforced: Option, } #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] @@ -1235,17 +1201,24 @@ impl ConstraintCharacteristics { impl fmt::Display for ConstraintCharacteristics { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.deferrable.deferrable.is_some() || self.deferrable.initially.is_some() { - write!( - f, - "{}{}", - self.deferrable, - display_option_spaced(&self.enforced_text()) - )?; - } else if let Some(enforced) = self.enforced_text() { - write!(f, "{}", enforced)?; + let mut parts = vec![]; + if let Some(deferrable) = self.deferrable { + parts.push( + if deferrable { + "DEFERRABLE" + } else { + "NOT DEFERRABLE" + } + .to_string(), + ); } - Ok(()) + if let Some(initially) = self.initially { + parts.push(initially.to_string()); + } + if let Some(enforced) = self.enforced_text() { + parts.push(enforced.to_string()); + } + write!(f, "{}", display_separated(&parts, " ")) } } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index dbe8ebcc6..dabe2a39c 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -33,10 +33,10 @@ pub use self::data_type::{ pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue}; pub use self::ddl::{ AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption, - ColumnOptionDef, ConstraintCharacteristics, DeferrableCharacteristics, DeferrableInitial, - GeneratedAs, GeneratedExpressionMode, IndexOption, IndexType, KeyOrIndexDisplay, Owner, - Partition, ProcedureParam, ReferentialAction, TableConstraint, - UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef, + ColumnOptionDef, ConstraintCharacteristics, DeferrableInitial, GeneratedAs, + GeneratedExpressionMode, IndexOption, IndexType, KeyOrIndexDisplay, Owner, Partition, + ProcedureParam, ReferentialAction, TableConstraint, UserDefinedTypeCompositeAttributeDef, + UserDefinedTypeRepresentation, ViewColumnDef, }; pub use self::dml::{CreateIndex, CreateTable, Delete, Insert}; pub use self::operator::{BinaryOperator, UnaryOperator}; @@ -2666,7 +2666,7 @@ pub enum Statement { /// Execute logic block exec_body: TriggerExecBody, /// The characteristic of the trigger, which include whether the trigger is `DEFERRABLE`, `INITIALLY DEFERRED`, or `INITIALLY IMMEDIATE`, - characteristics: Option, + characteristics: Option, }, /// DROP TRIGGER /// diff --git a/src/parser/mod.rs b/src/parser/mod.rs index e5d0f266b..3dfd9782f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4202,28 +4202,7 @@ impl<'a> Parser<'a> { self.expect_keyword(Keyword::ON)?; let table_name = self.parse_object_name(false)?; - // [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ] - let mut deferrable: Option = None; - - if self.parse_keyword(Keyword::NOT) { - self.expect_keyword(Keyword::DEFERRABLE)?; - deferrable = Some(false); - } else if self.parse_keyword(Keyword::DEFERRABLE) { - deferrable = Some(true); - }; - - let initially: Option = - if deferrable.is_some() && self.parse_keyword(Keyword::INITIALLY) { - Some( - match self.expect_one_of_keywords(&[Keyword::IMMEDIATE, Keyword::DEFERRED])? { - Keyword::IMMEDIATE => DeferrableInitial::Immediate, - Keyword::DEFERRED => DeferrableInitial::Deferred, - _ => unreachable!(), - }, - ) - } else { - None - }; + let characteristics = self.parse_constraint_characteristics()?; let mut referencing = vec![]; if self.parse_keyword(Keyword::REFERENCING) { @@ -4261,12 +4240,7 @@ impl<'a> Parser<'a> { include_each, condition, exec_body, - characteristics: (deferrable.is_some() || initially.is_some()).then_some( - DeferrableCharacteristics { - deferrable, - initially, - }, - ), + characteristics, }) } @@ -6175,18 +6149,16 @@ impl<'a> Parser<'a> { let mut cc = ConstraintCharacteristics::default(); loop { - if cc.deferrable.deferrable.is_none() - && self.parse_keywords(&[Keyword::NOT, Keyword::DEFERRABLE]) + if cc.deferrable.is_none() && self.parse_keywords(&[Keyword::NOT, Keyword::DEFERRABLE]) { - cc.deferrable.deferrable = Some(false); - } else if cc.deferrable.deferrable.is_none() && self.parse_keyword(Keyword::DEFERRABLE) - { - cc.deferrable.deferrable = Some(true); - } else if cc.deferrable.initially.is_none() && self.parse_keyword(Keyword::INITIALLY) { + cc.deferrable = Some(false); + } else if cc.deferrable.is_none() && self.parse_keyword(Keyword::DEFERRABLE) { + cc.deferrable = Some(true); + } else if cc.initially.is_none() && self.parse_keyword(Keyword::INITIALLY) { if self.parse_keyword(Keyword::DEFERRED) { - cc.deferrable.initially = Some(DeferrableInitial::Deferred); + cc.initially = Some(DeferrableInitial::Deferred); } else if self.parse_keyword(Keyword::IMMEDIATE) { - cc.deferrable.initially = Some(DeferrableInitial::Immediate); + cc.initially = Some(DeferrableInitial::Immediate); } else { self.expected("one of DEFERRED or IMMEDIATE", self.peek_token())?; } @@ -6201,10 +6173,7 @@ impl<'a> Parser<'a> { } } - if cc.deferrable.deferrable.is_some() - || cc.deferrable.initially.is_some() - || cc.enforced.is_some() - { + if cc.deferrable.is_some() || cc.initially.is_some() || cc.enforced.is_some() { Ok(Some(cc)) } else { Ok(None) diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 71270ecfc..dd3ed0515 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -3031,10 +3031,8 @@ fn parse_create_table_with_constraint_characteristics() { on_delete: Some(ReferentialAction::Restrict), on_update: None, characteristics: Some(ConstraintCharacteristics { - deferrable: DeferrableCharacteristics { - deferrable: Some(true), - initially: Some(DeferrableInitial::Deferred) - }, + deferrable: Some(true), + initially: Some(DeferrableInitial::Deferred), enforced: None }), }, @@ -3046,10 +3044,8 @@ fn parse_create_table_with_constraint_characteristics() { on_delete: Some(ReferentialAction::NoAction), on_update: Some(ReferentialAction::Restrict), characteristics: Some(ConstraintCharacteristics { - deferrable: DeferrableCharacteristics { - deferrable: Some(true), - initially: Some(DeferrableInitial::Immediate) - }, + deferrable: Some(true), + initially: Some(DeferrableInitial::Immediate), enforced: None, }), }, @@ -3061,10 +3057,8 @@ fn parse_create_table_with_constraint_characteristics() { on_delete: Some(ReferentialAction::Cascade), on_update: Some(ReferentialAction::SetDefault), characteristics: Some(ConstraintCharacteristics { - deferrable: DeferrableCharacteristics { - deferrable: Some(false), - initially: Some(DeferrableInitial::Deferred) - }, + deferrable: Some(false), + initially: Some(DeferrableInitial::Deferred), enforced: Some(false), }), }, @@ -3076,10 +3070,8 @@ fn parse_create_table_with_constraint_characteristics() { on_delete: None, on_update: Some(ReferentialAction::SetNull), characteristics: Some(ConstraintCharacteristics { - deferrable: DeferrableCharacteristics { - deferrable: Some(false), - initially: Some(DeferrableInitial::Immediate) - }, + deferrable: Some(false), + initially: Some(DeferrableInitial::Immediate), enforced: Some(true), }), }, @@ -3143,10 +3135,8 @@ fn parse_create_table_column_constraint_characteristics() { let expected_value = if deferrable.is_some() || initially.is_some() || enforced.is_some() { Some(ConstraintCharacteristics { - deferrable: DeferrableCharacteristics { - deferrable, - initially, - }, + deferrable, + initially, enforced, }) } else { diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index aa3f695be..9de0c2099 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4485,29 +4485,33 @@ fn parse_create_trigger() { for (characteristics, characteristics_text) in [ (None, ""), ( - Some(DeferrableCharacteristics { + Some(ConstraintCharacteristics { deferrable: Some(true), + enforced: None, initially: Some(DeferrableInitial::Deferred), }), "DEFERRABLE INITIALLY DEFERRED ", ), ( - Some(DeferrableCharacteristics { + Some(ConstraintCharacteristics { deferrable: Some(false), + enforced: None, initially: Some(DeferrableInitial::Immediate), }), "NOT DEFERRABLE INITIALLY IMMEDIATE ", ), ( - Some(DeferrableCharacteristics { + Some(ConstraintCharacteristics { deferrable: Some(true), + enforced: None, initially: Some(DeferrableInitial::Immediate), }), "DEFERRABLE INITIALLY IMMEDIATE ", ), ( - Some(DeferrableCharacteristics { + Some(ConstraintCharacteristics { deferrable: Some(true), + enforced: None, initially: Some(DeferrableInitial::Deferred), }), "DEFERRABLE INITIALLY DEFERRED ", From 058cf7faee2f74193804592446826e7b5f564ea3 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Tue, 30 Jul 2024 10:37:55 +0200 Subject: [PATCH 41/51] Reverted change relative to ConstraintCharacteristics --- src/ast/ddl.rs | 57 ++++++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 2933e1f38..8594142a1 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -1154,7 +1154,7 @@ fn display_option_spaced(option: &Option) -> impl fmt::Displ /// ` = [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] [ ENFORCED | NOT ENFORCED ]` /// /// Used in UNIQUE and foreign key constraints. The individual settings may occur in any order. -#[derive(Debug, Copy, Clone, Default, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Default, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct ConstraintCharacteristics { @@ -1176,16 +1176,25 @@ pub enum DeferrableInitial { Deferred, } -impl fmt::Display for DeferrableInitial { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match self { - DeferrableInitial::Immediate => "INITIALLY IMMEDIATE", - DeferrableInitial::Deferred => "INITIALLY DEFERRED", +impl ConstraintCharacteristics { + fn deferrable_text(&self) -> Option<&'static str> { + self.deferrable.map(|deferrable| { + if deferrable { + "DEFERRABLE" + } else { + "NOT DEFERRABLE" + } }) } -} -impl ConstraintCharacteristics { + fn initially_immediate_text(&self) -> Option<&'static str> { + self.initially + .map(|initially_immediate| match initially_immediate { + DeferrableInitial::Immediate => "INITIALLY IMMEDIATE", + DeferrableInitial::Deferred => "INITIALLY DEFERRED", + }) + } + fn enforced_text(&self) -> Option<&'static str> { self.enforced.map( |enforced| { @@ -1201,24 +1210,22 @@ impl ConstraintCharacteristics { impl fmt::Display for ConstraintCharacteristics { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut parts = vec![]; - if let Some(deferrable) = self.deferrable { - parts.push( - if deferrable { - "DEFERRABLE" - } else { - "NOT DEFERRABLE" - } - .to_string(), - ); - } - if let Some(initially) = self.initially { - parts.push(initially.to_string()); - } - if let Some(enforced) = self.enforced_text() { - parts.push(enforced.to_string()); + let deferrable = self.deferrable_text(); + let initially_immediate = self.initially_immediate_text(); + let enforced = self.enforced_text(); + + match (deferrable, initially_immediate, enforced) { + (None, None, None) => Ok(()), + (None, None, Some(enforced)) => write!(f, "{enforced}"), + (None, Some(initial), None) => write!(f, "{initial}"), + (None, Some(initial), Some(enforced)) => write!(f, "{initial} {enforced}"), + (Some(deferrable), None, None) => write!(f, "{deferrable}"), + (Some(deferrable), None, Some(enforced)) => write!(f, "{deferrable} {enforced}"), + (Some(deferrable), Some(initial), None) => write!(f, "{deferrable} {initial}"), + (Some(deferrable), Some(initial), Some(enforced)) => { + write!(f, "{deferrable} {initial} {enforced}") + } } - write!(f, "{}", display_separated(&parts, " ")) } } From 7ab8fdb05ba937b0e14c376866b5ac280e5b59c6 Mon Sep 17 00:00:00 2001 From: Luca Cappelletti Date: Tue, 30 Jul 2024 15:16:05 +0200 Subject: [PATCH 42/51] Update tests/sqlparser_postgres.rs Co-authored-by: Ifeanyi Ubah --- tests/sqlparser_postgres.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index d0c4fc00b..c28bd8482 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4995,7 +4995,6 @@ fn parse_trigger_related_functions() { // Check the second statement - dbg!(&create_function); assert_eq!( create_function, From 6c5487f0a97951dc9c345f025a1016a29232c655 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Tue, 30 Jul 2024 15:23:47 +0200 Subject: [PATCH 43/51] Added support for the CONSTRAINT keyword in triggers --- src/ast/mod.rs | 6 +++++- src/parser/mod.rs | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index b1dbf378c..d9d625e37 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2619,6 +2619,8 @@ pub enum Statement { /// EXECUTE FUNCTION trigger_function(); /// ``` or_replace: bool, + /// The `CONSTRAINT` keyword is used to create a trigger as a constraint. + is_constraint: bool, /// The name of the trigger to be created. name: ObjectName, /// Determines whether the function is called before, after, or instead of the event. @@ -3447,6 +3449,7 @@ impl fmt::Display for Statement { } Statement::CreateTrigger { or_replace, + is_constraint, name, period, events, @@ -3460,8 +3463,9 @@ impl fmt::Display for Statement { } => { write!( f, - "CREATE {or_replace}TRIGGER {name} {period}", + "CREATE {or_replace}{is_constraint}TRIGGER {name} {period}", or_replace = if *or_replace { "OR REPLACE " } else { "" }, + is_constraint = if *is_constraint { "CONSTRAINT " } else { "" }, )?; if !events.is_empty() { write!(f, " {}", display_separated(events, " OR "))?; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 5c378e572..b8404573f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4202,6 +4202,7 @@ impl<'a> Parser<'a> { return self.expected("an object type after CREATE", self.peek_token()); } + let is_constraint = self.parse_keyword(Keyword::CONSTRAINT); let name = self.parse_object_name(false)?; let period = self.parse_trigger_period()?; @@ -4238,6 +4239,7 @@ impl<'a> Parser<'a> { Ok(Statement::CreateTrigger { or_replace, + is_constraint, name, period, events, From c0aab06524dddc38087072d39688f2155ebecfa0 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Tue, 30 Jul 2024 15:52:10 +0200 Subject: [PATCH 44/51] Extended test suite and added support for CONSTRAINT keyword and FROM table name in CREATE TRIGGER --- Cargo.toml | 1 + src/ast/mod.rs | 9 + src/parser/mod.rs | 20 +- tests/sqlparser_postgres.rs | 608 ++++++++++++++++++++---------------- 4 files changed, 364 insertions(+), 274 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4c510a8c6..8c32001a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ sqlparser_derive = { version = "0.2.0", path = "derive", optional = true } simple_logger = "5.0" matches = "0.1" pretty_assertions = "1" +itertools = "0.13" [package.metadata.release] # Instruct `cargo release` to not run `cargo publish` locally: diff --git a/src/ast/mod.rs b/src/ast/mod.rs index d9d625e37..82c37ec25 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2656,6 +2656,9 @@ pub enum Statement { events: Vec, /// The table on which the trigger is to be created. table_name: ObjectName, + /// The optional referenced table name that can be referenced via + /// the `FROM` keyword. + referenced_table_name: Option, /// This keyword immediately precedes the declaration of one or two relation names that provide access to the transition relations of the triggering statement. referencing: Vec, /// This specifies whether the trigger function should be fired once for @@ -3454,6 +3457,7 @@ impl fmt::Display for Statement { period, events, table_name, + referenced_table_name, referencing, trigger_object, condition, @@ -3467,11 +3471,16 @@ impl fmt::Display for Statement { or_replace = if *or_replace { "OR REPLACE " } else { "" }, is_constraint = if *is_constraint { "CONSTRAINT " } else { "" }, )?; + if !events.is_empty() { write!(f, " {}", display_separated(events, " OR "))?; } write!(f, " ON {table_name}")?; + if let Some(referenced_table_name) = referenced_table_name { + write!(f, " FROM {referenced_table_name}")?; + } + if let Some(characteristics) = characteristics { write!(f, " {characteristics}")?; } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index b8404573f..758787926 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -3576,7 +3576,9 @@ impl<'a> Parser<'a> { } else if self.parse_keyword(Keyword::FUNCTION) { self.parse_create_function(or_replace, temporary) } else if self.parse_keyword(Keyword::TRIGGER) { - self.parse_create_trigger(or_replace) + self.parse_create_trigger(or_replace, false) + } else if self.parse_keywords(&[Keyword::CONSTRAINT, Keyword::TRIGGER]) { + self.parse_create_trigger(or_replace, true) } else if self.parse_keyword(Keyword::MACRO) { self.parse_create_macro(or_replace, temporary) } else if self.parse_keyword(Keyword::SECRET) { @@ -4195,14 +4197,17 @@ impl<'a> Parser<'a> { option, }) } - - pub fn parse_create_trigger(&mut self, or_replace: bool) -> Result { + + pub fn parse_create_trigger( + &mut self, + or_replace: bool, + is_constraint: bool, + ) -> Result { if !dialect_of!(self is PostgreSqlDialect | GenericDialect) { self.prev_token(); return self.expected("an object type after CREATE", self.peek_token()); } - let is_constraint = self.parse_keyword(Keyword::CONSTRAINT); let name = self.parse_object_name(false)?; let period = self.parse_trigger_period()?; @@ -4210,6 +4215,12 @@ impl<'a> Parser<'a> { self.expect_keyword(Keyword::ON)?; let table_name = self.parse_object_name(false)?; + let referenced_table_name = if self.parse_keyword(Keyword::FROM) { + self.parse_object_name(true).ok() + } else { + None + }; + let characteristics = self.parse_constraint_characteristics()?; let mut referencing = vec![]; @@ -4244,6 +4255,7 @@ impl<'a> Parser<'a> { period, events, table_name, + referenced_table_name, referencing, trigger_object, include_each, diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index c28bd8482..6728d712b 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -18,6 +18,7 @@ mod test_utils; use test_utils::*; +use itertools::Itertools; use sqlparser::ast::*; use sqlparser::dialect::{GenericDialect, PostgreSqlDialect}; use sqlparser::parser::ParserError; @@ -4442,9 +4443,8 @@ fn test_table_unnest_with_ordinality() { } } -#[test] -fn parse_create_trigger() { - for referencing in [ +fn possible_trigger_referencing_variants() -> Vec> { + vec![ vec![], vec![TriggerReferencing { refer_type: TriggerReferencingType::NewTable, @@ -4480,276 +4480,342 @@ fn parse_create_trigger() { transition_relation_name: ObjectName(vec![Ident::new("new_table")]), }, ], - ] { - for trigger_object in [TriggerObject::Row, TriggerObject::Statement] { - for (characteristics, characteristics_text) in [ - (None, ""), - ( - Some(ConstraintCharacteristics { - deferrable: Some(true), - enforced: None, - initially: Some(DeferrableInitial::Deferred), - }), - "DEFERRABLE INITIALLY DEFERRED ", - ), - ( - Some(ConstraintCharacteristics { - deferrable: Some(false), - enforced: None, - initially: Some(DeferrableInitial::Immediate), - }), - "NOT DEFERRABLE INITIALLY IMMEDIATE ", - ), - ( - Some(ConstraintCharacteristics { - deferrable: Some(true), - enforced: None, - initially: Some(DeferrableInitial::Immediate), - }), - "DEFERRABLE INITIALLY IMMEDIATE ", - ), + ] +} + +fn possible_trigger_object_variants() -> Vec { + vec![TriggerObject::Row, TriggerObject::Statement] +} + +fn possible_trigger_deferrable_variants() -> Vec> { + vec![ + None, + Some(ConstraintCharacteristics { + deferrable: Some(true), + enforced: None, + initially: Some(DeferrableInitial::Deferred), + }), + Some(ConstraintCharacteristics { + deferrable: Some(false), + enforced: None, + initially: Some(DeferrableInitial::Immediate), + }), + Some(ConstraintCharacteristics { + deferrable: Some(true), + enforced: None, + initially: Some(DeferrableInitial::Immediate), + }), + Some(ConstraintCharacteristics { + deferrable: Some(true), + enforced: None, + initially: Some(DeferrableInitial::Deferred), + }), + ] +} + +fn possible_trigger_function_descriptions() -> Vec { + vec![ + FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: None, + }, + FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: Some(vec![OperateFunctionArg::unnamed(DataType::Int(None))]), + }, + FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: Some(vec![ + OperateFunctionArg::with_name("a", DataType::Int(None)), + OperateFunctionArg { + mode: Some(ArgMode::In), + name: Some("b".into()), + data_type: DataType::Int(None), + default_expr: Some(Expr::Value(Value::Number("1".parse().unwrap(), false))), + }, + ]), + }, + ] +} + +fn possible_trigger_exec_body_types() -> Vec { + vec![ + TriggerExecBodyType::Function, + TriggerExecBodyType::Procedure, + ] +} + +fn possible_trigger_events() -> Vec> { + vec![ + vec![TriggerEvent::Update(vec![])], + vec![TriggerEvent::Insert], + vec![TriggerEvent::Delete], + vec![TriggerEvent::Update(vec![]), TriggerEvent::Insert], + vec![ + TriggerEvent::Update(vec![]), + TriggerEvent::Insert, + TriggerEvent::Delete, + ], + vec![TriggerEvent::Update(vec![Ident::new("balance")])], + vec![ + TriggerEvent::Update(vec![Ident::new("balance")]), + TriggerEvent::Insert, + ], + vec![ + TriggerEvent::Update(vec![Ident::new("balance")]), + TriggerEvent::Delete, + ], + vec![ + TriggerEvent::Update(vec![Ident::new("balance")]), + TriggerEvent::Insert, + TriggerEvent::Delete, + ], + ] +} + +fn possible_referencing_table_names() -> Vec> { + vec![ + // Case with no referencing table + None, + // Case with a referencing table + Some(ObjectName(vec![Ident::new("referencing_table")])), + // Case with a referencing table from a different schema + Some(ObjectName(vec![ + Ident::new("referencing_schema"), + Ident::new("referencing_table"), + ])), + ] +} + +fn possible_trigger_periods() -> Vec { + vec![ + TriggerPeriod::Before, + TriggerPeriod::After, + TriggerPeriod::InsteadOf, + ] +} + +fn possible_trigger_condition() -> Vec> { + vec![ + None, + Some(Expr::Nested(Box::new(Expr::IsNotDistinctFrom( + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("OLD"), + Ident::new("balance"), + ])), + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("NEW"), + Ident::new("balance"), + ])), + )))), + Some(Expr::Nested(Box::new(Expr::IsDistinctFrom( + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("OLD"), + Ident::new("balance"), + ])), + Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("NEW"), + Ident::new("balance"), + ])), + )))), + ] +} + +type PossibleTriggerConfiguration = ( + TriggerPeriod, + Vec, + Option, + Vec, + TriggerObject, + Option, + TriggerExecBodyType, + FunctionDesc, + Option, + bool, // OR REPLACE + bool, // EACH + bool, // CONSTRAINT +); + +fn iterate_trigger_configurations() -> impl Iterator { + possible_trigger_periods() // TriggerPeriod + .into_iter() + .cartesian_product(possible_trigger_events()) // Vec + .cartesian_product(possible_referencing_table_names()) // Option + .cartesian_product(possible_trigger_referencing_variants()) // Vec + .cartesian_product(possible_trigger_object_variants()) // TriggerObject + .cartesian_product(possible_trigger_deferrable_variants()) // Option + .cartesian_product(possible_trigger_exec_body_types()) // TriggerExecBodyType + .cartesian_product(possible_trigger_function_descriptions()) // FunctionDesc + .cartesian_product(possible_trigger_condition()) // Option + .cartesian_product([true, false]) // bool (OR REPLACE) + .cartesian_product([true, false]) // bool (EACH) + .cartesian_product([true, false]) // bool (CONSTRAINT) + .map( + |( ( - Some(ConstraintCharacteristics { - deferrable: Some(true), - enforced: None, - initially: Some(DeferrableInitial::Deferred), - }), - "DEFERRABLE INITIALLY DEFERRED ", - ), - ] { - for func_desc in [ - FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: None, - }, - FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: Some(vec![OperateFunctionArg::unnamed(DataType::Int(None))]), - }, - FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: Some(vec![ - OperateFunctionArg::with_name("a", DataType::Int(None)), - OperateFunctionArg { - mode: Some(ArgMode::In), - name: Some("b".into()), - data_type: DataType::Int(None), - default_expr: Some(Expr::Value(Value::Number( - "1".parse().unwrap(), - false, - ))), - }, - ]), - }, - ] { - for exec_type in [ - TriggerExecBodyType::Function, - TriggerExecBodyType::Procedure, - ] { - for (events, event_string) in [ - (vec![TriggerEvent::Update(vec![])], "UPDATE"), - (vec![TriggerEvent::Insert], "INSERT"), - (vec![TriggerEvent::Delete], "DELETE"), - ( - vec![TriggerEvent::Update(vec![]), TriggerEvent::Insert], - "UPDATE OR INSERT", - ), - ( - vec![ - TriggerEvent::Update(vec![]), - TriggerEvent::Insert, - TriggerEvent::Delete, - ], - "UPDATE OR INSERT OR DELETE", - ), - ( - vec![TriggerEvent::Update(vec![Ident::new("balance")])], - "UPDATE OF balance", - ), - ( - vec![ - TriggerEvent::Update(vec![Ident::new("balance")]), - TriggerEvent::Insert, - ], - "UPDATE OF balance OR INSERT", - ), - ( - vec![ - TriggerEvent::Update(vec![Ident::new("balance")]), - TriggerEvent::Insert, - TriggerEvent::Delete, - TriggerEvent::Update(vec![Ident::new("name")]), - ], - "UPDATE OF balance OR INSERT OR DELETE OR UPDATE OF name", - ), + ( + ( ( - vec![ - TriggerEvent::Update(vec![ - Ident::new("balance"), - Ident::new("name"), - Ident::new("name2"), - Ident::new("name3"), - ]), - TriggerEvent::Update(vec![Ident::new("name")]), - ], - "UPDATE OF balance, name, name2, name3 OR UPDATE OF name", - ), - ( - vec![ - TriggerEvent::Update(vec![ - Ident::new("balance"), - Ident::new("name"), - ]), - TriggerEvent::Insert, - ], - "UPDATE OF balance, name OR INSERT", - ), - ( - vec![ - TriggerEvent::Update(vec![ - Ident::new("balance"), - Ident::new("name"), - ]), - TriggerEvent::Delete, - ], - "UPDATE OF balance, name OR DELETE", - ), - ( - vec![ - TriggerEvent::Update(vec![ - Ident::new("balance"), - Ident::new("name"), - ]), - TriggerEvent::Insert, - TriggerEvent::Delete, - ], - "UPDATE OF balance, name OR INSERT OR DELETE", + ( + ( + ( + (((period, events), referencing), trigger_referencing), + trigger_object, + ), + deferrable, + ), + exec_type, + ), + func_desc, ), - ] { - for period in [ - TriggerPeriod::Before, - TriggerPeriod::After, - TriggerPeriod::InsteadOf, - ] { - for include_each in [true, false] { - let referencing_text = if referencing.is_empty() { - "".to_string() - } else { - format!( - "REFERENCING {} ", - referencing - .iter() - .map(|r| { - format!( - "{} {}{}", - r.refer_type, - if r.is_as { "AS " } else { "" }, - r.transition_relation_name - ) - }) - .collect::>() - .join(" ") - ) - }; - - let for_each = if include_each { "FOR EACH" } else { "FOR" }; - - let sql = &format!( - "CREATE TRIGGER check_update {period} {event_string} ON accounts {characteristics_text}{referencing_text}{for_each} {trigger_object} EXECUTE {exec_type} {func_desc}" - ); + condition, + ), + or_replace, + ), + include_each, + ), + is_constraint, + )| { + ( + period, + events, + referencing, + trigger_referencing, + trigger_object, + deferrable, + exec_type, + func_desc, + condition, + or_replace, + include_each, + is_constraint, + ) + }, + ) +} - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period, - events: events.clone(), - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: referencing.clone(), - trigger_object, - include_each, - condition: None, - exec_body: TriggerExecBody { - exec_type, - func_desc: func_desc.clone() - }, - characteristics - } - ); - - let sql = &format!("CREATE TRIGGER check_update {period} {event_string} ON accounts {characteristics_text}{referencing_text}{for_each} {trigger_object} WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period, - events: events.clone(), - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: referencing.clone(), - trigger_object, - include_each, - condition: Some(Expr::Nested(Box::new( - Expr::IsDistinctFrom( - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("OLD"), - Ident::new("balance") - ])), - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("NEW"), - Ident::new("balance") - ])), - ) - ))), - exec_body: TriggerExecBody { - exec_type, - func_desc: func_desc.clone() - }, - characteristics - } - ); - - let sql = &format!("CREATE TRIGGER check_update {period} {event_string} ON accounts {characteristics_text}{referencing_text}{for_each} {trigger_object} WHEN (OLD.balance IS NOT DISTINCT FROM NEW.balance) EXECUTE {exec_type} {func_desc}"); - assert_eq!( - pg().verified_stmt(sql), - Statement::CreateTrigger { - or_replace: false, - name: ObjectName(vec![Ident::new("check_update")]), - period, - events: events.clone(), - table_name: ObjectName(vec![Ident::new("accounts")]), - referencing: referencing.clone(), - trigger_object, - include_each, - condition: Some(Expr::Nested(Box::new( - Expr::IsNotDistinctFrom( - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("OLD"), - Ident::new("balance") - ])), - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("NEW"), - Ident::new("balance") - ])), - ) - ))), - exec_body: TriggerExecBody { - exec_type, - func_desc: func_desc.clone() - }, - characteristics - } - ); - } - } - } - } - } - } - } - } - +fn build_sql_from_trigger_configuration( + ( + period, + events, + referenced_table_name, + referencing, + trigger_object, + characteristics, + exec_type, + func_desc, + condition, + or_replace, + include_each, + is_constraint, + ): &PossibleTriggerConfiguration, +) -> String { + // First, we combine the possible events into a single string + // by joining them with ' OR '. + let events = events + .iter() + .map(|event| format!("{event}")) + .collect::>() + .join(" OR "); + + // When provided, we need to include the referenced table name in the statement, + // which is prefixed with the 'FROM' keyword. + let referenced_table_name = referenced_table_name + .as_ref() + .map(|referenced_table_name| format!("FROM {referenced_table_name} ")) + .unwrap_or_default(); + + let characteristics = characteristics.map(|c| format!("{c} ")).unwrap_or_default(); + + // Next, we combine the possible referencing clauses into a single string + // by joining them with spaces, if there are any, and we prepend them with + // the 'REFERENCING' keyword. + let referencing = (!referencing.is_empty()) + .then(|| { + format!( + "REFERENCING {referencing} ", + referencing = referencing + .iter() + .map(|reference| format!("{reference}",)) + .join(" ") + ) + }) + .unwrap_or_default(); + + // In the case where a condition is provided, we prepend it with the 'WHEN' keyword. + let condition = condition + .as_ref() + .map(|c| format!("WHEN {c} ")) + .unwrap_or_default(); + + // When requested, we prepend the 'OR REPLACE' keyword to the statement. + let or_replace = if *or_replace { " OR REPLACE" } else { "" }; + // Similarly, when requested, we prepend the 'FOR EACH' keyword to the statement. + let for_each = if *include_each { "FOR EACH" } else { "FOR" }; + // And, when requested, we prepend the 'CONSTRAINT' keyword to the statement. + let is_constraint = if *is_constraint { " CONSTRAINT" } else { "" }; + + // Finally, we combine all the parts into a single string, + // following the syntax of a postgres CREATE TRIGGER statement: + // + // CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER name { BEFORE | AFTER | INSTEAD OF } { event [ OR ... ] } + // ON table_name + // [ FROM referenced_table_name ] + // [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ] + // [ REFERENCING { { OLD | NEW } TABLE [ AS ] transition_relation_name } [ ... ] ] + // [ FOR [ EACH ] { ROW | STATEMENT } ] + // [ WHEN ( condition ) ] + // EXECUTE { FUNCTION | PROCEDURE } function_name ( arguments ) + // + format!( + "CREATE{or_replace}{is_constraint} TRIGGER check_update {period} {events} ON accounts {referenced_table_name}{characteristics}{referencing}{for_each} {trigger_object} {condition}EXECUTE {exec_type} {func_desc}" + ) +} + +fn build_trigger_from_configuration( + ( + period, + events, + referenced_table_name, + referencing, + trigger_object, + characteristics, + exec_type, + func_desc, + condition, + or_replace, + include_each, + is_constraint, + ): PossibleTriggerConfiguration, +) -> Statement { + Statement::CreateTrigger { + or_replace, + is_constraint, + name: ObjectName(vec![Ident::new("check_update")]), + period, + events, + table_name: ObjectName(vec![Ident::new("accounts")]), + referenced_table_name, + referencing, + trigger_object, + include_each, + condition, + exec_body: TriggerExecBody { + exec_type, + func_desc, + }, + characteristics, + } +} + +#[test] +fn parse_create_trigger() { + for configuration in iterate_trigger_configurations() { + let sql = build_sql_from_trigger_configuration(&configuration); + let trigger = build_trigger_from_configuration(configuration); + + assert_eq!(pg().verified_stmt(&sql), trigger); + } } #[test] @@ -5036,10 +5102,12 @@ fn parse_trigger_related_functions() { create_trigger, Statement::CreateTrigger { or_replace: false, + is_constraint: false, name: ObjectName(vec![Ident::new("emp_stamp")]), period: TriggerPeriod::Before, events: vec![TriggerEvent::Insert, TriggerEvent::Update(vec![])], table_name: ObjectName(vec![Ident::new("emp")]), + referenced_table_name: None, referencing: vec![], trigger_object: TriggerObject::Row, include_each: true, @@ -5067,7 +5135,7 @@ fn parse_trigger_related_functions() { ); } -#[test] +#[test] fn test_unicode_string_literal() { let pairs = [ // Example from the postgres docs From c198e24001bdac01c06c5b5352cbf66ce116e58a Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Tue, 30 Jul 2024 15:53:01 +0200 Subject: [PATCH 45/51] Formatted code --- src/parser/mod.rs | 2 +- tests/sqlparser_postgres.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 758787926..055a7e5db 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4197,7 +4197,7 @@ impl<'a> Parser<'a> { option, }) } - + pub fn parse_create_trigger( &mut self, or_replace: bool, diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 6728d712b..cb80aa7ce 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -5061,7 +5061,6 @@ fn parse_trigger_related_functions() { // Check the second statement - assert_eq!( create_function, Statement::CreateFunction { From d8db70e0c39a512c2e8e725d520e7a94e5ace299 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Tue, 30 Jul 2024 17:08:12 +0200 Subject: [PATCH 46/51] Removed itertools dependency and tried to clean up code as much as possible --- Cargo.toml | 1 - src/parser/mod.rs | 38 +++---- tests/sqlparser_postgres.rs | 83 +++++---------- tests/test_utils/extend_tuple.rs | 174 +++++++++++++++++++++++++++++++ tests/test_utils/mod.rs | 3 + 5 files changed, 224 insertions(+), 75 deletions(-) create mode 100644 tests/test_utils/extend_tuple.rs diff --git a/Cargo.toml b/Cargo.toml index 8c32001a7..4c510a8c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,6 @@ sqlparser_derive = { version = "0.2.0", path = "derive", optional = true } simple_logger = "5.0" matches = "0.1" pretty_assertions = "1" -itertools = "0.13" [package.metadata.release] # Instruct `cargo release` to not run `cargo publish` locally: diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 055a7e5db..56ba60463 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -2026,25 +2026,6 @@ impl<'a> Parser<'a> { }) } - /// Parse a keyword-separated list of 1+ items accepted by `F` - pub fn parse_keyword_separated( - &mut self, - keyword: Keyword, - mut f: F, - ) -> Result, ParserError> - where - F: FnMut(&mut Parser<'a>) -> Result, - { - let mut values = vec![]; - loop { - values.push(f(self)?); - if !self.parse_keyword(keyword) { - break; - } - } - Ok(values) - } - /// Parse an `INTERVAL` expression. /// /// Some syntactically valid intervals: @@ -3474,6 +3455,25 @@ impl<'a> Parser<'a> { Ok(values) } + /// Parse a keyword-separated list of 1+ items accepted by `F` + pub fn parse_keyword_separated( + &mut self, + keyword: Keyword, + mut f: F, + ) -> Result, ParserError> + where + F: FnMut(&mut Parser<'a>) -> Result, + { + let mut values = vec![]; + loop { + values.push(f(self)?); + if !self.parse_keyword(keyword) { + break; + } + } + Ok(values) + } + pub fn parse_parenthesized(&mut self, mut f: F) -> Result where F: FnMut(&mut Parser<'a>) -> Result, diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index cb80aa7ce..1671c560b 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -18,7 +18,6 @@ mod test_utils; use test_utils::*; -use itertools::Itertools; use sqlparser::ast::*; use sqlparser::dialect::{GenericDialect, PostgreSqlDialect}; use sqlparser::parser::ParserError; @@ -4636,62 +4635,35 @@ type PossibleTriggerConfiguration = ( bool, // CONSTRAINT ); +fn cartesian(tuple: T, generator: F) -> impl Iterator>::Output> +where + F: Fn() -> Vec, + V: Clone + ExtendTuple, + T: Clone, +{ + generator() + .into_iter() + .map(move |v| v.extend_tuple(tuple.clone())) +} + fn iterate_trigger_configurations() -> impl Iterator { - possible_trigger_periods() // TriggerPeriod + possible_trigger_periods() .into_iter() - .cartesian_product(possible_trigger_events()) // Vec - .cartesian_product(possible_referencing_table_names()) // Option - .cartesian_product(possible_trigger_referencing_variants()) // Vec - .cartesian_product(possible_trigger_object_variants()) // TriggerObject - .cartesian_product(possible_trigger_deferrable_variants()) // Option - .cartesian_product(possible_trigger_exec_body_types()) // TriggerExecBodyType - .cartesian_product(possible_trigger_function_descriptions()) // FunctionDesc - .cartesian_product(possible_trigger_condition()) // Option - .cartesian_product([true, false]) // bool (OR REPLACE) - .cartesian_product([true, false]) // bool (EACH) - .cartesian_product([true, false]) // bool (CONSTRAINT) - .map( - |( - ( - ( - ( - ( - ( - ( - ( - (((period, events), referencing), trigger_referencing), - trigger_object, - ), - deferrable, - ), - exec_type, - ), - func_desc, - ), - condition, - ), - or_replace, - ), - include_each, - ), - is_constraint, - )| { - ( - period, - events, - referencing, - trigger_referencing, - trigger_object, - deferrable, - exec_type, - func_desc, - condition, - or_replace, - include_each, - is_constraint, - ) - }, - ) + .map(|period| (period,)) + .flat_map(|period| cartesian(period, possible_trigger_events)) + .flat_map(|prev| cartesian(prev, possible_referencing_table_names)) + .flat_map(|prev| cartesian(prev, possible_trigger_referencing_variants)) + .flat_map(|prev| cartesian(prev, possible_trigger_object_variants)) + .flat_map(|prev| cartesian(prev, possible_trigger_deferrable_variants)) + .flat_map(|prev| cartesian(prev, possible_trigger_exec_body_types)) + .flat_map(|prev| cartesian(prev, possible_trigger_function_descriptions)) + .flat_map(|prev| cartesian(prev, possible_trigger_condition)) + // We exclude the case where the trigger is a constraint and the trigger is replaced + .flat_map(|prev| cartesian(prev, || vec![true, false])) + // We exclude the case where the trigger is a constraint and the trigger is replaced + .flat_map(|prev| cartesian(prev, || vec![true, false])) + // We exclude the case where the trigger is a constraint and the trigger is replaced + .flat_map(|prev| cartesian(prev, || vec![true, false])) } fn build_sql_from_trigger_configuration( @@ -4737,6 +4709,7 @@ fn build_sql_from_trigger_configuration( referencing = referencing .iter() .map(|reference| format!("{reference}",)) + .collect::>() .join(" ") ) }) diff --git a/tests/test_utils/extend_tuple.rs b/tests/test_utils/extend_tuple.rs new file mode 100644 index 000000000..352ed9b47 --- /dev/null +++ b/tests/test_utils/extend_tuple.rs @@ -0,0 +1,174 @@ +//! Submodule providing the extend tuple trait. + +/// Trait for extending a tuple with a new element. +pub trait ExtendTuple { + /// The output type after extending the tuple. + type Output; + + /// Extend a tuple with a new element. + fn extend_tuple(self, to_extend: ToExtend) -> Self::Output; +} + +impl ExtendTuple<(T1,)> for T2 { + type Output = (T1, T2); + + fn extend_tuple(self, to_extend: (T1,)) -> Self::Output { + (to_extend.0, self) + } +} + +impl ExtendTuple<(T1, T2)> for T3 { + type Output = (T1, T2, T3); + + fn extend_tuple(self, to_extend: (T1, T2)) -> Self::Output { + (to_extend.0, to_extend.1, self) + } +} + +impl ExtendTuple<(T1, T2, T3)> for T4 { + type Output = (T1, T2, T3, T4); + + fn extend_tuple(self, to_extend: (T1, T2, T3)) -> Self::Output { + (to_extend.0, to_extend.1, to_extend.2, self) + } +} + +impl ExtendTuple<(T1, T2, T3, T4)> for T5 { + type Output = (T1, T2, T3, T4, T5); + + fn extend_tuple(self, to_extend: (T1, T2, T3, T4)) -> Self::Output { + (to_extend.0, to_extend.1, to_extend.2, to_extend.3, self) + } +} + +impl ExtendTuple<(T1, T2, T3, T4, T5)> for T6 { + type Output = (T1, T2, T3, T4, T5, T6); + + fn extend_tuple(self, to_extend: (T1, T2, T3, T4, T5)) -> Self::Output { + ( + to_extend.0, + to_extend.1, + to_extend.2, + to_extend.3, + to_extend.4, + self, + ) + } +} + +impl ExtendTuple<(T1, T2, T3, T4, T5, T6)> for T7 { + type Output = (T1, T2, T3, T4, T5, T6, T7); + + fn extend_tuple(self, to_extend: (T1, T2, T3, T4, T5, T6)) -> Self::Output { + ( + to_extend.0, + to_extend.1, + to_extend.2, + to_extend.3, + to_extend.4, + to_extend.5, + self, + ) + } +} + +impl ExtendTuple<(T1, T2, T3, T4, T5, T6, T7)> for T8 { + type Output = (T1, T2, T3, T4, T5, T6, T7, T8); + + fn extend_tuple(self, to_extend: (T1, T2, T3, T4, T5, T6, T7)) -> Self::Output { + ( + to_extend.0, + to_extend.1, + to_extend.2, + to_extend.3, + to_extend.4, + to_extend.5, + to_extend.6, + self, + ) + } +} + +impl ExtendTuple<(T1, T2, T3, T4, T5, T6, T7, T8)> for T9 { + type Output = (T1, T2, T3, T4, T5, T6, T7, T8, T9); + + fn extend_tuple(self, to_extend: (T1, T2, T3, T4, T5, T6, T7, T8)) -> Self::Output { + ( + to_extend.0, + to_extend.1, + to_extend.2, + to_extend.3, + to_extend.4, + to_extend.5, + to_extend.6, + to_extend.7, + self, + ) + } +} + +impl ExtendTuple<(T1, T2, T3, T4, T5, T6, T7, T8, T9)> + for T10 +{ + type Output = (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10); + + fn extend_tuple(self, to_extend: (T1, T2, T3, T4, T5, T6, T7, T8, T9)) -> Self::Output { + ( + to_extend.0, + to_extend.1, + to_extend.2, + to_extend.3, + to_extend.4, + to_extend.5, + to_extend.6, + to_extend.7, + to_extend.8, + self, + ) + } +} + +impl + ExtendTuple<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)> for T11 +{ + type Output = (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11); + + fn extend_tuple(self, to_extend: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)) -> Self::Output { + ( + to_extend.0, + to_extend.1, + to_extend.2, + to_extend.3, + to_extend.4, + to_extend.5, + to_extend.6, + to_extend.7, + to_extend.8, + to_extend.9, + self, + ) + } +} + +impl + ExtendTuple<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11)> for T12 +{ + type Output = (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12); + + fn extend_tuple(self, to_extend: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11)) -> Self::Output { + ( + to_extend.0, + to_extend.1, + to_extend.2, + to_extend.3, + to_extend.4, + to_extend.5, + to_extend.6, + to_extend.7, + to_extend.8, + to_extend.9, + to_extend.10, + self, + ) + } +} diff --git a/tests/test_utils/mod.rs b/tests/test_utils/mod.rs index d3a56ce46..cabc934b4 100644 --- a/tests/test_utils/mod.rs +++ b/tests/test_utils/mod.rs @@ -32,3 +32,6 @@ macro_rules! nest { }), alias: None} }; } + +mod extend_tuple; +pub use extend_tuple::ExtendTuple; \ No newline at end of file From 0f81194b4ff924ae3800322d9a484db5e34ccd61 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Tue, 30 Jul 2024 17:08:47 +0200 Subject: [PATCH 47/51] Reformatted code --- tests/test_utils/extend_tuple.rs | 5 ++++- tests/test_utils/mod.rs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_utils/extend_tuple.rs b/tests/test_utils/extend_tuple.rs index 352ed9b47..86cdfc62a 100644 --- a/tests/test_utils/extend_tuple.rs +++ b/tests/test_utils/extend_tuple.rs @@ -155,7 +155,10 @@ impl { type Output = (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12); - fn extend_tuple(self, to_extend: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11)) -> Self::Output { + fn extend_tuple( + self, + to_extend: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11), + ) -> Self::Output { ( to_extend.0, to_extend.1, diff --git a/tests/test_utils/mod.rs b/tests/test_utils/mod.rs index cabc934b4..676b1eba3 100644 --- a/tests/test_utils/mod.rs +++ b/tests/test_utils/mod.rs @@ -34,4 +34,4 @@ macro_rules! nest { } mod extend_tuple; -pub use extend_tuple::ExtendTuple; \ No newline at end of file +pub use extend_tuple::ExtendTuple; From 169a0be1d12bdf56752490d7d6859eaf426f8099 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Tue, 30 Jul 2024 17:15:44 +0200 Subject: [PATCH 48/51] Fixed comments --- tests/sqlparser_postgres.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 1671c560b..29934fcab 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4658,11 +4658,11 @@ fn iterate_trigger_configurations() -> impl Iterator Date: Tue, 13 Aug 2024 15:37:27 +0200 Subject: [PATCH 49/51] Replaced complete test with partial tests --- tests/sqlparser_postgres.rs | 304 ++++++++++++++----------------- tests/test_utils/extend_tuple.rs | 177 ------------------ tests/test_utils/mod.rs | 3 - 3 files changed, 140 insertions(+), 344 deletions(-) delete mode 100644 tests/test_utils/extend_tuple.rs diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 29934fcab..12b85e0c7 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4620,185 +4620,161 @@ fn possible_trigger_condition() -> Vec> { ] } -type PossibleTriggerConfiguration = ( - TriggerPeriod, - Vec, - Option, - Vec, - TriggerObject, - Option, - TriggerExecBodyType, - FunctionDesc, - Option, - bool, // OR REPLACE - bool, // EACH - bool, // CONSTRAINT -); - -fn cartesian(tuple: T, generator: F) -> impl Iterator>::Output> -where - F: Fn() -> Vec, - V: Clone + ExtendTuple, - T: Clone, -{ - generator() - .into_iter() - .map(move |v| v.extend_tuple(tuple.clone())) -} - -fn iterate_trigger_configurations() -> impl Iterator { - possible_trigger_periods() - .into_iter() - .map(|period| (period,)) - .flat_map(|period| cartesian(period, possible_trigger_events)) - .flat_map(|prev| cartesian(prev, possible_referencing_table_names)) - .flat_map(|prev| cartesian(prev, possible_trigger_referencing_variants)) - .flat_map(|prev| cartesian(prev, possible_trigger_object_variants)) - .flat_map(|prev| cartesian(prev, possible_trigger_deferrable_variants)) - .flat_map(|prev| cartesian(prev, possible_trigger_exec_body_types)) - .flat_map(|prev| cartesian(prev, possible_trigger_function_descriptions)) - .flat_map(|prev| cartesian(prev, possible_trigger_condition)) - // Whether the trigger should be replaced - .flat_map(|prev| cartesian(prev, || vec![true, false])) - // Whether the 'EACH' keyword should be included - .flat_map(|prev| cartesian(prev, || vec![true, false])) - // Whether the trigger should be a constraint - .flat_map(|prev| cartesian(prev, || vec![true, false])) +#[test] +fn test_escaped_string_literal() { + match pg().verified_expr(r#"E'\n'"#) { + Expr::Value(Value::EscapedStringLiteral(s)) => { + assert_eq!("\n", s); + } + _ => unreachable!(), + } } -fn build_sql_from_trigger_configuration( - ( - period, - events, - referenced_table_name, - referencing, - trigger_object, - characteristics, - exec_type, - func_desc, - condition, - or_replace, - include_each, - is_constraint, - ): &PossibleTriggerConfiguration, -) -> String { - // First, we combine the possible events into a single string - // by joining them with ' OR '. - let events = events - .iter() - .map(|event| format!("{event}")) - .collect::>() - .join(" OR "); - - // When provided, we need to include the referenced table name in the statement, - // which is prefixed with the 'FROM' keyword. - let referenced_table_name = referenced_table_name - .as_ref() - .map(|referenced_table_name| format!("FROM {referenced_table_name} ")) - .unwrap_or_default(); - - let characteristics = characteristics.map(|c| format!("{c} ")).unwrap_or_default(); - - // Next, we combine the possible referencing clauses into a single string - // by joining them with spaces, if there are any, and we prepend them with - // the 'REFERENCING' keyword. - let referencing = (!referencing.is_empty()) - .then(|| { - format!( - "REFERENCING {referencing} ", - referencing = referencing - .iter() - .map(|reference| format!("{reference}",)) - .collect::>() - .join(" ") - ) - }) - .unwrap_or_default(); - - // In the case where a condition is provided, we prepend it with the 'WHEN' keyword. - let condition = condition - .as_ref() - .map(|c| format!("WHEN {c} ")) - .unwrap_or_default(); - - // When requested, we prepend the 'OR REPLACE' keyword to the statement. - let or_replace = if *or_replace { " OR REPLACE" } else { "" }; - // Similarly, when requested, we prepend the 'FOR EACH' keyword to the statement. - let for_each = if *include_each { "FOR EACH" } else { "FOR" }; - // And, when requested, we prepend the 'CONSTRAINT' keyword to the statement. - let is_constraint = if *is_constraint { " CONSTRAINT" } else { "" }; - - // Finally, we combine all the parts into a single string, - // following the syntax of a postgres CREATE TRIGGER statement: - // - // CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER name { BEFORE | AFTER | INSTEAD OF } { event [ OR ... ] } - // ON table_name - // [ FROM referenced_table_name ] - // [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ] - // [ REFERENCING { { OLD | NEW } TABLE [ AS ] transition_relation_name } [ ... ] ] - // [ FOR [ EACH ] { ROW | STATEMENT } ] - // [ WHEN ( condition ) ] - // EXECUTE { FUNCTION | PROCEDURE } function_name ( arguments ) - // - format!( - "CREATE{or_replace}{is_constraint} TRIGGER check_update {period} {events} ON accounts {referenced_table_name}{characteristics}{referencing}{for_each} {trigger_object} {condition}EXECUTE {exec_type} {func_desc}" - ) +#[test] +fn parse_create_simple_before_insert_trigger() { + let sql = "CREATE TRIGGER check_insert BEFORE INSERT ON accounts FOR EACH ROW EXECUTE FUNCTION check_account_insert"; + let expected = Statement::CreateTrigger { + or_replace: false, + is_constraint: false, + name: ObjectName(vec![Ident::new("check_insert")]), + period: TriggerPeriod::Before, + events: vec![TriggerEvent::Insert], + table_name: ObjectName(vec![Ident::new("accounts")]), + referenced_table_name: None, + referencing: vec![], + trigger_object: TriggerObject::Row, + include_each: true, + condition: None, + exec_body: TriggerExecBody { + exec_type: TriggerExecBodyType::Function, + func_desc: FunctionDesc{name: ObjectName(vec![Ident::new("check_account_insert")]), args: None}, + }, + characteristics: None, + }; + + assert_eq!(pg().verified_stmt(sql), expected); } -fn build_trigger_from_configuration( - ( - period, - events, - referenced_table_name, - referencing, - trigger_object, - characteristics, - exec_type, - func_desc, - condition, - or_replace, - include_each, - is_constraint, - ): PossibleTriggerConfiguration, -) -> Statement { - Statement::CreateTrigger { - or_replace, - is_constraint, +#[test] +fn parse_create_after_update_trigger_with_condition() { + let sql = "CREATE TRIGGER check_update AFTER UPDATE ON accounts FOR EACH ROW WHEN (NEW.balance > 10000) EXECUTE FUNCTION check_account_update"; + let expected = Statement::CreateTrigger { + or_replace: false, + is_constraint: false, name: ObjectName(vec![Ident::new("check_update")]), - period, - events, + period: TriggerPeriod::After, + events: vec![TriggerEvent::Update(vec![])], table_name: ObjectName(vec![Ident::new("accounts")]), - referenced_table_name, - referencing, - trigger_object, - include_each, - condition, + referenced_table_name: None, + referencing: vec![], + trigger_object: TriggerObject::Row, + include_each: true, + condition: Some(Expr::Nested(Box::new(Expr::BinaryOp { + left: Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("NEW"), + Ident::new("balance"), + ])), + op: BinaryOperator::Gt, + right: Box::new(Expr::Value(Value::Number("10000".to_string(), false))), + }))), exec_body: TriggerExecBody { - exec_type, - func_desc, + exec_type: TriggerExecBodyType::Function, + func_desc: FunctionDesc{name: ObjectName(vec![Ident::new("check_account_update")]), args: None}, }, - characteristics, - } + characteristics: None, + }; + + assert_eq!(pg().verified_stmt(sql), expected); } #[test] -fn parse_create_trigger() { - for configuration in iterate_trigger_configurations() { - let sql = build_sql_from_trigger_configuration(&configuration); - let trigger = build_trigger_from_configuration(configuration); +fn parse_create_instead_of_delete_trigger() { + let sql = "CREATE TRIGGER check_delete INSTEAD OF DELETE ON accounts FOR EACH ROW EXECUTE FUNCTION check_account_deletes"; + let expected = Statement::CreateTrigger { + or_replace: false, + is_constraint: false, + name: ObjectName(vec![Ident::new("check_delete")]), + period: TriggerPeriod::InsteadOf, + events: vec![TriggerEvent::Delete], + table_name: ObjectName(vec![Ident::new("accounts")]), + referenced_table_name: None, + referencing: vec![], + trigger_object: TriggerObject::Row, + include_each: true, + condition: None, + exec_body: TriggerExecBody { + exec_type: TriggerExecBodyType::Function, + func_desc: FunctionDesc{name: ObjectName(vec![Ident::new("check_account_deletes")]), args: None}, + }, + characteristics: None, + }; + + assert_eq!(pg().verified_stmt(sql), expected); +} - assert_eq!(pg().verified_stmt(&sql), trigger); - } +#[test] +fn parse_create_trigger_with_multiple_events_and_deferrable() { + let sql = "CREATE CONSTRAINT TRIGGER check_multiple_events BEFORE INSERT OR UPDATE OR DELETE ON accounts DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION check_account_changes"; + let expected = Statement::CreateTrigger { + or_replace: false, + is_constraint: true, + name: ObjectName(vec![Ident::new("check_multiple_events")]), + period: TriggerPeriod::Before, + events: vec![TriggerEvent::Insert, TriggerEvent::Update(vec![]), TriggerEvent::Delete], + table_name: ObjectName(vec![Ident::new("accounts")]), + referenced_table_name: None, + referencing: vec![], + trigger_object: TriggerObject::Row, + include_each: true, + condition: None, + exec_body: TriggerExecBody { + exec_type: TriggerExecBodyType::Function, + func_desc: FunctionDesc{name: ObjectName(vec![Ident::new("check_account_changes")]), args: None}, + }, + characteristics: Some(ConstraintCharacteristics { + deferrable: Some(true), + initially: Some(DeferrableInitial::Deferred), + enforced: None, + }), + }; + + assert_eq!(pg().verified_stmt(sql), expected); } #[test] -fn test_escaped_string_literal() { - match pg().verified_expr(r#"E'\n'"#) { - Expr::Value(Value::EscapedStringLiteral(s)) => { - assert_eq!("\n", s); - } - _ => unreachable!(), - } +fn parse_create_trigger_with_referencing() { + let sql = "CREATE TRIGGER check_referencing BEFORE INSERT ON accounts REFERENCING NEW TABLE AS new_accounts OLD TABLE AS old_accounts FOR EACH ROW EXECUTE FUNCTION check_account_referencing"; + let expected = Statement::CreateTrigger { + or_replace: false, + is_constraint: false, + name: ObjectName(vec![Ident::new("check_referencing")]), + period: TriggerPeriod::Before, + events: vec![TriggerEvent::Insert], + table_name: ObjectName(vec![Ident::new("accounts")]), + referenced_table_name: None, + referencing: vec![ + TriggerReferencing{ + refer_type: TriggerReferencingType::NewTable, + is_as: true, + transition_relation_name: ObjectName(vec![Ident::new("new_accounts")]), + }, + TriggerReferencing{ + refer_type: TriggerReferencingType::OldTable, + is_as: true, + transition_relation_name: ObjectName(vec![Ident::new("old_accounts")]), + }, + ], + trigger_object: TriggerObject::Row, + include_each: true, + condition: None, + exec_body: TriggerExecBody { + exec_type: TriggerExecBodyType::Function, + func_desc: FunctionDesc{name: ObjectName(vec![Ident::new("check_account_referencing")]), args: None}, + }, + characteristics: None, + }; + + assert_eq!(pg().verified_stmt(sql), expected); } #[test] diff --git a/tests/test_utils/extend_tuple.rs b/tests/test_utils/extend_tuple.rs deleted file mode 100644 index 86cdfc62a..000000000 --- a/tests/test_utils/extend_tuple.rs +++ /dev/null @@ -1,177 +0,0 @@ -//! Submodule providing the extend tuple trait. - -/// Trait for extending a tuple with a new element. -pub trait ExtendTuple { - /// The output type after extending the tuple. - type Output; - - /// Extend a tuple with a new element. - fn extend_tuple(self, to_extend: ToExtend) -> Self::Output; -} - -impl ExtendTuple<(T1,)> for T2 { - type Output = (T1, T2); - - fn extend_tuple(self, to_extend: (T1,)) -> Self::Output { - (to_extend.0, self) - } -} - -impl ExtendTuple<(T1, T2)> for T3 { - type Output = (T1, T2, T3); - - fn extend_tuple(self, to_extend: (T1, T2)) -> Self::Output { - (to_extend.0, to_extend.1, self) - } -} - -impl ExtendTuple<(T1, T2, T3)> for T4 { - type Output = (T1, T2, T3, T4); - - fn extend_tuple(self, to_extend: (T1, T2, T3)) -> Self::Output { - (to_extend.0, to_extend.1, to_extend.2, self) - } -} - -impl ExtendTuple<(T1, T2, T3, T4)> for T5 { - type Output = (T1, T2, T3, T4, T5); - - fn extend_tuple(self, to_extend: (T1, T2, T3, T4)) -> Self::Output { - (to_extend.0, to_extend.1, to_extend.2, to_extend.3, self) - } -} - -impl ExtendTuple<(T1, T2, T3, T4, T5)> for T6 { - type Output = (T1, T2, T3, T4, T5, T6); - - fn extend_tuple(self, to_extend: (T1, T2, T3, T4, T5)) -> Self::Output { - ( - to_extend.0, - to_extend.1, - to_extend.2, - to_extend.3, - to_extend.4, - self, - ) - } -} - -impl ExtendTuple<(T1, T2, T3, T4, T5, T6)> for T7 { - type Output = (T1, T2, T3, T4, T5, T6, T7); - - fn extend_tuple(self, to_extend: (T1, T2, T3, T4, T5, T6)) -> Self::Output { - ( - to_extend.0, - to_extend.1, - to_extend.2, - to_extend.3, - to_extend.4, - to_extend.5, - self, - ) - } -} - -impl ExtendTuple<(T1, T2, T3, T4, T5, T6, T7)> for T8 { - type Output = (T1, T2, T3, T4, T5, T6, T7, T8); - - fn extend_tuple(self, to_extend: (T1, T2, T3, T4, T5, T6, T7)) -> Self::Output { - ( - to_extend.0, - to_extend.1, - to_extend.2, - to_extend.3, - to_extend.4, - to_extend.5, - to_extend.6, - self, - ) - } -} - -impl ExtendTuple<(T1, T2, T3, T4, T5, T6, T7, T8)> for T9 { - type Output = (T1, T2, T3, T4, T5, T6, T7, T8, T9); - - fn extend_tuple(self, to_extend: (T1, T2, T3, T4, T5, T6, T7, T8)) -> Self::Output { - ( - to_extend.0, - to_extend.1, - to_extend.2, - to_extend.3, - to_extend.4, - to_extend.5, - to_extend.6, - to_extend.7, - self, - ) - } -} - -impl ExtendTuple<(T1, T2, T3, T4, T5, T6, T7, T8, T9)> - for T10 -{ - type Output = (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10); - - fn extend_tuple(self, to_extend: (T1, T2, T3, T4, T5, T6, T7, T8, T9)) -> Self::Output { - ( - to_extend.0, - to_extend.1, - to_extend.2, - to_extend.3, - to_extend.4, - to_extend.5, - to_extend.6, - to_extend.7, - to_extend.8, - self, - ) - } -} - -impl - ExtendTuple<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)> for T11 -{ - type Output = (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11); - - fn extend_tuple(self, to_extend: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)) -> Self::Output { - ( - to_extend.0, - to_extend.1, - to_extend.2, - to_extend.3, - to_extend.4, - to_extend.5, - to_extend.6, - to_extend.7, - to_extend.8, - to_extend.9, - self, - ) - } -} - -impl - ExtendTuple<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11)> for T12 -{ - type Output = (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12); - - fn extend_tuple( - self, - to_extend: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11), - ) -> Self::Output { - ( - to_extend.0, - to_extend.1, - to_extend.2, - to_extend.3, - to_extend.4, - to_extend.5, - to_extend.6, - to_extend.7, - to_extend.8, - to_extend.9, - to_extend.10, - self, - ) - } -} diff --git a/tests/test_utils/mod.rs b/tests/test_utils/mod.rs index 676b1eba3..d3a56ce46 100644 --- a/tests/test_utils/mod.rs +++ b/tests/test_utils/mod.rs @@ -32,6 +32,3 @@ macro_rules! nest { }), alias: None} }; } - -mod extend_tuple; -pub use extend_tuple::ExtendTuple; From be1a682f05776bb316899ee63b028388946825a5 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Tue, 13 Aug 2024 17:11:52 +0200 Subject: [PATCH 50/51] Removed dead code --- tests/sqlparser_postgres.rs | 223 ++++++------------------------------ 1 file changed, 32 insertions(+), 191 deletions(-) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 12b85e0c7..0db0786c6 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4442,184 +4442,6 @@ fn test_table_unnest_with_ordinality() { } } -fn possible_trigger_referencing_variants() -> Vec> { - vec![ - vec![], - vec![TriggerReferencing { - refer_type: TriggerReferencingType::NewTable, - is_as: false, - transition_relation_name: ObjectName(vec![Ident::new("new_table")]), - }], - vec![TriggerReferencing { - refer_type: TriggerReferencingType::OldTable, - is_as: false, - transition_relation_name: ObjectName(vec![Ident::new("old_table")]), - }], - vec![ - TriggerReferencing { - refer_type: TriggerReferencingType::NewTable, - is_as: false, - transition_relation_name: ObjectName(vec![Ident::new("new_table")]), - }, - TriggerReferencing { - refer_type: TriggerReferencingType::OldTable, - is_as: false, - transition_relation_name: ObjectName(vec![Ident::new("old_table")]), - }, - ], - vec![ - TriggerReferencing { - refer_type: TriggerReferencingType::OldTable, - is_as: false, - transition_relation_name: ObjectName(vec![Ident::new("old_table")]), - }, - TriggerReferencing { - refer_type: TriggerReferencingType::NewTable, - is_as: false, - transition_relation_name: ObjectName(vec![Ident::new("new_table")]), - }, - ], - ] -} - -fn possible_trigger_object_variants() -> Vec { - vec![TriggerObject::Row, TriggerObject::Statement] -} - -fn possible_trigger_deferrable_variants() -> Vec> { - vec![ - None, - Some(ConstraintCharacteristics { - deferrable: Some(true), - enforced: None, - initially: Some(DeferrableInitial::Deferred), - }), - Some(ConstraintCharacteristics { - deferrable: Some(false), - enforced: None, - initially: Some(DeferrableInitial::Immediate), - }), - Some(ConstraintCharacteristics { - deferrable: Some(true), - enforced: None, - initially: Some(DeferrableInitial::Immediate), - }), - Some(ConstraintCharacteristics { - deferrable: Some(true), - enforced: None, - initially: Some(DeferrableInitial::Deferred), - }), - ] -} - -fn possible_trigger_function_descriptions() -> Vec { - vec![ - FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: None, - }, - FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: Some(vec![OperateFunctionArg::unnamed(DataType::Int(None))]), - }, - FunctionDesc { - name: ObjectName(vec![Ident::new("check_account_update")]), - args: Some(vec![ - OperateFunctionArg::with_name("a", DataType::Int(None)), - OperateFunctionArg { - mode: Some(ArgMode::In), - name: Some("b".into()), - data_type: DataType::Int(None), - default_expr: Some(Expr::Value(Value::Number("1".parse().unwrap(), false))), - }, - ]), - }, - ] -} - -fn possible_trigger_exec_body_types() -> Vec { - vec![ - TriggerExecBodyType::Function, - TriggerExecBodyType::Procedure, - ] -} - -fn possible_trigger_events() -> Vec> { - vec![ - vec![TriggerEvent::Update(vec![])], - vec![TriggerEvent::Insert], - vec![TriggerEvent::Delete], - vec![TriggerEvent::Update(vec![]), TriggerEvent::Insert], - vec![ - TriggerEvent::Update(vec![]), - TriggerEvent::Insert, - TriggerEvent::Delete, - ], - vec![TriggerEvent::Update(vec![Ident::new("balance")])], - vec![ - TriggerEvent::Update(vec![Ident::new("balance")]), - TriggerEvent::Insert, - ], - vec![ - TriggerEvent::Update(vec![Ident::new("balance")]), - TriggerEvent::Delete, - ], - vec![ - TriggerEvent::Update(vec![Ident::new("balance")]), - TriggerEvent::Insert, - TriggerEvent::Delete, - ], - ] -} - -fn possible_referencing_table_names() -> Vec> { - vec![ - // Case with no referencing table - None, - // Case with a referencing table - Some(ObjectName(vec![Ident::new("referencing_table")])), - // Case with a referencing table from a different schema - Some(ObjectName(vec![ - Ident::new("referencing_schema"), - Ident::new("referencing_table"), - ])), - ] -} - -fn possible_trigger_periods() -> Vec { - vec![ - TriggerPeriod::Before, - TriggerPeriod::After, - TriggerPeriod::InsteadOf, - ] -} - -fn possible_trigger_condition() -> Vec> { - vec![ - None, - Some(Expr::Nested(Box::new(Expr::IsNotDistinctFrom( - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("OLD"), - Ident::new("balance"), - ])), - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("NEW"), - Ident::new("balance"), - ])), - )))), - Some(Expr::Nested(Box::new(Expr::IsDistinctFrom( - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("OLD"), - Ident::new("balance"), - ])), - Box::new(Expr::CompoundIdentifier(vec![ - Ident::new("NEW"), - Ident::new("balance"), - ])), - )))), - ] -} - #[test] fn test_escaped_string_literal() { match pg().verified_expr(r#"E'\n'"#) { @@ -4647,11 +4469,14 @@ fn parse_create_simple_before_insert_trigger() { condition: None, exec_body: TriggerExecBody { exec_type: TriggerExecBodyType::Function, - func_desc: FunctionDesc{name: ObjectName(vec![Ident::new("check_account_insert")]), args: None}, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_insert")]), + args: None, + }, }, characteristics: None, }; - + assert_eq!(pg().verified_stmt(sql), expected); } @@ -4679,11 +4504,14 @@ fn parse_create_after_update_trigger_with_condition() { }))), exec_body: TriggerExecBody { exec_type: TriggerExecBodyType::Function, - func_desc: FunctionDesc{name: ObjectName(vec![Ident::new("check_account_update")]), args: None}, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_update")]), + args: None, + }, }, characteristics: None, }; - + assert_eq!(pg().verified_stmt(sql), expected); } @@ -4704,11 +4532,14 @@ fn parse_create_instead_of_delete_trigger() { condition: None, exec_body: TriggerExecBody { exec_type: TriggerExecBodyType::Function, - func_desc: FunctionDesc{name: ObjectName(vec![Ident::new("check_account_deletes")]), args: None}, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_deletes")]), + args: None, + }, }, characteristics: None, }; - + assert_eq!(pg().verified_stmt(sql), expected); } @@ -4720,7 +4551,11 @@ fn parse_create_trigger_with_multiple_events_and_deferrable() { is_constraint: true, name: ObjectName(vec![Ident::new("check_multiple_events")]), period: TriggerPeriod::Before, - events: vec![TriggerEvent::Insert, TriggerEvent::Update(vec![]), TriggerEvent::Delete], + events: vec![ + TriggerEvent::Insert, + TriggerEvent::Update(vec![]), + TriggerEvent::Delete, + ], table_name: ObjectName(vec![Ident::new("accounts")]), referenced_table_name: None, referencing: vec![], @@ -4729,7 +4564,10 @@ fn parse_create_trigger_with_multiple_events_and_deferrable() { condition: None, exec_body: TriggerExecBody { exec_type: TriggerExecBodyType::Function, - func_desc: FunctionDesc{name: ObjectName(vec![Ident::new("check_account_changes")]), args: None}, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_changes")]), + args: None, + }, }, characteristics: Some(ConstraintCharacteristics { deferrable: Some(true), @@ -4737,7 +4575,7 @@ fn parse_create_trigger_with_multiple_events_and_deferrable() { enforced: None, }), }; - + assert_eq!(pg().verified_stmt(sql), expected); } @@ -4753,12 +4591,12 @@ fn parse_create_trigger_with_referencing() { table_name: ObjectName(vec![Ident::new("accounts")]), referenced_table_name: None, referencing: vec![ - TriggerReferencing{ + TriggerReferencing { refer_type: TriggerReferencingType::NewTable, is_as: true, transition_relation_name: ObjectName(vec![Ident::new("new_accounts")]), }, - TriggerReferencing{ + TriggerReferencing { refer_type: TriggerReferencingType::OldTable, is_as: true, transition_relation_name: ObjectName(vec![Ident::new("old_accounts")]), @@ -4769,11 +4607,14 @@ fn parse_create_trigger_with_referencing() { condition: None, exec_body: TriggerExecBody { exec_type: TriggerExecBodyType::Function, - func_desc: FunctionDesc{name: ObjectName(vec![Ident::new("check_account_referencing")]), args: None}, + func_desc: FunctionDesc { + name: ObjectName(vec![Ident::new("check_account_referencing")]), + args: None, + }, }, characteristics: None, }; - + assert_eq!(pg().verified_stmt(sql), expected); } From 0182a5060e84011d9fbdf1dbd82a368ae87f1462 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 13 Aug 2024 11:17:35 -0400 Subject: [PATCH 51/51] Fix clippy --- tests/sqlparser_postgres.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 4bb53fb29..2f9fe86c9 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4503,7 +4503,7 @@ fn parse_create_after_update_trigger_with_condition() { Ident::new("balance"), ])), op: BinaryOperator::Gt, - right: Box::new(Expr::Value(Value::Number("10000".to_string(), false))), + right: Box::new(Expr::Value(number("10000"))), }))), exec_body: TriggerExecBody { exec_type: TriggerExecBodyType::Function,