Skip to content

Commit c14f6e1

Browse files
committed
PostgreSQL: COMMENT ON CONSTRAINT / OPERATOR / RULE
Adds parser + AST coverage for the three remaining COMMENT ON shapes that pgmold-sqlparser had been silently rejecting: - `COMMENT ON CONSTRAINT name ON [DOMAIN] target IS '…'` - `COMMENT ON OPERATOR name (left, right) IS '…'` (each side may be NONE) - `COMMENT ON RULE name ON target IS '…'` `Statement::Comment` gains two fields: `operator_args: Option<CommentOperatorArgs>` (operand types for OPERATOR, modeled as `Option<DataType>` per side so unary `NONE` is preserved), and `on_domain: bool` (set when CONSTRAINT uses the `ON DOMAIN <domain>` form). The existing `arguments` and `table_name` fields keep their prior semantics for FUNCTION/PROCEDURE/AGGREGATE and TRIGGER/POLICY. Refactors `parse_drop_operator_signature` to share the new `parse_operator_arg_type_or_none` helper with `parse_comment`, since both parse the `{ DataType | NONE }` slot shape. Bumps fork to 0.63.0 and adds changelog/0.63.0.md.
1 parent f3200a2 commit c14f6e1

6 files changed

Lines changed: 328 additions & 22 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717

1818
[package]
1919
name = "pgmold-sqlparser"
20-
description = "Fork of sqlparser with additional PostgreSQL features (PARTITION OF, SECURITY DEFINER/INVOKER, SET params, EXCLUDE, TEXT SEARCH, AGGREGATE, FOREIGN TABLE/FDW, PUBLICATION, SUBSCRIPTION, ALTER DOMAIN/TRIGGER/EXTENSION, CAST, CONVERSION, LANGUAGE, RULE, STATISTICS, ACCESS METHOD, EVENT TRIGGER, TRANSFORM, SECURITY LABEL, USER MAPPING, TABLESPACE, GRANT ON TYPE/DOMAIN, COMMENT ON TRIGGER/AGGREGATE/POLICY, ALTER TYPE OWNER/SCHEMA/ATTRIBUTE, ALTER DEFAULT PRIVILEGES)"
21-
version = "0.62.0"
20+
description = "Fork of sqlparser with additional PostgreSQL features (PARTITION OF, SECURITY DEFINER/INVOKER, SET params, EXCLUDE, TEXT SEARCH, AGGREGATE, FOREIGN TABLE/FDW, PUBLICATION, SUBSCRIPTION, ALTER DOMAIN/TRIGGER/EXTENSION, CAST, CONVERSION, LANGUAGE, RULE, STATISTICS, ACCESS METHOD, EVENT TRIGGER, TRANSFORM, SECURITY LABEL, USER MAPPING, TABLESPACE, GRANT ON TYPE/DOMAIN, COMMENT ON TRIGGER/AGGREGATE/POLICY/CONSTRAINT/OPERATOR/RULE, ALTER TYPE OWNER/SCHEMA/ATTRIBUTE, ALTER DEFAULT PRIVILEGES)"
21+
version = "0.63.0"
2222
authors = ["Filipe Guerreiro <filipe.m.guerreiro@gmail.com>"]
2323
homepage = "https://github.com/fmguerreiro/datafusion-sqlparser-rs"
2424
documentation = "https://docs.rs/pgmold-sqlparser/"

changelog/0.63.0.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<!--
2+
Licensed to the Apache Software Foundation (ASF) under one
3+
or more contributor license agreements. See the NOTICE file
4+
distributed with this work for additional information
5+
regarding copyright ownership. The ASF licenses this file
6+
to you under the Apache License, Version 2.0 (the
7+
"License"); you may not use this file except in compliance
8+
with the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing,
13+
software distributed under the License is distributed on an
14+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
KIND, either express or implied. See the License for the
16+
specific language governing permissions and limitations
17+
under the License.
18+
-->
19+
20+
# pgmold-sqlparser 0.63.0 Changelog
21+
22+
Fork-only release. Covers fork-side changes since 0.62.0; no upstream sync.
23+
24+
**Breaking changes:**
25+
26+
- PostgreSQL: `Statement::Comment` gains two fields, `operator_args: Option<CommentOperatorArgs>` (operand types for `COMMENT ON OPERATOR`, each side `Option<DataType>` to model `NONE` for unary operators) and `on_domain: bool` (set when `COMMENT ON CONSTRAINT … ON DOMAIN <domain>` is parsed). Existing destructures must be updated.
27+
28+
**Other:**
29+
30+
- PostgreSQL: `COMMENT ON CONSTRAINT name ON [DOMAIN] target IS …` is now parsed into `CommentObject::Constraint` with `table_name` carrying the relation/domain and `on_domain` distinguishing the two forms.
31+
- PostgreSQL: `COMMENT ON OPERATOR name (left, right) IS …` is now parsed into `CommentObject::Operator` with `operator_args` carrying the operand signature; `NONE` is preserved as `None` for prefix/postfix unary operators.
32+
- PostgreSQL: `COMMENT ON RULE name ON target IS …` is now parsed into `CommentObject::Rule` with `table_name` carrying the target relation.

src/ast/mod.rs

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2467,6 +2467,8 @@ pub enum CommentObject {
24672467
Collation,
24682468
/// A table column.
24692469
Column,
2470+
/// A table or domain constraint.
2471+
Constraint,
24702472
/// A database.
24712473
Database,
24722474
/// A domain.
@@ -2479,12 +2481,16 @@ pub enum CommentObject {
24792481
Index,
24802482
/// A materialized view.
24812483
MaterializedView,
2484+
/// A user-defined operator.
2485+
Operator,
24822486
/// A row-level security policy.
24832487
Policy,
24842488
/// A procedure.
24852489
Procedure,
24862490
/// A role.
24872491
Role,
2492+
/// A query rewrite rule.
2493+
Rule,
24882494
/// A schema.
24892495
Schema,
24902496
/// A sequence.
@@ -2507,15 +2513,18 @@ impl CommentObject {
25072513
CommentObject::Aggregate => "AGGREGATE",
25082514
CommentObject::Collation => "COLLATION",
25092515
CommentObject::Column => "COLUMN",
2516+
CommentObject::Constraint => "CONSTRAINT",
25102517
CommentObject::Database => "DATABASE",
25112518
CommentObject::Domain => "DOMAIN",
25122519
CommentObject::Extension => "EXTENSION",
25132520
CommentObject::Function => "FUNCTION",
25142521
CommentObject::Index => "INDEX",
25152522
CommentObject::MaterializedView => "MATERIALIZED VIEW",
2523+
CommentObject::Operator => "OPERATOR",
25162524
CommentObject::Policy => "POLICY",
25172525
CommentObject::Procedure => "PROCEDURE",
25182526
CommentObject::Role => "ROLE",
2527+
CommentObject::Rule => "RULE",
25192528
CommentObject::Schema => "SCHEMA",
25202529
CommentObject::Sequence => "SEQUENCE",
25212530
CommentObject::Table => "TABLE",
@@ -2535,14 +2544,17 @@ impl CommentObject {
25352544
Keyword::AGGREGATE => CommentObject::Aggregate,
25362545
Keyword::COLLATION => CommentObject::Collation,
25372546
Keyword::COLUMN => CommentObject::Column,
2547+
Keyword::CONSTRAINT => CommentObject::Constraint,
25382548
Keyword::DATABASE => CommentObject::Database,
25392549
Keyword::DOMAIN => CommentObject::Domain,
25402550
Keyword::EXTENSION => CommentObject::Extension,
25412551
Keyword::FUNCTION => CommentObject::Function,
25422552
Keyword::INDEX => CommentObject::Index,
2553+
Keyword::OPERATOR => CommentObject::Operator,
25432554
Keyword::POLICY => CommentObject::Policy,
25442555
Keyword::PROCEDURE => CommentObject::Procedure,
25452556
Keyword::ROLE => CommentObject::Role,
2557+
Keyword::RULE => CommentObject::Rule,
25462558
Keyword::SCHEMA => CommentObject::Schema,
25472559
Keyword::SEQUENCE => CommentObject::Sequence,
25482560
Keyword::TABLE => CommentObject::Table,
@@ -2561,6 +2573,38 @@ impl fmt::Display for CommentObject {
25612573
}
25622574
}
25632575

2576+
/// Operand types for `COMMENT ON OPERATOR name (left, right)`.
2577+
///
2578+
/// Each side may be `NONE` for prefix or postfix unary operators, mirroring
2579+
/// the syntax allowed by `DROP OPERATOR` / `ALTER OPERATOR`. Both sides are
2580+
/// optional independently because PostgreSQL accepts unary operators in
2581+
/// either direction.
2582+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2583+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2584+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
2585+
pub struct CommentOperatorArgs {
2586+
/// Left-hand operand type, or `None` for `NONE` (prefix operator).
2587+
pub left: Option<DataType>,
2588+
/// Right-hand operand type, or `None` for `NONE` (postfix operator).
2589+
pub right: Option<DataType>,
2590+
}
2591+
2592+
impl fmt::Display for CommentOperatorArgs {
2593+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2594+
let write_side = |opt: &Option<DataType>, f: &mut fmt::Formatter| -> fmt::Result {
2595+
match opt {
2596+
Some(dt) => write!(f, "{dt}"),
2597+
None => f.write_str("NONE"),
2598+
}
2599+
};
2600+
f.write_str("(")?;
2601+
write_side(&self.left, f)?;
2602+
f.write_str(", ")?;
2603+
write_side(&self.right, f)?;
2604+
f.write_str(")")
2605+
}
2606+
}
2607+
25642608
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
25652609
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
25662610
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
@@ -4461,10 +4505,21 @@ pub enum Statement {
44614505
/// while `None` means no parameter list was provided. Used for
44624506
/// `FUNCTION`, `PROCEDURE`, and `AGGREGATE` targets.
44634507
arguments: Option<Vec<DataType>>,
4464-
/// Partner table for objects scoped to a table, i.e. the
4465-
/// `ON <table>` tail in `COMMENT ON TRIGGER t ON tbl IS '…'` or
4466-
/// `COMMENT ON POLICY p ON tbl IS '…'`.
4508+
/// Operand signature for `COMMENT ON OPERATOR name (left, right)`.
4509+
/// Always `Some(_)` for `Operator`, `None` for every other variant.
4510+
/// Modeled separately from `arguments` because operator slots may be
4511+
/// `NONE` (unary operators), which `Vec<DataType>` cannot express.
4512+
operator_args: Option<CommentOperatorArgs>,
4513+
/// Partner relation for objects scoped to a relation, i.e. the
4514+
/// `ON <table>` (or `ON DOMAIN <domain>`) tail in
4515+
/// `COMMENT ON TRIGGER t ON tbl IS '…'`,
4516+
/// `COMMENT ON POLICY p ON tbl IS '…'`,
4517+
/// `COMMENT ON RULE r ON tbl IS '…'`, or
4518+
/// `COMMENT ON CONSTRAINT c ON [DOMAIN] tbl IS '…'`.
44674519
table_name: Option<ObjectName>,
4520+
/// `true` when the relation tail used the `ON DOMAIN <domain>` form.
4521+
/// Only meaningful for `Constraint`; always `false` otherwise.
4522+
on_domain: bool,
44684523
/// Optional comment text (None to remove comment).
44694524
comment: Option<String>,
44704525
/// An optional `IF EXISTS` clause. (Non-standard.)
@@ -6252,7 +6307,9 @@ impl fmt::Display for Statement {
62526307
object_type,
62536308
object_name,
62546309
arguments,
6310+
operator_args,
62556311
table_name,
6312+
on_domain,
62566313
comment,
62576314
if_exists,
62586315
} => {
@@ -6264,8 +6321,12 @@ impl fmt::Display for Statement {
62646321
if let Some(args) = arguments {
62656322
write!(f, "({})", display_comma_separated(args))?;
62666323
}
6324+
if let Some(operator_args) = operator_args {
6325+
write!(f, "{operator_args}")?;
6326+
}
62676327
if let Some(table_name) = table_name {
6268-
write!(f, " ON {table_name}")?;
6328+
let prefix = if *on_domain { " ON DOMAIN " } else { " ON " };
6329+
write!(f, "{prefix}{table_name}")?;
62696330
}
62706331
write!(f, " IS ")?;
62716332
if let Some(c) = comment {

src/parser/mod.rs

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -917,7 +917,11 @@ impl<'a> Parser<'a> {
917917
},
918918
None => return self.expected("comment object_type", token),
919919
};
920-
let object_name = self.parse_object_name(false)?;
920+
let object_name = if object_type == CommentObject::Operator {
921+
self.parse_operator_name()?
922+
} else {
923+
self.parse_object_name(false)?
924+
};
921925

922926
let arguments = match object_type {
923927
CommentObject::Function | CommentObject::Procedure | CommentObject::Aggregate => {
@@ -939,12 +943,28 @@ impl<'a> Parser<'a> {
939943
));
940944
}
941945

942-
let table_name = match object_type {
943-
CommentObject::Trigger | CommentObject::Policy => {
946+
let operator_args = if object_type == CommentObject::Operator {
947+
self.expect_token(&Token::LParen)?;
948+
let left = self.parse_operator_arg_type_or_none()?;
949+
self.expect_token(&Token::Comma)?;
950+
let right = self.parse_operator_arg_type_or_none()?;
951+
self.expect_token(&Token::RParen)?;
952+
Some(CommentOperatorArgs { left, right })
953+
} else {
954+
None
955+
};
956+
957+
let (table_name, on_domain) = match object_type {
958+
CommentObject::Trigger | CommentObject::Policy | CommentObject::Rule => {
944959
self.expect_keyword_is(Keyword::ON)?;
945-
Some(self.parse_object_name(false)?)
960+
(Some(self.parse_object_name(false)?), false)
946961
}
947-
_ => None,
962+
CommentObject::Constraint => {
963+
self.expect_keyword_is(Keyword::ON)?;
964+
let on_domain = self.parse_keyword(Keyword::DOMAIN);
965+
(Some(self.parse_object_name(false)?), on_domain)
966+
}
967+
_ => (None, false),
948968
};
949969

950970
self.expect_keyword_is(Keyword::IS)?;
@@ -957,7 +977,9 @@ impl<'a> Parser<'a> {
957977
object_type,
958978
object_name,
959979
arguments,
980+
operator_args,
960981
table_name,
982+
on_domain,
961983
comment,
962984
if_exists,
963985
})
@@ -8532,19 +8554,9 @@ impl<'a> Parser<'a> {
85328554
fn parse_drop_operator_signature(&mut self) -> Result<DropOperatorSignature, ParserError> {
85338555
let name = self.parse_operator_name()?;
85348556
self.expect_token(&Token::LParen)?;
8535-
8536-
// Parse left operand type (or NONE for prefix operators)
8537-
let left_type = if self.parse_keyword(Keyword::NONE) {
8538-
None
8539-
} else {
8540-
Some(self.parse_data_type()?)
8541-
};
8542-
8557+
let left_type = self.parse_operator_arg_type_or_none()?;
85438558
self.expect_token(&Token::Comma)?;
8544-
8545-
// Parse right operand type (always required)
85468559
let right_type = self.parse_data_type()?;
8547-
85488560
self.expect_token(&Token::RParen)?;
85498561

85508562
Ok(DropOperatorSignature {
@@ -8554,6 +8566,16 @@ impl<'a> Parser<'a> {
85548566
})
85558567
}
85568568

8569+
/// Parse one slot of a PostgreSQL operator signature: a `DataType` or the
8570+
/// keyword `NONE`. Used by `DROP OPERATOR` and `COMMENT ON OPERATOR`.
8571+
fn parse_operator_arg_type_or_none(&mut self) -> Result<Option<DataType>, ParserError> {
8572+
if self.parse_keyword(Keyword::NONE) {
8573+
Ok(None)
8574+
} else {
8575+
Ok(Some(self.parse_data_type()?))
8576+
}
8577+
}
8578+
85578579
/// Parse a [Statement::DropOperatorFamily]
85588580
///
85598581
/// [PostgreSQL Documentation](https://www.postgresql.org/docs/current/sql-dropopfamily.html)

tests/sqlparser_common.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15368,6 +15368,9 @@ fn parse_comments() {
1536815368
}
1536915369

1537015370
// https://www.postgresql.org/docs/current/sql-comment.html
15371+
// CONSTRAINT, OPERATOR, RULE require object-specific tails handled in
15372+
// their own tests; this table only covers `COMMENT ON <KEYWORD> name IS '…'`
15373+
// shapes that share the simple object_name + comment structure.
1537115374
let object_types = [
1537215375
("COLLATION", CommentObject::Collation),
1537315376
("COLUMN", CommentObject::Column),

0 commit comments

Comments
 (0)