STET

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

Back to adjudication
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") {