mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-22 21:52:28 +00:00
Adding support for parsing CREATE TRIGGER and DROP TRIGGER statements (#1352)
Co-authored-by: hulk <hulk.website@gmail.com> Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com> Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
This commit is contained in:
parent
f5b818e74b
commit
b072ce2589
8 changed files with 1022 additions and 25 deletions
|
@ -319,6 +319,10 @@ pub enum DataType {
|
||||||
/// [`SQLiteDialect`](crate::dialect::SQLiteDialect), from statements such
|
/// [`SQLiteDialect`](crate::dialect::SQLiteDialect), from statements such
|
||||||
/// as `CREATE TABLE t1 (a)`.
|
/// as `CREATE TABLE t1 (a)`.
|
||||||
Unspecified,
|
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 {
|
impl fmt::Display for DataType {
|
||||||
|
@ -543,6 +547,7 @@ impl fmt::Display for DataType {
|
||||||
write!(f, "Nested({})", display_comma_separated(fields))
|
write!(f, "Nested({})", display_comma_separated(fields))
|
||||||
}
|
}
|
||||||
DataType::Unspecified => Ok(()),
|
DataType::Unspecified => Ok(()),
|
||||||
|
DataType::Trigger => write!(f, "TRIGGER"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1175,7 +1175,7 @@ fn display_option_spaced<T: fmt::Display>(option: &Option<T>) -> impl fmt::Displ
|
||||||
/// `<constraint_characteristics> = [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] [ ENFORCED | NOT ENFORCED ]`
|
/// `<constraint_characteristics> = [ 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.
|
/// 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, PartialEq, PartialOrd, Default, Eq, Ord, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
pub struct ConstraintCharacteristics {
|
pub struct ConstraintCharacteristics {
|
||||||
|
|
172
src/ast/mod.rs
172
src/ast/mod.rs
|
@ -53,6 +53,12 @@ pub use self::query::{
|
||||||
TableAlias, TableFactor, TableFunctionArgs, TableVersion, TableWithJoins, Top, TopQuantity,
|
TableAlias, TableFactor, TableFunctionArgs, TableVersion, TableWithJoins, Top, TopQuantity,
|
||||||
ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill,
|
ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use self::trigger::{
|
||||||
|
TriggerEvent, TriggerExecBody, TriggerExecBodyType, TriggerObject, TriggerPeriod,
|
||||||
|
TriggerReferencing, TriggerReferencingType,
|
||||||
|
};
|
||||||
|
|
||||||
pub use self::value::{
|
pub use self::value::{
|
||||||
escape_double_quote_string, escape_quoted_string, DateTimeField, DollarQuotedString,
|
escape_double_quote_string, escape_quoted_string, DateTimeField, DollarQuotedString,
|
||||||
TrimWhereField, Value,
|
TrimWhereField, Value,
|
||||||
|
@ -71,6 +77,7 @@ mod dml;
|
||||||
pub mod helpers;
|
pub mod helpers;
|
||||||
mod operator;
|
mod operator;
|
||||||
mod query;
|
mod query;
|
||||||
|
mod trigger;
|
||||||
mod value;
|
mod value;
|
||||||
|
|
||||||
#[cfg(feature = "visitor")]
|
#[cfg(feature = "visitor")]
|
||||||
|
@ -2282,7 +2289,7 @@ pub enum Statement {
|
||||||
DropFunction {
|
DropFunction {
|
||||||
if_exists: bool,
|
if_exists: bool,
|
||||||
/// One or more function to drop
|
/// One or more function to drop
|
||||||
func_desc: Vec<DropFunctionDesc>,
|
func_desc: Vec<FunctionDesc>,
|
||||||
/// `CASCADE` or `RESTRICT`
|
/// `CASCADE` or `RESTRICT`
|
||||||
option: Option<ReferentialAction>,
|
option: Option<ReferentialAction>,
|
||||||
},
|
},
|
||||||
|
@ -2292,7 +2299,7 @@ pub enum Statement {
|
||||||
DropProcedure {
|
DropProcedure {
|
||||||
if_exists: bool,
|
if_exists: bool,
|
||||||
/// One or more function to drop
|
/// One or more function to drop
|
||||||
proc_desc: Vec<DropFunctionDesc>,
|
proc_desc: Vec<FunctionDesc>,
|
||||||
/// `CASCADE` or `RESTRICT`
|
/// `CASCADE` or `RESTRICT`
|
||||||
option: Option<ReferentialAction>,
|
option: Option<ReferentialAction>,
|
||||||
},
|
},
|
||||||
|
@ -2618,6 +2625,96 @@ pub enum Statement {
|
||||||
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_a_remote_function)
|
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_a_remote_function)
|
||||||
remote_connection: Option<ObjectName>,
|
remote_connection: Option<ObjectName>,
|
||||||
},
|
},
|
||||||
|
/// 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>
|
||||||
|
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 `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.
|
||||||
|
///
|
||||||
|
/// 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, such as `INSERT`, `UPDATE`, `DELETE`, or `TRUNCATE`.
|
||||||
|
events: Vec<TriggerEvent>,
|
||||||
|
/// 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<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<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.
|
||||||
|
trigger_object: TriggerObject,
|
||||||
|
/// Whether to include the `EACH` term of the `FOR EACH`, as it is optional syntax.
|
||||||
|
include_each: bool,
|
||||||
|
/// Triggering conditions
|
||||||
|
condition: Option<Expr>,
|
||||||
|
/// 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<ConstraintCharacteristics>,
|
||||||
|
},
|
||||||
|
/// 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<ReferentialAction>,
|
||||||
|
},
|
||||||
/// ```sql
|
/// ```sql
|
||||||
/// CREATE PROCEDURE
|
/// CREATE PROCEDURE
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -3394,6 +3491,71 @@ impl fmt::Display for Statement {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Statement::CreateTrigger {
|
||||||
|
or_replace,
|
||||||
|
is_constraint,
|
||||||
|
name,
|
||||||
|
period,
|
||||||
|
events,
|
||||||
|
table_name,
|
||||||
|
referenced_table_name,
|
||||||
|
referencing,
|
||||||
|
trigger_object,
|
||||||
|
condition,
|
||||||
|
include_each,
|
||||||
|
exec_body,
|
||||||
|
characteristics,
|
||||||
|
} => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"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 "))?;
|
||||||
|
}
|
||||||
|
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}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !referencing.is_empty() {
|
||||||
|
write!(f, " REFERENCING {}", display_separated(referencing, " "))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if *include_each {
|
||||||
|
write!(f, " FOR EACH {trigger_object}")?;
|
||||||
|
} else {
|
||||||
|
write!(f, " FOR {trigger_object}")?;
|
||||||
|
}
|
||||||
|
if let Some(condition) = condition {
|
||||||
|
write!(f, " WHEN {condition}")?;
|
||||||
|
}
|
||||||
|
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 {
|
Statement::CreateProcedure {
|
||||||
name,
|
name,
|
||||||
or_alter,
|
or_alter,
|
||||||
|
@ -6026,16 +6188,16 @@ 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)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
pub struct DropFunctionDesc {
|
pub struct FunctionDesc {
|
||||||
pub name: ObjectName,
|
pub name: ObjectName,
|
||||||
pub args: Option<Vec<OperateFunctionArg>>,
|
pub args: Option<Vec<OperateFunctionArg>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for DropFunctionDesc {
|
impl fmt::Display for FunctionDesc {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{}", self.name)?;
|
write!(f, "{}", self.name)?;
|
||||||
if let Some(args) = &self.args {
|
if let Some(args) = &self.args {
|
||||||
|
|
158
src/ast/trigger.rs
Normal file
158
src/ast/trigger.rs
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
// 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::*;
|
||||||
|
|
||||||
|
/// 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, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
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, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
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))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
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))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub enum TriggerEvent {
|
||||||
|
Insert,
|
||||||
|
Update(Vec<Ident>),
|
||||||
|
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, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Types of trigger body execution body.
|
||||||
|
#[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 {
|
||||||
|
Function,
|
||||||
|
Procedure,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TriggerExecBodyType {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
TriggerExecBodyType::Function => write!(f, "FUNCTION"),
|
||||||
|
TriggerExecBodyType::Procedure => 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))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub struct TriggerExecBody {
|
||||||
|
pub exec_type: TriggerExecBodyType,
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -108,6 +108,7 @@ define_keywords!(
|
||||||
AVRO,
|
AVRO,
|
||||||
BACKWARD,
|
BACKWARD,
|
||||||
BASE64,
|
BASE64,
|
||||||
|
BEFORE,
|
||||||
BEGIN,
|
BEGIN,
|
||||||
BEGIN_FRAME,
|
BEGIN_FRAME,
|
||||||
BEGIN_PARTITION,
|
BEGIN_PARTITION,
|
||||||
|
@ -378,6 +379,7 @@ define_keywords!(
|
||||||
INSENSITIVE,
|
INSENSITIVE,
|
||||||
INSERT,
|
INSERT,
|
||||||
INSTALL,
|
INSTALL,
|
||||||
|
INSTEAD,
|
||||||
INT,
|
INT,
|
||||||
INT128,
|
INT128,
|
||||||
INT16,
|
INT16,
|
||||||
|
@ -683,6 +685,7 @@ define_keywords!(
|
||||||
STABLE,
|
STABLE,
|
||||||
STAGE,
|
STAGE,
|
||||||
START,
|
START,
|
||||||
|
STATEMENT,
|
||||||
STATIC,
|
STATIC,
|
||||||
STATISTICS,
|
STATISTICS,
|
||||||
STATUS,
|
STATUS,
|
||||||
|
|
|
@ -3368,6 +3368,25 @@ impl<'a> Parser<'a> {
|
||||||
Ok(values)
|
Ok(values)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a keyword-separated list of 1+ items accepted by `F`
|
||||||
|
pub fn parse_keyword_separated<T, F>(
|
||||||
|
&mut self,
|
||||||
|
keyword: Keyword,
|
||||||
|
mut f: F,
|
||||||
|
) -> Result<Vec<T>, ParserError>
|
||||||
|
where
|
||||||
|
F: FnMut(&mut Parser<'a>) -> Result<T, ParserError>,
|
||||||
|
{
|
||||||
|
let mut values = vec![];
|
||||||
|
loop {
|
||||||
|
values.push(f(self)?);
|
||||||
|
if !self.parse_keyword(keyword) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(values)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_parenthesized<T, F>(&mut self, mut f: F) -> Result<T, ParserError>
|
pub fn parse_parenthesized<T, F>(&mut self, mut f: F) -> Result<T, ParserError>
|
||||||
where
|
where
|
||||||
F: FnMut(&mut Parser<'a>) -> Result<T, ParserError>,
|
F: FnMut(&mut Parser<'a>) -> Result<T, ParserError>,
|
||||||
|
@ -3471,6 +3490,10 @@ impl<'a> Parser<'a> {
|
||||||
self.parse_create_external_table(or_replace)
|
self.parse_create_external_table(or_replace)
|
||||||
} else if self.parse_keyword(Keyword::FUNCTION) {
|
} else if self.parse_keyword(Keyword::FUNCTION) {
|
||||||
self.parse_create_function(or_replace, temporary)
|
self.parse_create_function(or_replace, temporary)
|
||||||
|
} else if self.parse_keyword(Keyword::TRIGGER) {
|
||||||
|
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) {
|
} else if self.parse_keyword(Keyword::MACRO) {
|
||||||
self.parse_create_macro(or_replace, temporary)
|
self.parse_create_macro(or_replace, temporary)
|
||||||
} else if self.parse_keyword(Keyword::SECRET) {
|
} else if self.parse_keyword(Keyword::SECRET) {
|
||||||
|
@ -4061,6 +4084,180 @@ 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<Statement, ParserError> {
|
||||||
|
if !dialect_of!(self is PostgreSqlDialect | GenericDialect) {
|
||||||
|
self.prev_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,
|
||||||
|
is_constraint: bool,
|
||||||
|
) -> Result<Statement, ParserError> {
|
||||||
|
if !dialect_of!(self is PostgreSqlDialect | GenericDialect) {
|
||||||
|
self.prev_token();
|
||||||
|
return self.expected("an object type after CREATE", self.peek_token());
|
||||||
|
}
|
||||||
|
|
||||||
|
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)?;
|
||||||
|
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![];
|
||||||
|
if self.parse_keyword(Keyword::REFERENCING) {
|
||||||
|
while let Some(refer) = self.parse_trigger_referencing()? {
|
||||||
|
referencing.push(refer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()?;
|
||||||
|
|
||||||
|
self.expect_keyword(Keyword::EXECUTE)?;
|
||||||
|
|
||||||
|
let exec_body = self.parse_trigger_exec_body()?;
|
||||||
|
|
||||||
|
Ok(Statement::CreateTrigger {
|
||||||
|
or_replace,
|
||||||
|
is_constraint,
|
||||||
|
name,
|
||||||
|
period,
|
||||||
|
events,
|
||||||
|
table_name,
|
||||||
|
referenced_table_name,
|
||||||
|
referencing,
|
||||||
|
trigger_object,
|
||||||
|
include_each,
|
||||||
|
condition,
|
||||||
|
exec_body,
|
||||||
|
characteristics,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_trigger_period(&mut self) -> Result<TriggerPeriod, ParserError> {
|
||||||
|
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<TriggerEvent, ParserError> {
|
||||||
|
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) {
|
||||||
|
let cols = self.parse_comma_separated(|ident| {
|
||||||
|
Parser::parse_identifier(ident, false)
|
||||||
|
})?;
|
||||||
|
TriggerEvent::Update(cols)
|
||||||
|
} else {
|
||||||
|
TriggerEvent::Update(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Keyword::DELETE => TriggerEvent::Delete,
|
||||||
|
Keyword::TRUNCATE => TriggerEvent::Truncate,
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_trigger_referencing(&mut self) -> Result<Option<TriggerReferencing>, 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<TriggerExecBody, ParserError> {
|
||||||
|
Ok(TriggerExecBody {
|
||||||
|
exec_type: match self
|
||||||
|
.expect_one_of_keywords(&[Keyword::FUNCTION, Keyword::PROCEDURE])?
|
||||||
|
{
|
||||||
|
Keyword::FUNCTION => TriggerExecBodyType::Function,
|
||||||
|
Keyword::PROCEDURE => TriggerExecBodyType::Procedure,
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
func_desc: self.parse_function_desc()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_create_macro(
|
pub fn parse_create_macro(
|
||||||
&mut self,
|
&mut self,
|
||||||
or_replace: bool,
|
or_replace: bool,
|
||||||
|
@ -4509,9 +4706,11 @@ impl<'a> Parser<'a> {
|
||||||
return self.parse_drop_procedure();
|
return self.parse_drop_procedure();
|
||||||
} else if self.parse_keyword(Keyword::SECRET) {
|
} else if self.parse_keyword(Keyword::SECRET) {
|
||||||
return self.parse_drop_secret(temporary, persistent);
|
return self.parse_drop_secret(temporary, persistent);
|
||||||
|
} else if self.parse_keyword(Keyword::TRIGGER) {
|
||||||
|
return self.parse_drop_trigger();
|
||||||
} else {
|
} else {
|
||||||
return self.expected(
|
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(),
|
self.peek_token(),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -4550,7 +4749,7 @@ impl<'a> Parser<'a> {
|
||||||
/// ```
|
/// ```
|
||||||
fn parse_drop_function(&mut self) -> Result<Statement, ParserError> {
|
fn parse_drop_function(&mut self) -> Result<Statement, ParserError> {
|
||||||
let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);
|
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]) {
|
let option = match self.parse_one_of_keywords(&[Keyword::CASCADE, Keyword::RESTRICT]) {
|
||||||
Some(Keyword::CASCADE) => Some(ReferentialAction::Cascade),
|
Some(Keyword::CASCADE) => Some(ReferentialAction::Cascade),
|
||||||
Some(Keyword::RESTRICT) => Some(ReferentialAction::Restrict),
|
Some(Keyword::RESTRICT) => Some(ReferentialAction::Restrict),
|
||||||
|
@ -4569,7 +4768,7 @@ impl<'a> Parser<'a> {
|
||||||
/// ```
|
/// ```
|
||||||
fn parse_drop_procedure(&mut self) -> Result<Statement, ParserError> {
|
fn parse_drop_procedure(&mut self) -> Result<Statement, ParserError> {
|
||||||
let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);
|
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]) {
|
let option = match self.parse_one_of_keywords(&[Keyword::CASCADE, Keyword::RESTRICT]) {
|
||||||
Some(Keyword::CASCADE) => Some(ReferentialAction::Cascade),
|
Some(Keyword::CASCADE) => Some(ReferentialAction::Cascade),
|
||||||
Some(Keyword::RESTRICT) => Some(ReferentialAction::Restrict),
|
Some(Keyword::RESTRICT) => Some(ReferentialAction::Restrict),
|
||||||
|
@ -4583,7 +4782,7 @@ impl<'a> Parser<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_drop_function_desc(&mut self) -> Result<DropFunctionDesc, ParserError> {
|
fn parse_function_desc(&mut self) -> Result<FunctionDesc, ParserError> {
|
||||||
let name = self.parse_object_name(false)?;
|
let name = self.parse_object_name(false)?;
|
||||||
|
|
||||||
let args = if self.consume_token(&Token::LParen) {
|
let args = if self.consume_token(&Token::LParen) {
|
||||||
|
@ -4598,7 +4797,7 @@ impl<'a> Parser<'a> {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(DropFunctionDesc { name, args })
|
Ok(FunctionDesc { name, args })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See [DuckDB Docs](https://duckdb.org/docs/sql/statements/create_secret.html) for more details.
|
/// See [DuckDB Docs](https://duckdb.org/docs/sql/statements/create_secret.html) for more details.
|
||||||
|
@ -5882,11 +6081,7 @@ impl<'a> Parser<'a> {
|
||||||
pub fn parse_constraint_characteristics(
|
pub fn parse_constraint_characteristics(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> Result<Option<ConstraintCharacteristics>, ParserError> {
|
) -> Result<Option<ConstraintCharacteristics>, ParserError> {
|
||||||
let mut cc = ConstraintCharacteristics {
|
let mut cc = ConstraintCharacteristics::default();
|
||||||
deferrable: None,
|
|
||||||
initially: None,
|
|
||||||
enforced: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if cc.deferrable.is_none() && self.parse_keywords(&[Keyword::NOT, Keyword::DEFERRABLE])
|
if cc.deferrable.is_none() && self.parse_keywords(&[Keyword::NOT, Keyword::DEFERRABLE])
|
||||||
|
@ -7285,6 +7480,7 @@ impl<'a> Parser<'a> {
|
||||||
let field_defs = self.parse_click_house_tuple_def()?;
|
let field_defs = self.parse_click_house_tuple_def()?;
|
||||||
Ok(DataType::Tuple(field_defs))
|
Ok(DataType::Tuple(field_defs))
|
||||||
}
|
}
|
||||||
|
Keyword::TRIGGER => Ok(DataType::Trigger),
|
||||||
_ => {
|
_ => {
|
||||||
self.prev_token();
|
self.prev_token();
|
||||||
let type_name = self.parse_object_name(false)?;
|
let type_name = self.parse_object_name(false)?;
|
||||||
|
|
|
@ -124,6 +124,7 @@ impl TestedDialects {
|
||||||
}
|
}
|
||||||
|
|
||||||
let only_statement = statements.pop().unwrap();
|
let only_statement = statements.pop().unwrap();
|
||||||
|
|
||||||
if !canonical.is_empty() {
|
if !canonical.is_empty() {
|
||||||
assert_eq!(canonical, only_statement.to_string())
|
assert_eq!(canonical, only_statement.to_string())
|
||||||
}
|
}
|
||||||
|
|
|
@ -3623,7 +3623,7 @@ fn parse_drop_function() {
|
||||||
pg().verified_stmt(sql),
|
pg().verified_stmt(sql),
|
||||||
Statement::DropFunction {
|
Statement::DropFunction {
|
||||||
if_exists: true,
|
if_exists: true,
|
||||||
func_desc: vec![DropFunctionDesc {
|
func_desc: vec![FunctionDesc {
|
||||||
name: ObjectName(vec![Ident {
|
name: ObjectName(vec![Ident {
|
||||||
value: "test_func".to_string(),
|
value: "test_func".to_string(),
|
||||||
quote_style: None
|
quote_style: None
|
||||||
|
@ -3639,7 +3639,7 @@ fn parse_drop_function() {
|
||||||
pg().verified_stmt(sql),
|
pg().verified_stmt(sql),
|
||||||
Statement::DropFunction {
|
Statement::DropFunction {
|
||||||
if_exists: true,
|
if_exists: true,
|
||||||
func_desc: vec![DropFunctionDesc {
|
func_desc: vec![FunctionDesc {
|
||||||
name: ObjectName(vec![Ident {
|
name: ObjectName(vec![Ident {
|
||||||
value: "test_func".to_string(),
|
value: "test_func".to_string(),
|
||||||
quote_style: None
|
quote_style: None
|
||||||
|
@ -3664,7 +3664,7 @@ fn parse_drop_function() {
|
||||||
Statement::DropFunction {
|
Statement::DropFunction {
|
||||||
if_exists: true,
|
if_exists: true,
|
||||||
func_desc: vec![
|
func_desc: vec![
|
||||||
DropFunctionDesc {
|
FunctionDesc {
|
||||||
name: ObjectName(vec![Ident {
|
name: ObjectName(vec![Ident {
|
||||||
value: "test_func1".to_string(),
|
value: "test_func1".to_string(),
|
||||||
quote_style: None
|
quote_style: None
|
||||||
|
@ -3682,7 +3682,7 @@ fn parse_drop_function() {
|
||||||
}
|
}
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
DropFunctionDesc {
|
FunctionDesc {
|
||||||
name: ObjectName(vec![Ident {
|
name: ObjectName(vec![Ident {
|
||||||
value: "test_func2".to_string(),
|
value: "test_func2".to_string(),
|
||||||
quote_style: None
|
quote_style: None
|
||||||
|
@ -3713,7 +3713,7 @@ fn parse_drop_procedure() {
|
||||||
pg().verified_stmt(sql),
|
pg().verified_stmt(sql),
|
||||||
Statement::DropProcedure {
|
Statement::DropProcedure {
|
||||||
if_exists: true,
|
if_exists: true,
|
||||||
proc_desc: vec![DropFunctionDesc {
|
proc_desc: vec![FunctionDesc {
|
||||||
name: ObjectName(vec![Ident {
|
name: ObjectName(vec![Ident {
|
||||||
value: "test_proc".to_string(),
|
value: "test_proc".to_string(),
|
||||||
quote_style: None
|
quote_style: None
|
||||||
|
@ -3729,7 +3729,7 @@ fn parse_drop_procedure() {
|
||||||
pg().verified_stmt(sql),
|
pg().verified_stmt(sql),
|
||||||
Statement::DropProcedure {
|
Statement::DropProcedure {
|
||||||
if_exists: true,
|
if_exists: true,
|
||||||
proc_desc: vec![DropFunctionDesc {
|
proc_desc: vec![FunctionDesc {
|
||||||
name: ObjectName(vec![Ident {
|
name: ObjectName(vec![Ident {
|
||||||
value: "test_proc".to_string(),
|
value: "test_proc".to_string(),
|
||||||
quote_style: None
|
quote_style: None
|
||||||
|
@ -3754,7 +3754,7 @@ fn parse_drop_procedure() {
|
||||||
Statement::DropProcedure {
|
Statement::DropProcedure {
|
||||||
if_exists: true,
|
if_exists: true,
|
||||||
proc_desc: vec![
|
proc_desc: vec![
|
||||||
DropFunctionDesc {
|
FunctionDesc {
|
||||||
name: ObjectName(vec![Ident {
|
name: ObjectName(vec![Ident {
|
||||||
value: "test_proc1".to_string(),
|
value: "test_proc1".to_string(),
|
||||||
quote_style: None
|
quote_style: None
|
||||||
|
@ -3772,7 +3772,7 @@ fn parse_drop_procedure() {
|
||||||
}
|
}
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
DropFunctionDesc {
|
FunctionDesc {
|
||||||
name: ObjectName(vec![Ident {
|
name: ObjectName(vec![Ident {
|
||||||
value: "test_proc2".to_string(),
|
value: "test_proc2".to_string(),
|
||||||
quote_style: None
|
quote_style: None
|
||||||
|
@ -4455,6 +4455,478 @@ fn test_escaped_string_literal() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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: TriggerPeriod::After,
|
||||||
|
events: vec![TriggerEvent::Update(vec![])],
|
||||||
|
table_name: ObjectName(vec![Ident::new("accounts")]),
|
||||||
|
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(number("10000"))),
|
||||||
|
}))),
|
||||||
|
exec_body: TriggerExecBody {
|
||||||
|
exec_type: TriggerExecBodyType::Function,
|
||||||
|
func_desc: FunctionDesc {
|
||||||
|
name: ObjectName(vec![Ident::new("check_account_update")]),
|
||||||
|
args: None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
characteristics: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(pg().verified_stmt(sql), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 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]
|
||||||
|
/// 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: FOR, 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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 <https://www.postgresql.org/docs/current/plpgsql-trigger.html>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_unicode_string_literal() {
|
fn test_unicode_string_literal() {
|
||||||
let pairs = [
|
let pairs = [
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue