Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 35 additions & 11 deletions src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3199,6 +3199,22 @@ pub struct CreateTrigger {
///
/// [MsSql](https://learn.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql?view=sql-server-ver16#arguments)
pub or_alter: bool,
/// True if this is a temporary trigger, which is supported in SQLite.
///
/// The possible syntaxes are two:
///
/// ```sql
/// CREATE TEMP TRIGGER trigger_name
/// ```
///
/// or
///
/// ```sql
/// CREATE TEMPORARY TRIGGER trigger_name
/// ```
///
/// [Temporary Triggers in SQLite](https://sqlite.org/lang_createtrigger.html#temp_triggers_on_non_temp_tables)
pub temporary: bool,
/// The `OR REPLACE` clause is used to re-create the trigger if it already exists.
///
/// Example:
Expand Down Expand Up @@ -3243,14 +3259,16 @@ pub struct CreateTrigger {
/// ```
pub period: TriggerPeriod,
/// Whether the trigger period was specified before the target table name.
/// This does not refer to whether the period is BEFORE, AFTER, or INSTEAD OF,
/// but rather the position of the period clause in relation to the table name.
///
/// ```sql
/// -- period_before_table == true: Postgres, MySQL, and standard SQL
/// -- period_specified_before_table == true: Postgres, MySQL, and standard SQL
/// CREATE TRIGGER t BEFORE INSERT ON table_name ...;
/// -- period_before_table == false: MSSQL
/// -- period_specified_before_table == false: MSSQL
/// CREATE TRIGGER t ON table_name BEFORE INSERT ...;
/// ```
pub period_before_table: bool,
pub period_specified_before_table: bool,
/// Multiple events can be specified using OR, such as `INSERT`, `UPDATE`, `DELETE`, or `TRUNCATE`.
pub events: Vec<TriggerEvent>,
/// The table on which the trigger is to be created.
Expand All @@ -3262,7 +3280,9 @@ pub struct CreateTrigger {
pub referencing: Vec<TriggerReferencing>,
/// This specifies whether the trigger function should be fired once for
/// every row affected by the trigger event, or just once per SQL statement.
pub trigger_object: TriggerObject,
/// This is optional in some SQL dialects, such as SQLite, and if not specified, in
/// those cases, the implied default is `FOR EACH ROW`.
pub trigger_object: Option<TriggerObject>,
/// Whether to include the `EACH` term of the `FOR EACH`, as it is optional syntax.
pub include_each: bool,
/// Triggering conditions
Expand All @@ -3281,10 +3301,11 @@ impl Display for CreateTrigger {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let CreateTrigger {
or_alter,
temporary,
or_replace,
is_constraint,
name,
period_before_table,
period_specified_before_table,
period,
events,
table_name,
Expand All @@ -3300,13 +3321,14 @@ impl Display for CreateTrigger {
} = self;
write!(
f,
"CREATE {or_alter}{or_replace}{is_constraint}TRIGGER {name} ",
"CREATE {temporary}{or_alter}{or_replace}{is_constraint}TRIGGER {name} ",
temporary = if *temporary { "TEMPORARY " } else { "" },
or_alter = if *or_alter { "OR ALTER " } else { "" },
or_replace = if *or_replace { "OR REPLACE " } else { "" },
is_constraint = if *is_constraint { "CONSTRAINT " } else { "" },
)?;

if *period_before_table {
if *period_specified_before_table {
write!(f, "{period}")?;
if !events.is_empty() {
write!(f, " {}", display_separated(events, " OR "))?;
Expand All @@ -3332,10 +3354,12 @@ impl Display for CreateTrigger {
write!(f, " REFERENCING {}", display_separated(referencing, " "))?;
}

if *include_each {
write!(f, " FOR EACH {trigger_object}")?;
} else if exec_body.is_some() {
write!(f, " FOR {trigger_object}")?;
if let Some(trigger_object) = trigger_object {
if *include_each {
write!(f, " FOR EACH {trigger_object}")?;
} else if exec_body.is_some() {
write!(f, " FOR {trigger_object}")?;
}
}
if let Some(condition) = condition {
write!(f, " WHEN {condition}")?;
Expand Down
5 changes: 3 additions & 2 deletions src/dialect/mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,16 +254,17 @@ impl MsSqlDialect {

Ok(CreateTrigger {
or_alter,
temporary: false,
or_replace: false,
is_constraint: false,
name,
period,
period_before_table: false,
period_specified_before_table: false,
events,
table_name,
referenced_table_name: None,
referencing: Vec::new(),
trigger_object: TriggerObject::Statement,
trigger_object: Some(TriggerObject::Statement),
include_each: false,
condition: None,
exec_body: None,
Expand Down
46 changes: 31 additions & 15 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4750,9 +4750,9 @@ impl<'a> Parser<'a> {
} else if self.parse_keyword(Keyword::DOMAIN) {
self.parse_create_domain()
} else if self.parse_keyword(Keyword::TRIGGER) {
self.parse_create_trigger(or_alter, or_replace, false)
self.parse_create_trigger(temporary, or_alter, or_replace, false)
} else if self.parse_keywords(&[Keyword::CONSTRAINT, Keyword::TRIGGER]) {
self.parse_create_trigger(or_alter, or_replace, true)
self.parse_create_trigger(temporary, or_alter, or_replace, true)
} else if self.parse_keyword(Keyword::MACRO) {
self.parse_create_macro(or_replace, temporary)
} else if self.parse_keyword(Keyword::SECRET) {
Expand Down Expand Up @@ -5546,7 +5546,8 @@ impl<'a> Parser<'a> {
/// DROP TRIGGER [ IF EXISTS ] name ON table_name [ CASCADE | RESTRICT ]
/// ```
pub fn parse_drop_trigger(&mut self) -> Result<Statement, ParserError> {
if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect | MsSqlDialect) {
if !dialect_of!(self is PostgreSqlDialect | SQLiteDialect | GenericDialect | MySqlDialect | MsSqlDialect)
{
self.prev_token();
return self.expected("an object type after DROP", self.peek_token());
}
Expand Down Expand Up @@ -5574,11 +5575,14 @@ impl<'a> Parser<'a> {

pub fn parse_create_trigger(
&mut self,
temporary: bool,
or_alter: bool,
or_replace: bool,
is_constraint: bool,
) -> Result<Statement, ParserError> {
if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect | MsSqlDialect) {
if !dialect_of!(self is PostgreSqlDialect | SQLiteDialect | GenericDialect | MySqlDialect | MsSqlDialect)
|| dialect_of!(self is SQLiteDialect) && (or_alter || or_replace || is_constraint)
{
self.prev_token();
return self.expected("an object type after CREATE", self.peek_token());
}
Expand All @@ -5605,14 +5609,24 @@ impl<'a> Parser<'a> {
}
}

self.expect_keyword_is(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 (include_each, trigger_object) = if self.parse_keyword(Keyword::FOR) {
(
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!(),
},
),
)
} else {
if !dialect_of!(self is SQLiteDialect ) {
self.expect_keyword_is(Keyword::FOR)?;
}

(false, None)
};

let condition = self
.parse_keyword(Keyword::WHEN)
Expand All @@ -5627,13 +5641,14 @@ impl<'a> Parser<'a> {
statements = Some(self.parse_conditional_statements(&[Keyword::END])?);
}

Ok(Statement::CreateTrigger(CreateTrigger {
Ok(CreateTrigger {
or_alter,
temporary,
or_replace,
is_constraint,
name,
period,
period_before_table: true,
period_specified_before_table: true,
events,
table_name,
referenced_table_name,
Expand All @@ -5645,7 +5660,8 @@ impl<'a> Parser<'a> {
statements_as: false,
statements,
characteristics,
}))
}
.into())
}

pub fn parse_trigger_period(&mut self) -> Result<TriggerPeriod, ParserError> {
Expand Down
5 changes: 3 additions & 2 deletions tests/sqlparser_mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2386,16 +2386,17 @@ fn parse_create_trigger() {
create_stmt,
Statement::CreateTrigger(CreateTrigger {
or_alter: true,
temporary: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("reminder1")]),
period: TriggerPeriod::After,
period_before_table: false,
period_specified_before_table: false,
events: vec![TriggerEvent::Insert, TriggerEvent::Update(vec![]),],
table_name: ObjectName::from(vec![Ident::new("Sales"), Ident::new("Customer")]),
referenced_table_name: None,
referencing: vec![],
trigger_object: TriggerObject::Statement,
trigger_object: Some(TriggerObject::Statement),
include_each: false,
condition: None,
exec_body: None,
Expand Down
5 changes: 3 additions & 2 deletions tests/sqlparser_mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3924,16 +3924,17 @@ fn parse_create_trigger() {
create_stmt,
Statement::CreateTrigger(CreateTrigger {
or_alter: false,
temporary: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
period: TriggerPeriod::Before,
period_before_table: true,
period_specified_before_table: true,
events: vec![TriggerEvent::Insert],
table_name: ObjectName::from(vec![Ident::new("emp")]),
referenced_table_name: None,
referencing: vec![],
trigger_object: TriggerObject::Row,
trigger_object: Some(TriggerObject::Row),
include_each: true,
condition: None,
exec_body: Some(TriggerExecBody {
Expand Down
30 changes: 18 additions & 12 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5673,16 +5673,17 @@ 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(CreateTrigger {
or_alter: false,
temporary: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("check_insert")]),
period: TriggerPeriod::Before,
period_before_table: true,
period_specified_before_table: true,
events: vec![TriggerEvent::Insert],
table_name: ObjectName::from(vec![Ident::new("accounts")]),
referenced_table_name: None,
referencing: vec![],
trigger_object: TriggerObject::Row,
trigger_object: Some(TriggerObject::Row),
include_each: true,
condition: None,
exec_body: Some(TriggerExecBody {
Expand All @@ -5705,16 +5706,17 @@ 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(CreateTrigger {
or_alter: false,
temporary: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("check_update")]),
period: TriggerPeriod::After,
period_before_table: true,
period_specified_before_table: true,
events: vec![TriggerEvent::Update(vec![])],
table_name: ObjectName::from(vec![Ident::new("accounts")]),
referenced_table_name: None,
referencing: vec![],
trigger_object: TriggerObject::Row,
trigger_object: Some(TriggerObject::Row),
include_each: true,
condition: Some(Expr::Nested(Box::new(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Expand Down Expand Up @@ -5744,16 +5746,17 @@ 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(CreateTrigger {
or_alter: false,
temporary: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("check_delete")]),
period: TriggerPeriod::InsteadOf,
period_before_table: true,
period_specified_before_table: true,
events: vec![TriggerEvent::Delete],
table_name: ObjectName::from(vec![Ident::new("accounts")]),
referenced_table_name: None,
referencing: vec![],
trigger_object: TriggerObject::Row,
trigger_object: Some(TriggerObject::Row),
include_each: true,
condition: None,
exec_body: Some(TriggerExecBody {
Expand All @@ -5776,11 +5779,12 @@ 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(CreateTrigger {
or_alter: false,
temporary: false,
or_replace: false,
is_constraint: true,
name: ObjectName::from(vec![Ident::new("check_multiple_events")]),
period: TriggerPeriod::Before,
period_before_table: true,
period_specified_before_table: true,
events: vec![
TriggerEvent::Insert,
TriggerEvent::Update(vec![]),
Expand All @@ -5789,7 +5793,7 @@ fn parse_create_trigger_with_multiple_events_and_deferrable() {
table_name: ObjectName::from(vec![Ident::new("accounts")]),
referenced_table_name: None,
referencing: vec![],
trigger_object: TriggerObject::Row,
trigger_object: Some(TriggerObject::Row),
include_each: true,
condition: None,
exec_body: Some(TriggerExecBody {
Expand All @@ -5816,11 +5820,12 @@ 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(CreateTrigger {
or_alter: false,
temporary: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("check_referencing")]),
period: TriggerPeriod::Before,
period_before_table: true,
period_specified_before_table: true,
events: vec![TriggerEvent::Insert],
table_name: ObjectName::from(vec![Ident::new("accounts")]),
referenced_table_name: None,
Expand All @@ -5836,7 +5841,7 @@ fn parse_create_trigger_with_referencing() {
transition_relation_name: ObjectName::from(vec![Ident::new("old_accounts")]),
},
],
trigger_object: TriggerObject::Row,
trigger_object: Some(TriggerObject::Row),
include_each: true,
condition: None,
exec_body: Some(TriggerExecBody {
Expand Down Expand Up @@ -6132,16 +6137,17 @@ fn parse_trigger_related_functions() {
create_trigger,
Statement::CreateTrigger(CreateTrigger {
or_alter: false,
temporary: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
period: TriggerPeriod::Before,
period_before_table: true,
period_specified_before_table: true,
events: vec![TriggerEvent::Insert, TriggerEvent::Update(vec![])],
table_name: ObjectName::from(vec![Ident::new("emp")]),
referenced_table_name: None,
referencing: vec![],
trigger_object: TriggerObject::Row,
trigger_object: Some(TriggerObject::Row),
include_each: true,
condition: None,
exec_body: Some(TriggerExecBody {
Expand Down
Loading