agent_patch
runs/2026-02-28__18-27-22__gpt-5-3-codex/flux-pr-2096/flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/agent-logs/agent.patch
27150 bytes
diff --git a/tmp/agent-patch-flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/ddl.rs b/app/src/ast/ddl.rs
index 286b16a..51e2846 100644
--- a/tmp/agent-patch-flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/ddl.rs
+++ b/app/src/ast/ddl.rs
@@ -3858,6 +3858,202 @@ impl Spanned for DropExtension {
}
}
+/// PostgreSQL operator name, such as `+`, `===`, or `schema.===`.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct PostgresOperatorName(pub Vec<String>);
+
+impl fmt::Display for PostgresOperatorName {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", display_separated(&self.0, "."))
+ }
+}
+
+/// PostgreSQL operator reference.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum PostgresOperatorReference {
+ /// Unqualified/reference form, for example `=`.
+ Name(PostgresOperatorName),
+ /// `OPERATOR(<operator>)` reference form.
+ Qualified(PostgresOperatorName),
+}
+
+impl fmt::Display for PostgresOperatorReference {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ PostgresOperatorReference::Name(name) => write!(f, "{name}"),
+ PostgresOperatorReference::Qualified(name) => write!(f, "OPERATOR({name})"),
+ }
+ }
+}
+
+/// PostgreSQL `CREATE OPERATOR` option.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum CreateOperatorOption {
+ Function(ObjectName),
+ Procedure(ObjectName),
+ LeftArg(DataType),
+ RightArg(DataType),
+ Commutator(PostgresOperatorReference),
+ Negator(PostgresOperatorReference),
+ Restrict(ObjectName),
+ Join(ObjectName),
+ Hashes,
+ Merges,
+}
+
+impl fmt::Display for CreateOperatorOption {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ CreateOperatorOption::Function(name) => write!(f, "FUNCTION = {name}"),
+ CreateOperatorOption::Procedure(name) => write!(f, "PROCEDURE = {name}"),
+ CreateOperatorOption::LeftArg(data_type) => write!(f, "LEFTARG = {data_type}"),
+ CreateOperatorOption::RightArg(data_type) => write!(f, "RIGHTARG = {data_type}"),
+ CreateOperatorOption::Commutator(operator) => write!(f, "COMMUTATOR = {operator}"),
+ CreateOperatorOption::Negator(operator) => write!(f, "NEGATOR = {operator}"),
+ CreateOperatorOption::Restrict(name) => write!(f, "RESTRICT = {name}"),
+ CreateOperatorOption::Join(name) => write!(f, "JOIN = {name}"),
+ CreateOperatorOption::Hashes => write!(f, "HASHES"),
+ CreateOperatorOption::Merges => write!(f, "MERGES"),
+ }
+ }
+}
+
+/// PostgreSQL `CREATE OPERATOR` 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 CreateOperator {
+ pub name: PostgresOperatorName,
+ pub options: Vec<CreateOperatorOption>,
+}
+
+impl fmt::Display for CreateOperator {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "CREATE OPERATOR {} ({})",
+ self.name,
+ display_comma_separated(&self.options)
+ )
+ }
+}
+
+/// PostgreSQL `CREATE OPERATOR FAMILY` 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 CreateOperatorFamily {
+ pub name: ObjectName,
+ pub index_method: Ident,
+}
+
+impl fmt::Display for CreateOperatorFamily {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "CREATE OPERATOR FAMILY {} USING {}",
+ self.name, self.index_method
+ )
+ }
+}
+
+/// PostgreSQL `CREATE OPERATOR CLASS ... AS` item.
+#[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,
+ operator: PostgresOperatorReference,
+ argument_types: Option<Vec<DataType>>,
+ is_for_search: bool,
+ order_by_family: Option<ObjectName>,
+ },
+ Function {
+ support: u64,
+ argument_types: Option<Vec<DataType>>,
+ function: FunctionDesc,
+ },
+ Storage(DataType),
+}
+
+impl fmt::Display for CreateOperatorClassItem {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ CreateOperatorClassItem::Operator {
+ strategy,
+ operator,
+ argument_types,
+ is_for_search,
+ order_by_family,
+ } => {
+ write!(f, "OPERATOR {strategy} {operator}")?;
+ if let Some(argument_types) = argument_types {
+ write!(f, " ({})", display_comma_separated(argument_types))?;
+ }
+ if *is_for_search {
+ write!(f, " FOR SEARCH")?;
+ } else if let Some(order_by_family) = order_by_family {
+ write!(f, " FOR ORDER BY {order_by_family}")?;
+ }
+ Ok(())
+ }
+ CreateOperatorClassItem::Function {
+ support,
+ argument_types,
+ function,
+ } => {
+ write!(f, "FUNCTION {support}")?;
+ if let Some(argument_types) = argument_types {
+ write!(f, " ({})", display_comma_separated(argument_types))?;
+ }
+ write!(f, " {function}")
+ }
+ CreateOperatorClassItem::Storage(data_type) => write!(f, "STORAGE {data_type}"),
+ }
+ }
+}
+
+/// PostgreSQL `CREATE OPERATOR CLASS` 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 CreateOperatorClass {
+ pub name: ObjectName,
+ pub is_default: bool,
+ pub for_type: DataType,
+ pub index_method: 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 {}", self.name)?;
+ if self.is_default {
+ write!(f, " DEFAULT")?;
+ }
+ write!(
+ f,
+ " FOR TYPE {} USING {}",
+ self.for_type, self.index_method
+ )?;
+ if let Some(family) = &self.family {
+ write!(f, " FAMILY {family}")?;
+ }
+ if !self.items.is_empty() {
+ write!(f, " AS {}", display_comma_separated(&self.items))?;
+ }
+ Ok(())
+ }
+}
+
/// Table type for ALTER TABLE statements.
/// Used to distinguish between regular tables, Iceberg tables, and Dynamic tables.
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
diff --git a/tmp/agent-patch-flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/mod.rs b/app/src/ast/mod.rs
index aa3fb08..f1ef929 100644
--- a/tmp/agent-patch-flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/mod.rs
+++ b/app/src/ast/mod.rs
@@ -65,15 +65,17 @@ pub use self::ddl::{
AlterTypeAddValuePosition, AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue,
ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnOptions, ColumnPolicy,
ColumnPolicyProperty, ConstraintCharacteristics, CreateConnector, CreateDomain,
- CreateExtension, CreateFunction, CreateIndex, CreateTable, CreateTrigger, CreateView,
- Deduplicate, DeferrableInitial, DropBehavior, DropExtension, DropFunction, DropTrigger,
- GeneratedAs, GeneratedExpressionMode, IdentityParameters, IdentityProperty,
- IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, IndexColumn,
- IndexOption, IndexType, KeyOrIndexDisplay, Msck, NullsDistinctOption, Owner, Partition,
- ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, TagsColumnOption,
- TriggerObjectKind, Truncate, UserDefinedTypeCompositeAttributeDef,
- UserDefinedTypeInternalLength, UserDefinedTypeRangeOption, UserDefinedTypeRepresentation,
- UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, ViewColumnDef,
+ CreateExtension, CreateFunction, CreateIndex, CreateOperator, CreateOperatorClass,
+ CreateOperatorClassItem, CreateOperatorFamily, CreateOperatorOption, CreateTable,
+ CreateTrigger, CreateView, Deduplicate, DeferrableInitial, DropBehavior, DropExtension,
+ DropFunction, DropTrigger, GeneratedAs, GeneratedExpressionMode, IdentityParameters,
+ IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder,
+ IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, Msck, NullsDistinctOption, Owner,
+ Partition, PostgresOperatorName, PostgresOperatorReference, ProcedureParam, ReferentialAction,
+ RenameTableNameKind, ReplicaIdentity, TagsColumnOption, TriggerObjectKind, Truncate,
+ UserDefinedTypeCompositeAttributeDef, UserDefinedTypeInternalLength,
+ UserDefinedTypeRangeOption, UserDefinedTypeRepresentation, UserDefinedTypeSqlDefinitionOption,
+ UserDefinedTypeStorage, ViewColumnDef,
};
pub use self::dml::{Delete, Insert, Update};
pub use self::operator::{BinaryOperator, UnaryOperator};
@@ -4104,6 +4106,12 @@ pub enum Statement {
},
/// A `CREATE DOMAIN` statement.
CreateDomain(CreateDomain),
+ /// A PostgreSQL `CREATE OPERATOR` statement.
+ CreateOperator(CreateOperator),
+ /// A PostgreSQL `CREATE OPERATOR FAMILY` statement.
+ CreateOperatorFamily(CreateOperatorFamily),
+ /// A PostgreSQL `CREATE OPERATOR CLASS` statement.
+ CreateOperatorClass(CreateOperatorClass),
/// ```sql
/// CREATE TYPE <name>
/// ```
@@ -4716,6 +4724,13 @@ impl fmt::Display for Statement {
}
Statement::CreateFunction(create_function) => create_function.fmt(f),
Statement::CreateDomain(create_domain) => create_domain.fmt(f),
+ Statement::CreateOperator(create_operator) => write!(f, "{create_operator}"),
+ Statement::CreateOperatorFamily(create_operator_family) => {
+ write!(f, "{create_operator_family}")
+ }
+ Statement::CreateOperatorClass(create_operator_class) => {
+ write!(f, "{create_operator_class}")
+ }
Statement::CreateTrigger(create_trigger) => create_trigger.fmt(f),
Statement::DropTrigger(drop_trigger) => drop_trigger.fmt(f),
Statement::CreateProcedure {
@@ -10727,6 +10742,24 @@ impl From<CreateIndex> 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<CreateServerStatement> for Statement {
fn from(c: CreateServerStatement) -> Self {
Self::CreateServer(c)
diff --git a/tmp/agent-patch-flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/spans.rs b/app/src/ast/spans.rs
index 3a4f1d0..33e5483 100644
--- a/tmp/agent-patch-flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/spans.rs
+++ b/app/src/ast/spans.rs
@@ -416,6 +416,9 @@ impl Spanned for Statement {
Statement::CreateDatabase { .. } => Span::empty(),
Statement::CreateFunction { .. } => Span::empty(),
Statement::CreateDomain { .. } => Span::empty(),
+ Statement::CreateOperator { .. } => Span::empty(),
+ Statement::CreateOperatorFamily { .. } => Span::empty(),
+ Statement::CreateOperatorClass { .. } => Span::empty(),
Statement::CreateTrigger { .. } => Span::empty(),
Statement::DropTrigger { .. } => Span::empty(),
Statement::CreateProcedure { .. } => Span::empty(),
diff --git a/tmp/agent-patch-flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/parser/mod.rs b/app/src/parser/mod.rs
index 1ab4626..213b099 100644
--- a/tmp/agent-patch-flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -4770,6 +4770,8 @@ impl<'a> Parser<'a> {
"[EXTERNAL] TABLE or [MATERIALIZED] VIEW or FUNCTION after CREATE OR REPLACE",
self.peek_token(),
)
+ } else if self.parse_keyword(Keyword::OPERATOR) {
+ self.parse_create_operator_statement()
} else if self.parse_keyword(Keyword::EXTENSION) {
self.parse_create_extension()
} else if self.parse_keyword(Keyword::INDEX) {
@@ -17352,6 +17354,212 @@ impl<'a> Parser<'a> {
})
}
+ fn parse_create_operator_statement(&mut self) -> Result<Statement, ParserError> {
+ if let Token::Word(w) = &self.peek_token_ref().token {
+ if w.value.eq_ignore_ascii_case("FAMILY") {
+ self.next_token();
+ return self.parse_create_operator_family();
+ }
+ if w.value.eq_ignore_ascii_case("CLASS") {
+ self.next_token();
+ return self.parse_create_operator_class();
+ }
+ }
+ self.parse_create_operator()
+ }
+
+ fn parse_create_operator(&mut self) -> Result<Statement, ParserError> {
+ let name = self.parse_postgres_operator_name()?;
+ self.expect_token(&Token::LParen)?;
+ let options =
+ self.parse_comma_separated0(Parser::parse_create_operator_option, Token::RParen)?;
+ self.expect_token(&Token::RParen)?;
+ Ok(Statement::CreateOperator(CreateOperator { name, options }))
+ }
+
+ fn parse_create_operator_option(&mut self) -> Result<CreateOperatorOption, ParserError> {
+ if self.parse_keyword(Keyword::FUNCTION) {
+ self.expect_token(&Token::Eq)?;
+ return Ok(CreateOperatorOption::Function(self.parse_object_name(false)?));
+ }
+ if self.parse_keyword(Keyword::PROCEDURE) {
+ self.expect_token(&Token::Eq)?;
+ return Ok(CreateOperatorOption::Procedure(self.parse_object_name(false)?));
+ }
+ if self.parse_keyword(Keyword::RESTRICT) {
+ self.expect_token(&Token::Eq)?;
+ return Ok(CreateOperatorOption::Restrict(self.parse_object_name(false)?));
+ }
+ if self.parse_keyword(Keyword::JOIN) {
+ self.expect_token(&Token::Eq)?;
+ return Ok(CreateOperatorOption::Join(self.parse_object_name(false)?));
+ }
+
+ let option = self.parse_identifier()?;
+ match option.value.to_ascii_uppercase().as_str() {
+ "LEFTARG" => {
+ self.expect_token(&Token::Eq)?;
+ Ok(CreateOperatorOption::LeftArg(self.parse_data_type()?))
+ }
+ "RIGHTARG" => {
+ self.expect_token(&Token::Eq)?;
+ Ok(CreateOperatorOption::RightArg(self.parse_data_type()?))
+ }
+ "COMMUTATOR" => {
+ self.expect_token(&Token::Eq)?;
+ Ok(CreateOperatorOption::Commutator(
+ self.parse_postgres_operator_reference()?,
+ ))
+ }
+ "NEGATOR" => {
+ self.expect_token(&Token::Eq)?;
+ Ok(CreateOperatorOption::Negator(
+ self.parse_postgres_operator_reference()?,
+ ))
+ }
+ "HASHES" => Ok(CreateOperatorOption::Hashes),
+ "MERGES" => Ok(CreateOperatorOption::Merges),
+ _ => parser_err!(
+ format!("Expected CREATE OPERATOR option, found: {}", option.value),
+ option.span.start
+ ),
+ }
+ }
+
+ fn parse_create_operator_family(&mut self) -> Result<Statement, ParserError> {
+ let name = self.parse_object_name(false)?;
+ self.expect_keyword_is(Keyword::USING)?;
+ let index_method = self.parse_identifier()?;
+ Ok(Statement::CreateOperatorFamily(CreateOperatorFamily {
+ name,
+ index_method,
+ }))
+ }
+
+ fn parse_create_operator_class(&mut self) -> Result<Statement, ParserError> {
+ let name = self.parse_object_name(false)?;
+ let is_default = self.parse_keyword(Keyword::DEFAULT);
+ self.expect_keyword_is(Keyword::FOR)?;
+ self.expect_keyword_is(Keyword::TYPE)?;
+ let for_type = self.parse_data_type()?;
+ self.expect_keyword_is(Keyword::USING)?;
+ let index_method = self.parse_identifier()?;
+ let family = if let Token::Word(w) = &self.peek_token_ref().token {
+ if w.value.eq_ignore_ascii_case("FAMILY") {
+ self.next_token();
+ Some(self.parse_object_name(false)?)
+ } else {
+ None
+ }
+ } else {
+ None
+ };
+ let items = if self.parse_keyword(Keyword::AS) {
+ self.parse_comma_separated(Parser::parse_create_operator_class_item)?
+ } else {
+ vec![]
+ };
+ Ok(Statement::CreateOperatorClass(CreateOperatorClass {
+ name,
+ is_default,
+ for_type,
+ index_method,
+ family,
+ items,
+ }))
+ }
+
+ fn parse_create_operator_class_item(&mut self) -> Result<CreateOperatorClassItem, ParserError> {
+ if self.parse_keyword(Keyword::OPERATOR) {
+ let strategy = self.parse_literal_uint()?;
+ let operator = self.parse_postgres_operator_reference()?;
+ let argument_types = if self.consume_token(&Token::LParen) {
+ let argument_types =
+ self.parse_comma_separated0(Parser::parse_data_type, Token::RParen)?;
+ self.expect_token(&Token::RParen)?;
+ Some(argument_types)
+ } else {
+ None
+ };
+ let (is_for_search, order_by_family) = if self.parse_keyword(Keyword::FOR) {
+ if self.parse_keyword(Keyword::SEARCH) {
+ (true, None)
+ } else {
+ self.expect_keyword_is(Keyword::ORDER)?;
+ self.expect_keyword_is(Keyword::BY)?;
+ (false, Some(self.parse_object_name(false)?))
+ }
+ } else {
+ (false, None)
+ };
+ return Ok(CreateOperatorClassItem::Operator {
+ strategy,
+ operator,
+ argument_types,
+ is_for_search,
+ order_by_family,
+ });
+ }
+
+ if self.parse_keyword(Keyword::FUNCTION) {
+ let support = self.parse_literal_uint()?;
+ let argument_types = if self.consume_token(&Token::LParen) {
+ let argument_types =
+ self.parse_comma_separated0(Parser::parse_data_type, Token::RParen)?;
+ self.expect_token(&Token::RParen)?;
+ Some(argument_types)
+ } else {
+ None
+ };
+ let function = self.parse_function_desc()?;
+ return Ok(CreateOperatorClassItem::Function {
+ support,
+ argument_types,
+ function,
+ });
+ }
+
+ if self.parse_keyword(Keyword::STORAGE) {
+ return Ok(CreateOperatorClassItem::Storage(self.parse_data_type()?));
+ }
+
+ self.expected("OPERATOR, FUNCTION, or STORAGE", self.peek_token())
+ }
+
+ fn parse_postgres_operator_reference(
+ &mut self,
+ ) -> Result<PostgresOperatorReference, ParserError> {
+ if self.parse_keyword(Keyword::OPERATOR) {
+ self.expect_token(&Token::LParen)?;
+ let name = self.parse_postgres_operator_name()?;
+ self.expect_token(&Token::RParen)?;
+ Ok(PostgresOperatorReference::Qualified(name))
+ } else {
+ Ok(PostgresOperatorReference::Name(
+ self.parse_postgres_operator_name()?,
+ ))
+ }
+ }
+
+ fn parse_postgres_operator_name(&mut self) -> Result<PostgresOperatorName, ParserError> {
+ let mut parts = vec![];
+ loop {
+ let token = self.next_token();
+ match token.token {
+ Token::EOF | Token::LParen | Token::RParen | Token::Comma => {
+ return self.expected("operator name", token);
+ }
+ _ => parts.push(token.to_string()),
+ }
+
+ if !self.consume_token(&Token::Period) {
+ break;
+ }
+ }
+
+ Ok(PostgresOperatorName(parts))
+ }
+
pub fn parse_window_spec(&mut self) -> Result<WindowSpec, ParserError> {
let window_name = match self.peek_token().token {
Token::Word(word) if word.keyword == Keyword::NoKeyword => {
diff --git a/tmp/agent-patch-flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_postgres.rs b/app/tests/sqlparser_postgres.rs
index 3bdf6d1..971f50c 100644
--- a/tmp/agent-patch-flux-pr-2096.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_postgres.rs
+++ b/app/tests/sqlparser_postgres.rs
@@ -670,6 +670,109 @@ fn parse_create_extension() {
.verified_stmt("CREATE EXTENSION extension_name WITH SCHEMA schema_name VERSION version");
}
+#[test]
+fn parse_create_operator() {
+ let sql = "CREATE OPERATOR === (FUNCTION = public.eq_fn, LEFTARG = INT4, RIGHTARG = INT4, COMMUTATOR = OPERATOR(pg_catalog.===), NEGATOR = !==, RESTRICT = eqsel, JOIN = eqjoinsel, HASHES, MERGES)";
+ let stmt = pg_and_generic().verified_stmt(sql);
+ assert_eq!(
+ stmt.to_string(),
+ "CREATE OPERATOR === (FUNCTION = public.eq_fn, LEFTARG = INT4, RIGHTARG = INT4, COMMUTATOR = OPERATOR(pg_catalog.===), NEGATOR = !==, RESTRICT = eqsel, JOIN = eqjoinsel, HASHES, MERGES)"
+ );
+
+ match stmt {
+ Statement::CreateOperator(CreateOperator { name, options }) => {
+ assert_eq!(name, PostgresOperatorName(vec!["===".to_string()]));
+ assert_eq!(
+ options,
+ vec![
+ CreateOperatorOption::Function(ObjectName::from(vec!["public", "eq_fn"])),
+ CreateOperatorOption::LeftArg(DataType::Int4),
+ CreateOperatorOption::RightArg(DataType::Int4),
+ CreateOperatorOption::Commutator(PostgresOperatorReference::Qualified(
+ PostgresOperatorName(vec!["pg_catalog".to_string(), "===".to_string()])
+ )),
+ CreateOperatorOption::Negator(PostgresOperatorReference::Name(
+ PostgresOperatorName(vec!["!==".to_string()])
+ )),
+ CreateOperatorOption::Restrict(ObjectName::from(vec!["eqsel"])),
+ CreateOperatorOption::Join(ObjectName::from(vec!["eqjoinsel"])),
+ CreateOperatorOption::Hashes,
+ CreateOperatorOption::Merges,
+ ]
+ );
+ }
+ _ => unreachable!(),
+ }
+}
+
+#[test]
+fn parse_create_operator_family() {
+ let sql = "CREATE OPERATOR FAMILY int_family USING btree";
+ assert_eq!(
+ pg_and_generic().verified_stmt(sql),
+ Statement::CreateOperatorFamily(CreateOperatorFamily {
+ name: ObjectName::from(vec!["int_family"]),
+ index_method: Ident::new("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 < FOR ORDER BY pg_catalog.integer_ops, FUNCTION 1 btint4cmp(int4, int4), STORAGE int4";
+ let stmt = pg_and_generic().verified_stmt(sql);
+ assert_eq!(
+ stmt.to_string(),
+ "CREATE OPERATOR CLASS int4_ops DEFAULT FOR TYPE INT4 USING btree FAMILY integer_ops AS OPERATOR 1 < FOR ORDER BY pg_catalog.integer_ops, FUNCTION 1 btint4cmp(INT4, INT4), STORAGE INT4"
+ );
+ match stmt {
+ Statement::CreateOperatorClass(CreateOperatorClass {
+ name,
+ is_default,
+ for_type,
+ index_method,
+ family,
+ items,
+ }) => {
+ assert_eq!(name, ObjectName::from(vec!["int4_ops"]));
+ assert!(is_default);
+ assert_eq!(for_type, DataType::Int4);
+ assert_eq!(index_method, Ident::new("btree"));
+ assert_eq!(family, Some(ObjectName::from(vec!["integer_ops"])));
+ assert_eq!(
+ items,
+ vec![
+ CreateOperatorClassItem::Operator {
+ strategy: 1,
+ operator: PostgresOperatorReference::Name(PostgresOperatorName(vec![
+ "<".to_string()
+ ])),
+ argument_types: None,
+ is_for_search: false,
+ order_by_family: Some(ObjectName::from(vec![
+ "pg_catalog",
+ "integer_ops"
+ ])),
+ },
+ CreateOperatorClassItem::Function {
+ support: 1,
+ argument_types: None,
+ function: FunctionDesc {
+ name: ObjectName::from(vec!["btint4cmp"]),
+ args: Some(vec![
+ OperateFunctionArg::unnamed(DataType::Int4),
+ OperateFunctionArg::unnamed(DataType::Int4),
+ ]),
+ },
+ },
+ CreateOperatorClassItem::Storage(DataType::Int4),
+ ]
+ );
+ }
+ _ => unreachable!(),
+ }
+}
+
#[test]
fn parse_drop_extension() {
assert_eq!(