Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
148 changes: 144 additions & 4 deletions src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.

//! AST types specific to CREATE/ALTER variants of [`Statement`](crate::ast::Statement)
//! AST types specific to CREATE/ALTER variants of [`Statement`]
//! (commonly referred to as Data Definition Language, or DDL)

#[cfg(not(feature = "std"))]
Expand Down Expand Up @@ -48,9 +48,9 @@ use crate::ast::{
HiveFormat, HiveIOFormat, HiveRowFormat, HiveSetLocation, Ident, InitializeKind,
MySQLColumnPosition, ObjectName, OnCommit, OneOrManyWithParens, OperateFunctionArg,
OrderByExpr, ProjectionSelect, Query, RefreshModeKind, ResetConfig, RowAccessPolicy,
SequenceOptions, Spanned, SqlOption, StorageLifecyclePolicy, StorageSerializationPolicy,
TableVersion, Tag, TriggerEvent, TriggerExecBody, TriggerObject, TriggerPeriod,
TriggerReferencing, Value, ValueWithSpan, WrappedCollection,
SequenceOptions, Spanned, SqlOption, Statement, StorageLifecyclePolicy,
StorageSerializationPolicy, TableVersion, Tag, TriggerEvent, TriggerExecBody, TriggerObject,
TriggerPeriod, TriggerReferencing, Value, ValueWithSpan, WrappedCollection,
};
use crate::display_utils::{DisplayCommaSeparated, Indent, NewLine, SpaceOrNewline};
use crate::keywords::Keyword;
Expand Down Expand Up @@ -5994,3 +5994,143 @@ impl From<AlterPolicy> for crate::ast::Statement {
crate::ast::Statement::AlterPolicy(v)
}
}

/// CREATE RULE statement.
///
/// ```sql
/// CREATE [ OR REPLACE ] RULE name AS ON event
/// TO table_name [ WHERE condition ]
/// DO [ ALSO | INSTEAD ] { NOTHING | command | ( command ; command ... ) }
/// ```
///
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createrule.html)
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct CreateRule {
/// `true` when `OR REPLACE` was present.
pub or_replace: bool,
/// Name of the rule.
pub name: Ident,
/// Event the rule fires on (`SELECT`, `INSERT`, `UPDATE` or `DELETE`).
pub event: CreateRuleEvent,
/// Table the rule is defined on.
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
pub table_name: ObjectName,
/// Optional expression for the `WHERE` clause.
pub condition: Option<Expr>,
/// Optional `ALSO` or `INSTEAD` keyword following `DO`.
pub do_kind: Option<CreateRuleDoKind>,
/// Action the rule performs.
pub action: CreateRuleAction,
}

impl fmt::Display for CreateRule {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"CREATE {or_replace}RULE {name} AS ON {event} TO {table_name}",
or_replace = if self.or_replace { "OR REPLACE " } else { "" },
name = self.name,
event = self.event,
table_name = self.table_name,
)?;
if let Some(condition) = &self.condition {
write!(f, " WHERE {condition}")?;
}
write!(f, " DO")?;
if let Some(do_kind) = &self.do_kind {
write!(f, " {do_kind}")?;
}
write!(f, " {}", self.action)
}
}

/// Event that fires a rule (`ON` clause).
/// ```sql
/// AS ON [SELECT | INSERT | UPDATE | DELETE]
/// ```
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createrule.html)
#[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 CreateRuleEvent {
/// Fires on SELECT.
Select,
/// Fires on INSERT.
Insert,
/// Fires on UPDATE.
Update,
/// Fires on DELETE.
Delete,
}

impl fmt::Display for CreateRuleEvent {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CreateRuleEvent::Select => write!(f, "SELECT"),
CreateRuleEvent::Insert => write!(f, "INSERT"),
CreateRuleEvent::Update => write!(f, "UPDATE"),
CreateRuleEvent::Delete => write!(f, "DELETE"),
}
}
}

/// Keyword following `DO` in a `CREATE RULE` statement.
/// ```sql
/// DO [ ALSO | INSTEAD ]
/// ```
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createrule.html)
#[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 CreateRuleDoKind {
/// The action runs in addition to the original statement.
Also,
/// The action runs instead of the original statement.
Instead,
}

impl fmt::Display for CreateRuleDoKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CreateRuleDoKind::Also => write!(f, "ALSO"),
CreateRuleDoKind::Instead => write!(f, "INSTEAD"),
}
}
}

/// Action of a `CREATE RULE` statement.
/// ```sql
/// { NOTHING | command | ( command ; command ... ) }
/// ```
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createrule.html)
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum CreateRuleAction {
/// `NOTHING`
Nothing,
/// A single command, e.g. `DO INSTEAD SELECT * FROM t`.
Statement(Box<Statement>),
/// A parenthesized, semicolon-separated list of commands.
Statements(Vec<Statement>),
}

impl fmt::Display for CreateRuleAction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CreateRuleAction::Nothing => write!(f, "NOTHING"),
CreateRuleAction::Statement(statement) => write!(f, "{statement}"),
CreateRuleAction::Statements(statements) => {
write!(f, "({})", display_separated(statements, "; "))
}
}
}
}

impl From<CreateRule> for crate::ast::Statement {
fn from(v: CreateRule) -> Self {
crate::ast::Statement::CreateRule(v)
}
}
25 changes: 16 additions & 9 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,16 @@ pub use self::ddl::{
ConstraintCharacteristics, CreateCollation, CreateCollationDefinition, CreateConnector,
CreateDomain, CreateExtension, CreateFunction, CreateIndex, CreateOperator,
CreateOperatorClass, CreateOperatorFamily, CreatePolicy, CreatePolicyCommand, CreatePolicyType,
CreateTable, CreateTextSearch, CreateTrigger, CreateView, Deduplicate, DeferrableInitial,
DistStyle, DropBehavior, DropExtension, DropFunction, DropOperator, DropOperatorClass,
DropOperatorFamily, DropOperatorSignature, DropPolicy, DropTrigger, ForValues,
FunctionReturnType, GeneratedAs, GeneratedExpressionMode, IdentityParameters, IdentityProperty,
IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, IndexColumn,
IndexOption, IndexType, KeyOrIndexDisplay, Msck, NullsDistinctOption, OperatorArgTypes,
OperatorClassItem, OperatorFamilyDropItem, OperatorFamilyItem, OperatorOption, OperatorPurpose,
Owner, Partition, PartitionBoundValue, ProcedureParam, ReferentialAction, RenameTableNameKind,
ReplicaIdentity, TagsColumnOption, TextSearchObjectType, TriggerObjectKind, Truncate,
CreateRule, CreateRuleAction, CreateRuleDoKind, CreateRuleEvent, CreateTable, CreateTextSearch,
CreateTrigger, CreateView, Deduplicate, DeferrableInitial, DistStyle, DropBehavior,
DropExtension, DropFunction, DropOperator, DropOperatorClass, DropOperatorFamily,
DropOperatorSignature, DropPolicy, DropTrigger, ForValues, FunctionReturnType, GeneratedAs,
GeneratedExpressionMode, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind,
IdentityPropertyKind, IdentityPropertyOrder, IndexColumn, IndexOption, IndexType,
KeyOrIndexDisplay, Msck, NullsDistinctOption, OperatorArgTypes, OperatorClassItem,
OperatorFamilyDropItem, OperatorFamilyItem, OperatorOption, OperatorPurpose, Owner, Partition,
PartitionBoundValue, ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity,
TagsColumnOption, TextSearchObjectType, TriggerObjectKind, Truncate,
UserDefinedTypeCompositeAttributeDef, UserDefinedTypeInternalLength,
UserDefinedTypeRangeOption, UserDefinedTypeRepresentation, UserDefinedTypeSqlDefinitionOption,
UserDefinedTypeStorage, ViewColumnDef, WithData,
Expand Down Expand Up @@ -3775,6 +3776,11 @@ pub enum Statement {
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createpolicy.html)
CreatePolicy(CreatePolicy),
/// ```sql
/// CREATE RULE
/// ```
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createrule.html)
CreateRule(CreateRule),
/// ```sql
/// CREATE CONNECTOR
/// ```
/// See [Hive](https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=27362034#LanguageManualDDL-CreateDataConnectorCreateConnector)
Expand Down Expand Up @@ -5630,6 +5636,7 @@ impl fmt::Display for Statement {
write!(f, "{stmt}")
}
Statement::CreatePolicy(policy) => write!(f, "{policy}"),
Statement::CreateRule(rule) => write!(f, "{rule}"),
Statement::CreateConnector(create_connector) => create_connector.fmt(f),
Statement::CreateOperator(create_operator) => create_operator.fmt(f),
Statement::CreateOperatorFamily(create_operator_family) => {
Expand Down
1 change: 1 addition & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@ impl Spanned for Statement {
Statement::Unload { .. } => Span::empty(),
Statement::OptimizeTable { .. } => Span::empty(),
Statement::CreatePolicy { .. } => Span::empty(),
Statement::CreateRule { .. } => Span::empty(),
Statement::AlterPolicy { .. } => Span::empty(),
Statement::AlterConnector { .. } => Span::empty(),
Statement::DropPolicy { .. } => Span::empty(),
Expand Down
1 change: 1 addition & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ define_keywords!(
ALL,
ALLOCATE,
ALLOWOVERWRITE,
ALSO,
ALTER,
ALWAYS,
ANALYZE,
Expand Down
78 changes: 78 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5266,6 +5266,8 @@ impl<'a> Parser<'a> {
.map(Into::into)
} else if self.parse_keyword(Keyword::POLICY) {
self.parse_create_policy().map(Into::into)
} else if self.parse_keyword(Keyword::RULE) {
self.parse_create_rule(or_replace).map(Into::into)
} else if self.parse_keyword(Keyword::EXTERNAL) {
self.parse_create_external_table(or_replace).map(Into::into)
} else if self.parse_keyword(Keyword::FUNCTION) {
Expand Down Expand Up @@ -7244,6 +7246,82 @@ impl<'a> Parser<'a> {
})
}

/// ```sql
/// CREATE [ OR REPLACE ] RULE name AS ON event
/// TO table_name [ WHERE condition ]
/// DO [ ALSO | INSTEAD ] { NOTHING | command | ( command ; command ... ) }
/// ```
///
/// [PostgreSQL Documentation](https://www.postgresql.org/docs/current/sql-createrule.html)
pub fn parse_create_rule(&mut self, or_replace: bool) -> Result<CreateRule, ParserError> {
let name = self.parse_identifier()?;
self.expect_keyword_is(Keyword::AS)?;
self.expect_keyword_is(Keyword::ON)?;
let event = match self.expect_one_of_keywords(&[
Keyword::SELECT,
Keyword::INSERT,
Keyword::UPDATE,
Keyword::DELETE,
])? {
Keyword::SELECT => CreateRuleEvent::Select,
Keyword::INSERT => CreateRuleEvent::Insert,
Keyword::UPDATE => CreateRuleEvent::Update,
Keyword::DELETE => CreateRuleEvent::Delete,
unexpected_keyword => {
return Err(ParserError::ParserError(format!(
"Internal parser error: unexpected keyword `{unexpected_keyword}` in rule event"
)))
}
};
self.expect_keyword_is(Keyword::TO)?;
let table_name = self.parse_object_name(false)?;

let condition = if self.parse_keyword(Keyword::WHERE) {
Some(self.parse_expr()?)
} else {
None
};

self.expect_keyword_is(Keyword::DO)?;
let do_kind = if self.parse_keyword(Keyword::ALSO) {
Some(CreateRuleDoKind::Also)
} else if self.parse_keyword(Keyword::INSTEAD) {
Some(CreateRuleDoKind::Instead)
} else {
None
};

let action = if self.parse_keyword(Keyword::NOTHING) {
CreateRuleAction::Nothing
} else if self.consume_token(&Token::LParen) {
let mut statements = Vec::new();
loop {
while self.consume_token(&Token::SemiColon) {}
if self.consume_token(&Token::RParen) {
break;
}
statements.push(self.parse_statement()?);
if !self.consume_token(&Token::SemiColon) {
self.expect_token(&Token::RParen)?;
break;
}
}
CreateRuleAction::Statements(statements)
} else {
CreateRuleAction::Statement(Box::new(self.parse_statement()?))
};

Ok(CreateRule {
or_replace,
name,
event,
table_name,
condition,
do_kind,
action,
})
}

/// ```sql
/// CREATE CONNECTOR [IF NOT EXISTS] connector_name
/// [TYPE datasource_type]
Expand Down
68 changes: 68 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19679,3 +19679,71 @@ fn parse_function_arg_call_chain_no_exponential_blowup() {
rx.recv_timeout(Duration::from_secs(5))
.expect("parser should reject this quickly, not loop exponentially");
}

#[test]
fn parse_create_rule() {
let sql = "CREATE RULE r AS ON DELETE TO docs DO INSTEAD NOTHING";
match all_dialects().verified_stmt(sql) {
Statement::CreateRule(CreateRule {
or_replace,
name,
event,
table_name,
condition,
do_kind,
action,
}) => {
assert!(!or_replace);
assert_eq!(name.to_string(), "r");
assert_eq!(event, CreateRuleEvent::Delete);
assert_eq!(table_name.to_string(), "docs");
assert!(condition.is_none());
assert_eq!(do_kind, Some(CreateRuleDoKind::Instead));
assert_eq!(action, CreateRuleAction::Nothing);
}
_ => unreachable!("Expected CREATE RULE"),
}

all_dialects().verified_stmt("CREATE OR REPLACE RULE r AS ON SELECT TO t DO ALSO NOTHING");
all_dialects().verified_stmt("CREATE RULE r AS ON INSERT TO s1.t DO NOTHING");
all_dialects().verified_stmt(
"CREATE RULE r AS ON UPDATE TO t WHERE quantity > 100 DO ALSO INSERT INTO audit (id) VALUES (1)",
);
all_dialects().verified_stmt(
"CREATE RULE r AS ON DELETE TO t DO INSTEAD (UPDATE t2 SET deleted = 1 WHERE id = 1; DELETE FROM t3)",
);

// A trailing semicolon inside a parenthesized action list is accepted and
// normalized away.
all_dialects().one_statement_parses_to(
"CREATE RULE r AS ON DELETE TO t DO INSTEAD (DELETE FROM t2;)",
"CREATE RULE r AS ON DELETE TO t DO INSTEAD (DELETE FROM t2)",
);

// invalid event
assert_eq!(
all_dialects()
.parse_sql_statements("CREATE RULE r AS ON TRUNCATE TO t DO NOTHING")
.unwrap_err()
.to_string(),
"sql parser error: Expected: one of SELECT or INSERT or UPDATE or DELETE, found: TRUNCATE"
);

// missing DO clause
assert_eq!(
all_dialects()
.parse_sql_statements("CREATE RULE r AS ON DELETE TO t")
.unwrap_err()
.to_string(),
"sql parser error: Expected: DO, found: EOF"
);

// missing table name
assert_eq!(
all_dialects()
.parse_sql_statements("CREATE RULE r AS ON DELETE DO NOTHING")
.unwrap_err()
.to_string(),
"sql parser error: Expected: TO, found: DO"
);
}
Loading
Loading