agent_patch
runs/2026-02-28__18-27-22__gpt-5-4/flux-pr-2096/flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-4/agent-logs/agent.patch
22201 bytes
diff --git a/tmp/agent-patch-flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/mod.rs b/app/src/ast/mod.rs
index aa3fb08..ae0d6fa 100644
--- a/tmp/agent-patch-flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/mod.rs
+++ b/app/src/ast/mod.rs
@@ -3327,6 +3327,18 @@ pub enum Statement {
},
/// A `CREATE SERVER` statement.
CreateServer(CreateServerStatement),
+ /// `CREATE OPERATOR`
+ ///
+ /// Note: this is a PostgreSQL-specific statement.
+ CreateOperator(CreateOperator),
+ /// `CREATE OPERATOR FAMILY`
+ ///
+ /// Note: this is a PostgreSQL-specific statement.
+ CreateOperatorFamily(CreateOperatorFamily),
+ /// `CREATE OPERATOR CLASS`
+ ///
+ /// Note: this is a PostgreSQL-specific statement.
+ CreateOperatorClass(CreateOperatorClass),
/// ```sql
/// CREATE POLICY
/// ```
@@ -4858,6 +4870,9 @@ impl fmt::Display for Statement {
Statement::CreateServer(stmt) => {
write!(f, "{stmt}")
}
+ Statement::CreateOperator(stmt) => write!(f, "{stmt}"),
+ Statement::CreateOperatorFamily(stmt) => write!(f, "{stmt}"),
+ Statement::CreateOperatorClass(stmt) => write!(f, "{stmt}"),
Statement::CreatePolicy {
name,
table_name,
@@ -7897,6 +7912,192 @@ impl fmt::Display for CreateServerOption {
}
}
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct PgOperatorName(pub Vec<String>);
+
+impl fmt::Display for PgOperatorName {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", display_separated(&self.0, "."))
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct CreateOperator {
+ pub name: PgOperatorName,
+ pub procedure: ObjectName,
+ pub left_arg: Option<PgOperatorArgument>,
+ pub right_arg: Option<PgOperatorArgument>,
+ pub commutator: Option<PgOperatorName>,
+ pub negator: Option<PgOperatorName>,
+ pub restrict: Option<ObjectName>,
+ pub join: Option<ObjectName>,
+ pub hashes: bool,
+ pub merges: bool,
+}
+
+impl fmt::Display for CreateOperator {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut options = vec![format!("PROCEDURE = {}", self.procedure)];
+
+ if let Some(left_arg) = &self.left_arg {
+ options.push(format!("LEFTARG = {left_arg}"));
+ }
+ if let Some(right_arg) = &self.right_arg {
+ options.push(format!("RIGHTARG = {right_arg}"));
+ }
+ if let Some(commutator) = &self.commutator {
+ options.push(format!("COMMUTATOR = {commutator}"));
+ }
+ if let Some(negator) = &self.negator {
+ options.push(format!("NEGATOR = {negator}"));
+ }
+ if let Some(restrict) = &self.restrict {
+ options.push(format!("RESTRICT = {restrict}"));
+ }
+ if let Some(join) = &self.join {
+ options.push(format!("JOIN = {join}"));
+ }
+ if self.hashes {
+ options.push("HASHES".to_string());
+ }
+ if self.merges {
+ options.push("MERGES".to_string());
+ }
+
+ write!(f, "CREATE OPERATOR {} ({})", self.name, options.join(", "))
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum PgOperatorArgument {
+ None,
+ Type(DataType),
+}
+
+impl fmt::Display for PgOperatorArgument {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ PgOperatorArgument::None => f.write_str("NONE"),
+ PgOperatorArgument::Type(data_type) => write!(f, "{data_type}"),
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct CreateOperatorFamily {
+ pub name: ObjectName,
+ pub using: Ident,
+}
+
+impl fmt::Display for CreateOperatorFamily {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "CREATE OPERATOR FAMILY {} USING {}", self.name, self.using)
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct CreateOperatorClass {
+ pub name: ObjectName,
+ pub default: bool,
+ pub data_type: DataType,
+ pub using: Ident,
+ pub family: Option<ObjectName>,
+ pub items: Vec<CreateOperatorClassItem>,
+}
+
+impl fmt::Display for CreateOperatorClass {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "CREATE OPERATOR CLASS {}{} FOR TYPE {} USING {}",
+ self.name,
+ if self.default { " DEFAULT" } else { "" },
+ self.data_type,
+ self.using
+ )?;
+
+ if let Some(family) = &self.family {
+ write!(f, " FAMILY {family}")?;
+ }
+
+ write!(f, " AS {}", display_comma_separated(&self.items))
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum CreateOperatorClassItem {
+ Operator {
+ strategy: u64,
+ name: PgOperatorName,
+ operand_types: Option<Vec<DataType>>,
+ for_search: bool,
+ sort_family: Option<ObjectName>,
+ },
+ Function {
+ support: u64,
+ operand_types: Option<Vec<DataType>>,
+ function_name: ObjectName,
+ arguments: Vec<DataType>,
+ },
+ Storage(DataType),
+}
+
+impl fmt::Display for CreateOperatorClassItem {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ CreateOperatorClassItem::Operator {
+ strategy,
+ name,
+ operand_types,
+ for_search,
+ sort_family,
+ } => {
+ write!(f, "OPERATOR {strategy} {name}")?;
+ if let Some(operand_types) = operand_types {
+ write!(f, " ({})", display_comma_separated(operand_types))?;
+ }
+ if *for_search {
+ write!(f, " FOR SEARCH")?;
+ }
+ if let Some(sort_family) = sort_family {
+ write!(f, " FOR ORDER BY {sort_family}")?;
+ }
+ Ok(())
+ }
+ CreateOperatorClassItem::Function {
+ support,
+ operand_types,
+ function_name,
+ arguments,
+ } => {
+ write!(f, "FUNCTION {support}")?;
+ if let Some(operand_types) = operand_types {
+ write!(f, " ({})", display_comma_separated(operand_types))?;
+ }
+ write!(
+ f,
+ " {}({})",
+ function_name,
+ display_comma_separated(arguments)
+ )
+ }
+ CreateOperatorClassItem::Storage(data_type) => write!(f, "STORAGE {data_type}"),
+ }
+ }
+}
+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
@@ -10733,6 +10934,24 @@ impl From<CreateServerStatement> for Statement {
}
}
+impl From<CreateOperator> for Statement {
+ fn from(c: CreateOperator) -> Self {
+ Self::CreateOperator(c)
+ }
+}
+
+impl From<CreateOperatorFamily> for Statement {
+ fn from(c: CreateOperatorFamily) -> Self {
+ Self::CreateOperatorFamily(c)
+ }
+}
+
+impl From<CreateOperatorClass> for Statement {
+ fn from(c: CreateOperatorClass) -> Self {
+ Self::CreateOperatorClass(c)
+ }
+}
+
impl From<CreateConnector> for Statement {
fn from(c: CreateConnector) -> Self {
Self::CreateConnector(c)
diff --git a/tmp/agent-patch-flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/keywords.rs b/app/src/keywords.rs
index 7ff42b4..d839c72 100644
--- a/tmp/agent-patch-flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/keywords.rs
+++ b/app/src/keywords.rs
@@ -217,6 +217,7 @@ define_keywords!(
COMMENT,
COMMIT,
COMMITTED,
+ COMMUTATOR,
COMPATIBLE,
COMPRESSION,
COMPUPDATE,
@@ -383,6 +384,7 @@ define_keywords!(
EXTRACT,
FACTS,
FAIL,
+ FAMILY,
FAILOVER,
FALSE,
FETCH,
@@ -446,6 +448,7 @@ define_keywords!(
GROUPS,
GZIP,
HASH,
+ HASHES,
HAVING,
HEADER,
HEAP,
@@ -540,6 +543,7 @@ define_keywords!(
LEAD,
LEADING,
LEFT,
+ LEFTARG,
LEVEL,
LIKE,
LIKE_REGEX,
@@ -594,6 +598,7 @@ define_keywords!(
MEDIUMTEXT,
MEMBER,
MERGE,
+ MERGES,
MESSAGE,
METADATA,
METHOD,
@@ -632,6 +637,7 @@ define_keywords!(
NATURAL,
NCHAR,
NCLOB,
+ NEGATOR,
NEST,
NESTED,
NETWORK,
@@ -844,6 +850,7 @@ define_keywords!(
RETURNS,
REVOKE,
RIGHT,
+ RIGHTARG,
RLIKE,
RM,
ROLE,
diff --git a/tmp/agent-patch-flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/parser/mod.rs b/app/src/parser/mod.rs
index 1ab4626..64a0664 100644
--- a/tmp/agent-patch-flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -4749,6 +4749,14 @@ impl<'a> Parser<'a> {
self.parse_create_view(or_alter, or_replace, temporary, create_view_params)
} else if self.parse_keyword(Keyword::POLICY) {
self.parse_create_policy()
+ } else if self.parse_keyword(Keyword::OPERATOR) {
+ if self.parse_keyword(Keyword::FAMILY) {
+ self.parse_create_operator_family()
+ } else if self.parse_keyword(Keyword::CLASS) {
+ self.parse_create_operator_class()
+ } else {
+ self.parse_create_operator()
+ }
} else if self.parse_keyword(Keyword::EXTERNAL) {
self.parse_create_external_table(or_replace)
} else if self.parse_keyword(Keyword::FUNCTION) {
@@ -6393,6 +6401,232 @@ impl<'a> Parser<'a> {
})
}
+ fn parse_pg_operator_name(&mut self) -> Result<PgOperatorName, ParserError> {
+ let mut idents = vec![];
+
+ if self.parse_keyword(Keyword::OPERATOR) {
+ self.expect_token(&Token::LParen)?;
+ loop {
+ self.advance_token();
+ idents.push(self.get_current_token().to_string());
+ if !self.consume_token(&Token::Period) {
+ break;
+ }
+ }
+ self.expect_token(&Token::RParen)?;
+ } else {
+ loop {
+ self.advance_token();
+ idents.push(self.get_current_token().to_string());
+ if !self.consume_token(&Token::Period) {
+ break;
+ }
+ }
+ }
+
+ Ok(PgOperatorName(idents))
+ }
+
+ fn parse_pg_operator_argument_type(&mut self) -> Result<PgOperatorArgument, ParserError> {
+ if self.parse_keyword(Keyword::NONE) {
+ Ok(PgOperatorArgument::None)
+ } else {
+ Ok(PgOperatorArgument::Type(self.parse_data_type()?))
+ }
+ }
+
+ pub fn parse_create_operator(&mut self) -> Result<Statement, ParserError> {
+ let name = self.parse_pg_operator_name()?;
+ self.expect_token(&Token::LParen)?;
+
+ let mut procedure = None;
+ let mut left_arg = None;
+ let mut right_arg = None;
+ let mut commutator = None;
+ let mut negator = None;
+ let mut restrict = None;
+ let mut join = None;
+ let mut hashes = false;
+ let mut merges = false;
+
+ loop {
+ if self.parse_keyword(Keyword::HASHES) {
+ hashes = true;
+ } else if self.parse_keyword(Keyword::MERGES) {
+ merges = true;
+ } else {
+ match self.expect_one_of_keywords(&[
+ Keyword::FUNCTION,
+ Keyword::PROCEDURE,
+ Keyword::LEFTARG,
+ Keyword::RIGHTARG,
+ Keyword::COMMUTATOR,
+ Keyword::NEGATOR,
+ Keyword::RESTRICT,
+ Keyword::JOIN,
+ ])? {
+ Keyword::FUNCTION | Keyword::PROCEDURE => {
+ self.expect_token(&Token::Eq)?;
+ procedure = Some(self.parse_object_name(false)?);
+ }
+ Keyword::LEFTARG => {
+ self.expect_token(&Token::Eq)?;
+ left_arg = Some(self.parse_pg_operator_argument_type()?);
+ }
+ Keyword::RIGHTARG => {
+ self.expect_token(&Token::Eq)?;
+ right_arg = Some(self.parse_pg_operator_argument_type()?);
+ }
+ Keyword::COMMUTATOR => {
+ self.expect_token(&Token::Eq)?;
+ commutator = Some(self.parse_pg_operator_name()?);
+ }
+ Keyword::NEGATOR => {
+ self.expect_token(&Token::Eq)?;
+ negator = Some(self.parse_pg_operator_name()?);
+ }
+ Keyword::RESTRICT => {
+ self.expect_token(&Token::Eq)?;
+ restrict = Some(self.parse_object_name(false)?);
+ }
+ Keyword::JOIN => {
+ self.expect_token(&Token::Eq)?;
+ join = Some(self.parse_object_name(false)?);
+ }
+ _ => unreachable!(),
+ }
+ }
+
+ if self.consume_token(&Token::Comma) {
+ continue;
+ }
+ self.expect_token(&Token::RParen)?;
+ break;
+ }
+
+ Ok(CreateOperator {
+ name,
+ procedure: procedure.ok_or_else(|| {
+ ParserError::ParserError("Expected PROCEDURE or FUNCTION option".to_string())
+ })?,
+ left_arg,
+ right_arg,
+ commutator,
+ negator,
+ restrict,
+ join,
+ hashes,
+ merges,
+ }
+ .into())
+ }
+
+ pub fn parse_create_operator_family(&mut self) -> Result<Statement, ParserError> {
+ let name = self.parse_object_name(false)?;
+ self.expect_keyword_is(Keyword::USING)?;
+ let using = self.parse_identifier()?;
+ Ok(CreateOperatorFamily { name, using }.into())
+ }
+
+ fn parse_pg_operator_class_item_operator(
+ &mut self,
+ ) -> Result<CreateOperatorClassItem, ParserError> {
+ let strategy = self.parse_literal_uint()?;
+ let name = self.parse_pg_operator_name()?;
+ let operand_types = if self.consume_token(&Token::LParen) {
+ let types = self.parse_comma_separated(Parser::parse_data_type)?;
+ self.expect_token(&Token::RParen)?;
+ Some(types)
+ } else {
+ None
+ };
+
+ let mut for_search = false;
+ let mut sort_family = None;
+ if self.parse_keyword(Keyword::FOR) {
+ if self.parse_keyword(Keyword::SEARCH) {
+ for_search = true;
+ } else {
+ self.expect_keyword_is(Keyword::ORDER)?;
+ self.expect_keyword_is(Keyword::BY)?;
+ sort_family = Some(self.parse_object_name(false)?);
+ }
+ }
+
+ Ok(CreateOperatorClassItem::Operator {
+ strategy,
+ name,
+ operand_types,
+ for_search,
+ sort_family,
+ })
+ }
+
+ fn parse_pg_operator_class_item_function(
+ &mut self,
+ ) -> Result<CreateOperatorClassItem, ParserError> {
+ let support = self.parse_literal_uint()?;
+ let operand_types = if self.consume_token(&Token::LParen) {
+ let types = self.parse_comma_separated(Parser::parse_data_type)?;
+ self.expect_token(&Token::RParen)?;
+ Some(types)
+ } else {
+ None
+ };
+ let function_name = self.parse_object_name(false)?;
+ self.expect_token(&Token::LParen)?;
+ let arguments = if self.consume_token(&Token::RParen) {
+ vec![]
+ } else {
+ let args = self.parse_comma_separated(Parser::parse_data_type)?;
+ self.expect_token(&Token::RParen)?;
+ args
+ };
+
+ Ok(CreateOperatorClassItem::Function {
+ support,
+ operand_types,
+ function_name,
+ arguments,
+ })
+ }
+
+ pub fn parse_create_operator_class(&mut self) -> Result<Statement, ParserError> {
+ let name = self.parse_object_name(false)?;
+ let default = self.parse_keyword(Keyword::DEFAULT);
+ self.expect_keywords(&[Keyword::FOR, Keyword::TYPE])?;
+ let data_type = self.parse_data_type()?;
+ self.expect_keyword_is(Keyword::USING)?;
+ let using = self.parse_identifier()?;
+ let family = if self.parse_keyword(Keyword::FAMILY) {
+ Some(self.parse_object_name(false)?)
+ } else {
+ None
+ };
+ self.expect_keyword_is(Keyword::AS)?;
+ let items = self.parse_comma_separated(|p| {
+ if p.parse_keyword(Keyword::OPERATOR) {
+ p.parse_pg_operator_class_item_operator()
+ } else if p.parse_keyword(Keyword::FUNCTION) {
+ p.parse_pg_operator_class_item_function()
+ } else if p.parse_keyword(Keyword::STORAGE) {
+ Ok(CreateOperatorClassItem::Storage(p.parse_data_type()?))
+ } else {
+ p.expected("OPERATOR, FUNCTION, or STORAGE", p.peek_token())
+ }
+ })?;
+
+ Ok(CreateOperatorClass {
+ name,
+ default,
+ data_type,
+ using,
+ family,
+ items,
+ }
+ .into())
+ }
+
/// ```sql
/// CREATE CONNECTOR [IF NOT EXISTS] connector_name
/// [TYPE datasource_type]
diff --git a/tmp/agent-patch-flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_postgres.rs b/app/tests/sqlparser_postgres.rs
index 3bdf6d1..1b0d51b 100644
--- a/tmp/agent-patch-flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_postgres.rs
+++ b/app/tests/sqlparser_postgres.rs
@@ -6551,6 +6551,83 @@ fn parse_create_server() {
}
}
+#[test]
+fn parse_create_operator() {
+ let sql = "CREATE OPERATOR === (
+ PROCEDURE = custom_eq,
+ LEFTARG = integer,
+ RIGHTARG = integer,
+ COMMUTATOR = OPERATOR(public.===),
+ NEGATOR = !==,
+ RESTRICT = eqsel,
+ JOIN = eqjoinsel,
+ HASHES,
+ MERGES
+ )";
+ pg().one_statement_parses_to(
+ sql,
+ "CREATE OPERATOR === (PROCEDURE = custom_eq, LEFTARG = INTEGER, RIGHTARG = INTEGER, COMMUTATOR = public.===, NEGATOR = !==, RESTRICT = eqsel, JOIN = eqjoinsel, HASHES, MERGES)",
+ );
+}
+
+#[test]
+fn parse_create_operator_with_none_argument() {
+ let sql = "CREATE OPERATOR ! (
+ FUNCTION = unary_not,
+ LEFTARG = NONE,
+ RIGHTARG = boolean
+ )";
+
+ let Statement::CreateOperator(stmt) = pg().verified_stmt(sql) else {
+ panic!("expected create operator");
+ };
+
+ assert_eq!(stmt.left_arg, Some(PgOperatorArgument::None));
+ assert_eq!(
+ stmt.right_arg,
+ Some(PgOperatorArgument::Type(DataType::Boolean))
+ );
+ assert_eq!(
+ stmt.to_string(),
+ "CREATE OPERATOR ! (PROCEDURE = unary_not, LEFTARG = NONE, RIGHTARG = BOOLEAN)"
+ );
+}
+
+#[test]
+fn parse_create_operator_family() {
+ let sql = "CREATE OPERATOR FAMILY integer_ops USING btree";
+ pg().one_statement_parses_to(sql, "CREATE OPERATOR FAMILY integer_ops USING btree");
+}
+
+#[test]
+fn parse_create_operator_class() {
+ let sql = "CREATE OPERATOR CLASS int4_ops
+ DEFAULT FOR TYPE int4 USING btree FAMILY integer_ops AS
+ OPERATOR 1 <,
+ OPERATOR 3 =,
+ FUNCTION 1 btint4cmp(int4, int4),
+ STORAGE int4";
+
+ pg().one_statement_parses_to(
+ sql,
+ "CREATE OPERATOR CLASS int4_ops DEFAULT FOR TYPE INT4 USING btree FAMILY integer_ops AS OPERATOR 1 <, OPERATOR 3 =, FUNCTION 1 btint4cmp(INT4, INT4), STORAGE INT4",
+ );
+}
+
+#[test]
+fn parse_create_operator_class_with_search_and_order_items() {
+ let sql = "CREATE OPERATOR CLASS point_ops
+ FOR TYPE point USING gist AS
+ OPERATOR 1 << (point, point) FOR SEARCH,
+ OPERATOR 2 <-> (point, point) FOR ORDER BY float_ops,
+ FUNCTION 1 (point, point) point_consistent(internal, point, smallint, oid, internal)";
+
+ pg().one_statement_parses_to(
+ sql,
+ "CREATE OPERATOR CLASS point_ops FOR TYPE point USING gist AS OPERATOR 1 << (point, point) FOR SEARCH, OPERATOR 2 <-> (point, point) FOR ORDER BY float_ops, FUNCTION 1 (point, point) point_consistent(internal, point, SMALLINT, oid, internal)",
+ );
+}
+
#[test]
fn parse_alter_schema() {
match pg_and_generic().verified_stmt("ALTER SCHEMA foo RENAME TO bar") {