diff --git a/crates/squawk_parser/src/grammar.rs b/crates/squawk_parser/src/grammar.rs index e1d0d2785..17c1f8ba4 100644 --- a/crates/squawk_parser/src/grammar.rs +++ b/crates/squawk_parser/src/grammar.rs @@ -3580,7 +3580,7 @@ fn opt_sort_order(p: &mut Parser<'_>) { } USING_KW => { p.bump(USING_KW); - operator(p); + op_or_opcall(p); SORT_USING } _ => { @@ -5048,19 +5048,122 @@ fn opt_operator(p: &mut Parser<'_>) -> bool { p.eat(kind) } +fn opt_op(p: &mut Parser<'_>) -> bool { + if !p.at_ts(OPERATOR_FIRST) || p.at(FAT_ARROW) { + return false; + } + let (power, kind, _) = current_op(p, &Restrictions::default()); + if power == 0 { + p.bump_any(); + return true; + } + p.eat(kind) +} + +const OP_RECOVERY: TokenSet = TokenSet::new(&[L_PAREN, COMMA]).union(EXPR_RECOVERY_SET); + +fn err_recover_run(p: &mut Parser<'_>, message: String, recovery: TokenSet) { + if p.at(EOF) || p.at_ts(recovery) { + p.error(message); + return; + } + let m = p.start(); + p.error(message); + while !p.at(EOF) && !p.at_ts(recovery) { + p.bump_any(); + } + m.complete(p, ERROR); +} + +fn op(p: &mut Parser<'_>) { + if opt_op(p) { + return; + } + let message = format!("expected operator, got {:?}", p.current()); + if p.at(EOF) || p.at_ts(OP_RECOVERY) { + p.error(message); + return; + } + let m = p.start(); + p.error(message); + if !p.eat(COLON_COLON) && !p.eat(COLON_EQ) && !p.eat(FAT_ARROW) { + p.bump_any(); + } + m.complete(p, ERROR); +} + +fn opt_op_or_opcall(p: &mut Parser<'_>) -> bool { + p.eat(OPERATOR_CALL) || opt_op(p) +} + +fn op_or_opcall(p: &mut Parser<'_>) { + if p.eat(OPERATOR_CALL) { + return; + } + let m = p.start(); + if p.nth_at(1, DOT) { + opt_op_path(p); + p.error("qualified operator requires OPERATOR(...)"); + } + op(p); + m.complete(p, OP); +} + +fn at_op_path_segment(p: &Parser<'_>) -> bool { + p.at_ts(NAME_FIRST) || (!p.at(DOT) && p.nth_at(1, DOT)) +} + +fn opt_op_path(p: &mut Parser<'_>) -> Option { + if !at_op_path_segment(p) { + return None; + } + let m = p.start(); + op_path_segment(p); + let mut qual = m.complete(p, PATH_REF); + while p.at(DOT) { + let path = qual.precede(p); + p.bump(DOT); + let named = at_op_path_segment(p); + op_path_segment(p); + qual = path.complete(p, PATH_REF); + if !named { + break; + } + } + Some(qual) +} + +fn op_path_segment(p: &mut Parser<'_>) { + let m = p.start(); + if p.at_ts(NAME_FIRST) { + pg_name(p); + } else if at_op_path_segment(p) { + p.err_and_bump("expected name"); + } + m.complete(p, PATH_SEGMENT_REF); +} + // optional schema supported // > // bar.> // foo.bar.> -pub(crate) fn operator(p: &mut Parser<'_>) { +pub(crate) fn qual_op(p: &mut Parser<'_>) { let m = p.start(); - opt_path_name_ref(p); - if !opt_operator(p) { - p.error(format!("expected operator, got {:?}", p.current())); - } + opt_op_path(p); + op(p); m.complete(p, OP); } +// > +// bar.> +// foo.bar.> +// operator(bar.>) +fn qual_op_or_opcall(p: &mut Parser<'_>) { + if !p.eat(OPERATOR_CALL) { + qual_op(p); + } +} + pub(crate) fn current_operator(p: &Parser<'_>) -> Option { let (power, kind, _) = current_op(p, &Restrictions::default()); if power == 0 { None } else { Some(kind) } @@ -5274,10 +5377,7 @@ fn opt_constraint_exclusion(p: &mut Parser<'_>) -> Option { return None; } p.expect(WITH_KW); - // support: - // with > - // with foo.bar.buzz.> - operator(p); + qual_op_or_opcall(p); Some(m.complete(p, CONSTRAINT_EXCLUSION)) } @@ -9152,7 +9252,7 @@ fn extension_member_object(p: &mut Parser<'_>) { } OPERATOR_KW => { p.bump(OPERATOR_KW); - operator(p); + qual_op(p); p.expect(L_PAREN); type_name(p); p.expect(COMMA); @@ -10804,7 +10904,7 @@ fn comment_object(p: &mut Parser<'_>) { } OPERATOR_KW => { p.bump(OPERATOR_KW); - operator(p); + qual_op(p); p.eat(L_PAREN); type_name(p); p.expect(COMMA); @@ -12182,7 +12282,7 @@ fn create_operator(p: &mut Parser<'_>) -> CompletedMarker { let m = p.start(); p.bump(CREATE_KW); p.bump(OPERATOR_KW); - operator(p); + qual_op(p); attribute_list(p); p.eat(SEMICOLON); m.complete(p, CREATE_OPERATOR) @@ -12234,7 +12334,7 @@ fn operator_class_option(p: &mut Parser<'_>) { if opt_numeric_literal(p).is_none() { p.error("expected an integer"); } - operator(p); + qual_op(p); if p.eat(L_PAREN) { type_name(p); p.expect(COMMA); @@ -13418,7 +13518,7 @@ fn op_sig_list(p: &mut Parser<'_>) { // name ( { left_type | NONE } , right_type ) fn operator_sig(p: &mut Parser<'_>) { let m = p.start(); - operator(p); + qual_op(p); p.expect(L_PAREN); if !p.eat(NONE_KW) { type_name(p); @@ -18675,22 +18775,25 @@ fn opt_attribute_option(p: &mut Parser<'_>) -> bool { // qual_all_Op: // | all_Op // | OPERATOR '(' any_operator ')' +const ATTRIBUTE_VALUE_RECOVERY: TokenSet = TokenSet::new(&[COMMA]).union(EXPR_RECOVERY_SET); + fn def_arg(p: &mut Parser<'_>) { let m = p.start(); if opt_bool_literal(p) || opt_string_literal(p).is_some() || opt_numeric_literal(p).is_some() - || opt_operator(p) + || opt_op_or_opcall(p) || p.eat(NONE_KW) { } else if p.at_ts(RESERVED_KEYWORDS) { p.bump_any(); } else if p.eat(OPERATOR_KW) { p.expect(L_PAREN); - operator(p); + qual_op(p); p.expect(R_PAREN); - } else { - opt_type_name(p); + } else if !opt_type_name(p) { + let message = format!("expected attribute value, got {:?}", p.current()); + err_recover_run(p, message, ATTRIBUTE_VALUE_RECOVERY); } m.complete(p, ATTRIBUTE_VALUE); } diff --git a/crates/squawk_parser/src/lib.rs b/crates/squawk_parser/src/lib.rs index e3b550f9e..af72744b2 100644 --- a/crates/squawk_parser/src/lib.rs +++ b/crates/squawk_parser/src/lib.rs @@ -400,7 +400,7 @@ impl<'t> Parser<'t> { self.bump(SyntaxKind::L_PAREN); // e.g. `+`, `pg_catalog.+`, `db.pg_catalog.+` - grammar::operator(self); + grammar::qual_op(self); self.expect(SyntaxKind::R_PAREN); m.complete(self, SyntaxKind::OPERATOR_CALL); diff --git a/crates/squawk_parser/tests/data/err/operator.sql b/crates/squawk_parser/tests/data/err/operator.sql new file mode 100644 index 000000000..ae4ebcadd --- /dev/null +++ b/crates/squawk_parser/tests/data/err/operator.sql @@ -0,0 +1,29 @@ +-- all_Op is symbolic only, keyword operators aren't operator names +select 1 operator(and) 2; +select 1 operator(or) 2; +select 1 operator(in) 2; +create operator and (leftarg = int, rightarg = int, function = int4pl); +drop operator and (int, int); + +-- neither are the tokens Postgres reserves +select 1 operator(::) 2; +select 1 operator(:=) 2; +select 1 operator(=>) 2; +create operator => (rightarg = int8, function = factorial); +create operator class c for type int using btree as operator 1 ::; +create operator === (leftarg = int, rightarg = int, commutator = ::); + +-- qualifiers are ColId, so type function name keywords aren't allowed +select 1 operator(binary.+) 2; +select 1 operator(left.+) 2; +select 1 operator(collation.+) 2; +select 1 operator(a.binary.+) 2; +create operator binary.+ (leftarg = int, rightarg = int, function = int4pl); + +-- order by using takes qual_all_Op, a qualified operator needs operator(...) +select 1 from t order by a using and; +select 1 from t order by a using pg_catalog.<; + +-- same for the operator of an exclusion constraint +create table t (c int, exclude using gist (c with and)); +create table t (c int, exclude using gist (c with binary.=)); diff --git a/crates/squawk_parser/tests/data/ok/create_table.sql b/crates/squawk_parser/tests/data/ok/create_table.sql index 6079f341a..e55f75036 100644 --- a/crates/squawk_parser/tests/data/ok/create_table.sql +++ b/crates/squawk_parser/tests/data/ok/create_table.sql @@ -235,6 +235,10 @@ create table t ( exclude using btree ( a with f.buzz.> ) where ( a > 10 ) ); +-- exclude constraint with an explicit operator call +create table t (c int, exclude using gist (c with operator(pg_catalog.=))); +create table t (c int, exclude using gist (c with operator(=))); + -- exclude constraint multiple exclusions create table t ( a int, diff --git a/crates/squawk_parser/tests/data/ok/select.sql b/crates/squawk_parser/tests/data/ok/select.sql index 3142f56a3..92343893a 100644 --- a/crates/squawk_parser/tests/data/ok/select.sql +++ b/crates/squawk_parser/tests/data/ok/select.sql @@ -503,6 +503,10 @@ select current_schema; -- order_by_with_custom_op select * from t order by a using >>>; +-- order_by_with_operator_call +select * from t order by a using operator(<); +select * from t order by a using operator(pg_catalog.<); + -- order_by_regression SELECT sensor_id, DATE_TRUNC('day', ts) AS day, MAX(value) AS max_value, MIN(value) AS min_value FROM sensors_uncompressed diff --git a/crates/squawk_parser/tests/snapshots/tests__create_table_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_table_ok.snap index 5e290a4cd..880706bb8 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_table_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_table_ok.snap @@ -2115,6 +2115,112 @@ SOURCE_FILE R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" + COMMENT "-- exclude constraint with an explicit operator call" + WHITESPACE "\n" + CREATE_TABLE + CREATE_KW "create" + WHITESPACE " " + TABLE_KW "table" + WHITESPACE " " + TABLE_NAME + PATH + PATH_SEGMENT + IDENT "t" + WHITESPACE " " + TABLE_ARG_LIST + L_PAREN "(" + COLUMN + COLUMN_NAME + IDENT "c" + WHITESPACE " " + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + INT_KW "int" + COMMA "," + WHITESPACE " " + EXCLUDE_CONSTRAINT + EXCLUDE_KW "exclude" + WHITESPACE " " + CONSTRAINT_INDEX_METHOD + USING_KW "using" + WHITESPACE " " + ACCESS_METHOD_REF + IDENT "gist" + WHITESPACE " " + CONSTRAINT_EXCLUSION_LIST + L_PAREN "(" + CONSTRAINT_EXCLUSION + NAME_REF + IDENT "c" + WHITESPACE " " + WITH_KW "with" + WHITESPACE " " + OPERATOR_CALL + OPERATOR_KW "operator" + L_PAREN "(" + OP + PATH_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "pg_catalog" + DOT "." + PATH_SEGMENT_REF + EQ "=" + R_PAREN ")" + R_PAREN ")" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + CREATE_TABLE + CREATE_KW "create" + WHITESPACE " " + TABLE_KW "table" + WHITESPACE " " + TABLE_NAME + PATH + PATH_SEGMENT + IDENT "t" + WHITESPACE " " + TABLE_ARG_LIST + L_PAREN "(" + COLUMN + COLUMN_NAME + IDENT "c" + WHITESPACE " " + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + INT_KW "int" + COMMA "," + WHITESPACE " " + EXCLUDE_CONSTRAINT + EXCLUDE_KW "exclude" + WHITESPACE " " + CONSTRAINT_INDEX_METHOD + USING_KW "using" + WHITESPACE " " + ACCESS_METHOD_REF + IDENT "gist" + WHITESPACE " " + CONSTRAINT_EXCLUSION_LIST + L_PAREN "(" + CONSTRAINT_EXCLUSION + NAME_REF + IDENT "c" + WHITESPACE " " + WITH_KW "with" + WHITESPACE " " + OPERATOR_CALL + OPERATOR_KW "operator" + L_PAREN "(" + OP + EQ "=" + R_PAREN ")" + R_PAREN ")" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n\n" COMMENT "-- exclude constraint multiple exclusions" WHITESPACE "\n" CREATE_TABLE diff --git a/crates/squawk_parser/tests/snapshots/tests__operator_err.snap b/crates/squawk_parser/tests/snapshots/tests__operator_err.snap new file mode 100644 index 000000000..8ba7851f9 --- /dev/null +++ b/crates/squawk_parser/tests/snapshots/tests__operator_err.snap @@ -0,0 +1,787 @@ +--- +source: crates/squawk_parser/tests/tests.rs +input_file: crates/squawk_parser/tests/data/err/operator.sql +--- +SOURCE_FILE + COMMENT "-- all_Op is symbolic only, keyword operators aren't operator names" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + BIN_EXPR + LITERAL + INT_NUMBER "1" + WHITESPACE " " + OPERATOR_CALL + OPERATOR_KW "operator" + L_PAREN "(" + OP + ERROR + AND_KW "and" + R_PAREN ")" + WHITESPACE " " + LITERAL + INT_NUMBER "2" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + BIN_EXPR + LITERAL + INT_NUMBER "1" + WHITESPACE " " + OPERATOR_CALL + OPERATOR_KW "operator" + L_PAREN "(" + OP + ERROR + OR_KW "or" + R_PAREN ")" + WHITESPACE " " + LITERAL + INT_NUMBER "2" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + BIN_EXPR + LITERAL + INT_NUMBER "1" + WHITESPACE " " + OPERATOR_CALL + OPERATOR_KW "operator" + L_PAREN "(" + OP + ERROR + IN_KW "in" + R_PAREN ")" + WHITESPACE " " + LITERAL + INT_NUMBER "2" + SEMICOLON ";" + WHITESPACE "\n" + CREATE_OPERATOR + CREATE_KW "create" + WHITESPACE " " + OPERATOR_KW "operator" + WHITESPACE " " + OP + ERROR + AND_KW "and" + WHITESPACE " " + ATTRIBUTE_LIST + L_PAREN "(" + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "leftarg" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + INT_KW "int" + COMMA "," + WHITESPACE " " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "rightarg" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + INT_KW "int" + COMMA "," + WHITESPACE " " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + FUNCTION_KW "function" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + IDENT "int4pl" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + DROP_OPERATOR + DROP_KW "drop" + WHITESPACE " " + OPERATOR_KW "operator" + WHITESPACE " " + OP_SIG_LIST + OP_SIG + OP + ERROR + AND_KW "and" + WHITESPACE " " + L_PAREN "(" + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + INT_KW "int" + COMMA "," + WHITESPACE " " + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + INT_KW "int" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- neither are the tokens Postgres reserves" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + BIN_EXPR + LITERAL + INT_NUMBER "1" + WHITESPACE " " + OPERATOR_CALL + OPERATOR_KW "operator" + L_PAREN "(" + OP + ERROR + COLON_COLON + COLON ":" + COLON ":" + R_PAREN ")" + WHITESPACE " " + LITERAL + INT_NUMBER "2" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + BIN_EXPR + LITERAL + INT_NUMBER "1" + WHITESPACE " " + OPERATOR_CALL + OPERATOR_KW "operator" + L_PAREN "(" + OP + ERROR + COLON_EQ ":=" + R_PAREN ")" + WHITESPACE " " + LITERAL + INT_NUMBER "2" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + BIN_EXPR + LITERAL + INT_NUMBER "1" + WHITESPACE " " + OPERATOR_CALL + OPERATOR_KW "operator" + L_PAREN "(" + OP + ERROR + FAT_ARROW "=>" + R_PAREN ")" + WHITESPACE " " + LITERAL + INT_NUMBER "2" + SEMICOLON ";" + WHITESPACE "\n" + CREATE_OPERATOR + CREATE_KW "create" + WHITESPACE " " + OPERATOR_KW "operator" + WHITESPACE " " + OP + ERROR + FAT_ARROW "=>" + WHITESPACE " " + ATTRIBUTE_LIST + L_PAREN "(" + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "rightarg" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + IDENT "int8" + COMMA "," + WHITESPACE " " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + FUNCTION_KW "function" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + IDENT "factorial" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + CREATE_OPERATOR_CLASS + CREATE_KW "create" + WHITESPACE " " + OPERATOR_KW "operator" + WHITESPACE " " + CLASS_KW "class" + WHITESPACE " " + OP_CLASS_NAME + PATH + PATH_SEGMENT + IDENT "c" + WHITESPACE " " + FOR_KW "for" + WHITESPACE " " + TYPE_KW "type" + WHITESPACE " " + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + INT_KW "int" + WHITESPACE " " + USING_METHOD + USING_KW "using" + WHITESPACE " " + ACCESS_METHOD_REF + IDENT "btree" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + OPERATOR_CLASS_OPTION_LIST + OP_CLASS_OPTION_OPERATOR + OPERATOR_KW "operator" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + WHITESPACE " " + OP + ERROR + COLON_COLON + COLON ":" + COLON ":" + SEMICOLON ";" + WHITESPACE "\n" + CREATE_OPERATOR + CREATE_KW "create" + WHITESPACE " " + OPERATOR_KW "operator" + WHITESPACE " " + OP + CUSTOM_OP + EQ "=" + EQ "=" + EQ "=" + WHITESPACE " " + ATTRIBUTE_LIST + L_PAREN "(" + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "leftarg" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + INT_KW "int" + COMMA "," + WHITESPACE " " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "rightarg" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + INT_KW "int" + COMMA "," + WHITESPACE " " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "commutator" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + ERROR + COLON ":" + COLON ":" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- qualifiers are ColId, so type function name keywords aren't allowed" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + BIN_EXPR + LITERAL + INT_NUMBER "1" + WHITESPACE " " + OPERATOR_CALL + OPERATOR_KW "operator" + L_PAREN "(" + OP + PATH_REF + PATH_REF + PATH_SEGMENT_REF + ERROR + BINARY_KW "binary" + DOT "." + PATH_SEGMENT_REF + PLUS "+" + R_PAREN ")" + WHITESPACE " " + LITERAL + INT_NUMBER "2" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + BIN_EXPR + LITERAL + INT_NUMBER "1" + WHITESPACE " " + OPERATOR_CALL + OPERATOR_KW "operator" + L_PAREN "(" + OP + PATH_REF + PATH_REF + PATH_SEGMENT_REF + ERROR + LEFT_KW "left" + DOT "." + PATH_SEGMENT_REF + PLUS "+" + R_PAREN ")" + WHITESPACE " " + LITERAL + INT_NUMBER "2" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + BIN_EXPR + LITERAL + INT_NUMBER "1" + WHITESPACE " " + OPERATOR_CALL + OPERATOR_KW "operator" + L_PAREN "(" + OP + PATH_REF + PATH_REF + PATH_SEGMENT_REF + ERROR + COLLATION_KW "collation" + DOT "." + PATH_SEGMENT_REF + PLUS "+" + R_PAREN ")" + WHITESPACE " " + LITERAL + INT_NUMBER "2" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + BIN_EXPR + LITERAL + INT_NUMBER "1" + WHITESPACE " " + OPERATOR_CALL + OPERATOR_KW "operator" + L_PAREN "(" + OP + PATH_REF + PATH_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "a" + DOT "." + PATH_SEGMENT_REF + ERROR + BINARY_KW "binary" + DOT "." + PATH_SEGMENT_REF + PLUS "+" + R_PAREN ")" + WHITESPACE " " + LITERAL + INT_NUMBER "2" + SEMICOLON ";" + WHITESPACE "\n" + CREATE_OPERATOR + CREATE_KW "create" + WHITESPACE " " + OPERATOR_KW "operator" + WHITESPACE " " + OP + PATH_REF + PATH_REF + PATH_SEGMENT_REF + ERROR + BINARY_KW "binary" + DOT "." + PATH_SEGMENT_REF + PLUS "+" + WHITESPACE " " + ATTRIBUTE_LIST + L_PAREN "(" + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "leftarg" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + INT_KW "int" + COMMA "," + WHITESPACE " " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "rightarg" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + INT_KW "int" + COMMA "," + WHITESPACE " " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + FUNCTION_KW "function" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + IDENT "int4pl" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- order by using takes qual_all_Op, a qualified operator needs operator(...)" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + WHITESPACE " " + FROM_CLAUSE + FROM_KW "from" + WHITESPACE " " + RELATION_FROM_ITEM + RELATION_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "t" + WHITESPACE " " + ORDER_BY_CLAUSE + ORDER_KW "order" + WHITESPACE " " + BY_KW "by" + WHITESPACE " " + SORT_BY_LIST + SORT_BY + NAME_REF + IDENT "a" + WHITESPACE " " + SORT_USING + USING_KW "using" + WHITESPACE " " + OP + ERROR + AND_KW "and" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + WHITESPACE " " + FROM_CLAUSE + FROM_KW "from" + WHITESPACE " " + RELATION_FROM_ITEM + RELATION_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "t" + WHITESPACE " " + ORDER_BY_CLAUSE + ORDER_KW "order" + WHITESPACE " " + BY_KW "by" + WHITESPACE " " + SORT_BY_LIST + SORT_BY + NAME_REF + IDENT "a" + WHITESPACE " " + SORT_USING + USING_KW "using" + WHITESPACE " " + OP + PATH_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "pg_catalog" + DOT "." + PATH_SEGMENT_REF + L_ANGLE "<" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- same for the operator of an exclusion constraint" + WHITESPACE "\n" + CREATE_TABLE + CREATE_KW "create" + WHITESPACE " " + TABLE_KW "table" + WHITESPACE " " + TABLE_NAME + PATH + PATH_SEGMENT + IDENT "t" + WHITESPACE " " + TABLE_ARG_LIST + L_PAREN "(" + COLUMN + COLUMN_NAME + IDENT "c" + WHITESPACE " " + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + INT_KW "int" + COMMA "," + WHITESPACE " " + EXCLUDE_CONSTRAINT + EXCLUDE_KW "exclude" + WHITESPACE " " + CONSTRAINT_INDEX_METHOD + USING_KW "using" + WHITESPACE " " + ACCESS_METHOD_REF + IDENT "gist" + WHITESPACE " " + CONSTRAINT_EXCLUSION_LIST + L_PAREN "(" + CONSTRAINT_EXCLUSION + NAME_REF + IDENT "c" + WHITESPACE " " + WITH_KW "with" + WHITESPACE " " + OP + ERROR + AND_KW "and" + R_PAREN ")" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + CREATE_TABLE + CREATE_KW "create" + WHITESPACE " " + TABLE_KW "table" + WHITESPACE " " + TABLE_NAME + PATH + PATH_SEGMENT + IDENT "t" + WHITESPACE " " + TABLE_ARG_LIST + L_PAREN "(" + COLUMN + COLUMN_NAME + IDENT "c" + WHITESPACE " " + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + INT_KW "int" + COMMA "," + WHITESPACE " " + EXCLUDE_CONSTRAINT + EXCLUDE_KW "exclude" + WHITESPACE " " + CONSTRAINT_INDEX_METHOD + USING_KW "using" + WHITESPACE " " + ACCESS_METHOD_REF + IDENT "gist" + WHITESPACE " " + CONSTRAINT_EXCLUSION_LIST + L_PAREN "(" + CONSTRAINT_EXCLUSION + NAME_REF + IDENT "c" + WHITESPACE " " + WITH_KW "with" + WHITESPACE " " + OP + PATH_REF + PATH_REF + PATH_SEGMENT_REF + ERROR + BINARY_KW "binary" + DOT "." + PATH_SEGMENT_REF + EQ "=" + R_PAREN ")" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" +--- +error[syntax-error]: expected operator, got AND_KW + ╭▸ +2 │ select 1 operator(and) 2; + ╰╴ ━ +error[syntax-error]: expected operator, got OR_KW + ╭▸ +3 │ select 1 operator(or) 2; + ╰╴ ━ +error[syntax-error]: expected operator, got IN_KW + ╭▸ +4 │ select 1 operator(in) 2; + ╰╴ ━ +error[syntax-error]: expected operator, got AND_KW + ╭▸ +5 │ create operator and (leftarg = int, rightarg = int, function = int4pl); + ╰╴ ━ +error[syntax-error]: expected operator, got AND_KW + ╭▸ +6 │ drop operator and (int, int); + ╰╴ ━ +error[syntax-error]: expected operator, got COLON + ╭▸ +9 │ select 1 operator(::) 2; + ╰╴ ━ +error[syntax-error]: expected operator, got COLON + ╭▸ +10 │ select 1 operator(:=) 2; + ╰╴ ━ +error[syntax-error]: expected operator, got EQ + ╭▸ +11 │ select 1 operator(=>) 2; + ╰╴ ━ +error[syntax-error]: expected operator, got EQ + ╭▸ +12 │ create operator => (rightarg = int8, function = factorial); + ╰╴ ━ +error[syntax-error]: expected operator, got COLON + ╭▸ +13 │ create operator class c for type int using btree as operator 1 ::; + ╰╴ ━ +error[syntax-error]: expected attribute value, got COLON + ╭▸ +14 │ create operator === (leftarg = int, rightarg = int, commutator = ::); + ╰╴ ━ +error[syntax-error]: expected name + ╭▸ +17 │ select 1 operator(binary.+) 2; + ╰╴ ━ +error[syntax-error]: expected name + ╭▸ +18 │ select 1 operator(left.+) 2; + ╰╴ ━ +error[syntax-error]: expected name + ╭▸ +19 │ select 1 operator(collation.+) 2; + ╰╴ ━ +error[syntax-error]: expected name + ╭▸ +20 │ select 1 operator(a.binary.+) 2; + ╰╴ ━ +error[syntax-error]: expected name + ╭▸ +21 │ create operator binary.+ (leftarg = int, rightarg = int, function = int4pl); + ╰╴ ━ +error[syntax-error]: expected operator, got AND_KW + ╭▸ +24 │ select 1 from t order by a using and; + ╰╴ ━ +error[syntax-error]: qualified operator requires OPERATOR(...) + ╭▸ +25 │ select 1 from t order by a using pg_catalog.<; + ╰╴ ━ +error[syntax-error]: expected operator, got AND_KW + ╭▸ +28 │ create table t (c int, exclude using gist (c with and)); + ╰╴ ━ +error[syntax-error]: expected name + ╭▸ +29 │ create table t (c int, exclude using gist (c with binary.=)); + ╰╴ ━ diff --git a/crates/squawk_parser/tests/snapshots/tests__select_ok.snap b/crates/squawk_parser/tests/snapshots/tests__select_ok.snap index 6b9b963da..409e6d1e6 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_ok.snap @@ -6478,6 +6478,90 @@ SOURCE_FILE R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n\n" + COMMENT "-- order_by_with_operator_call" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + STAR "*" + WHITESPACE " " + FROM_CLAUSE + FROM_KW "from" + WHITESPACE " " + RELATION_FROM_ITEM + RELATION_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "t" + WHITESPACE " " + ORDER_BY_CLAUSE + ORDER_KW "order" + WHITESPACE " " + BY_KW "by" + WHITESPACE " " + SORT_BY_LIST + SORT_BY + NAME_REF + IDENT "a" + WHITESPACE " " + SORT_USING + USING_KW "using" + WHITESPACE " " + OPERATOR_CALL + OPERATOR_KW "operator" + L_PAREN "(" + OP + L_ANGLE "<" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + STAR "*" + WHITESPACE " " + FROM_CLAUSE + FROM_KW "from" + WHITESPACE " " + RELATION_FROM_ITEM + RELATION_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "t" + WHITESPACE " " + ORDER_BY_CLAUSE + ORDER_KW "order" + WHITESPACE " " + BY_KW "by" + WHITESPACE " " + SORT_BY_LIST + SORT_BY + NAME_REF + IDENT "a" + WHITESPACE " " + SORT_USING + USING_KW "using" + WHITESPACE " " + OPERATOR_CALL + OPERATOR_KW "operator" + L_PAREN "(" + OP + PATH_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "pg_catalog" + DOT "." + PATH_SEGMENT_REF + L_ANGLE "<" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n\n" COMMENT "-- order_by_regression" WHITESPACE "\n" SELECT diff --git a/crates/squawk_syntax/src/ast/generated/nodes.rs b/crates/squawk_syntax/src/ast/generated/nodes.rs index dbfe0f72a..e1873d6c5 100644 --- a/crates/squawk_syntax/src/ast/generated/nodes.rs +++ b/crates/squawk_syntax/src/ast/generated/nodes.rs @@ -4387,6 +4387,10 @@ impl ConstraintExclusion { support::child(&self.syntax) } #[inline] + pub fn operator_call(&self) -> Option { + support::child(&self.syntax) + } + #[inline] pub fn with_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::WITH_KW) } @@ -24400,6 +24404,10 @@ impl SortUsing { support::child(&self.syntax) } #[inline] + pub fn operator_call(&self) -> Option { + support::child(&self.syntax) + } + #[inline] pub fn using_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::USING_KW) } diff --git a/crates/squawk_syntax/src/postgresql.ungram b/crates/squawk_syntax/src/postgresql.ungram index 3f98e851e..2556a14f8 100644 --- a/crates/squawk_syntax/src/postgresql.ungram +++ b/crates/squawk_syntax/src/postgresql.ungram @@ -2940,7 +2940,7 @@ SortDesc = 'desc' SortUsing = - 'using' Op + 'using' (Op | OperatorCall) SortBy = Expr SortOrder? NullsOrder? @@ -3041,7 +3041,7 @@ ConstraintExclusionList = '(' (ConstraintExclusion (',' ConstraintExclusion)*) ')' ConstraintExclusion = - Expr 'with' Op + Expr 'with' (Op | OperatorCall) WhereConditionClause = 'where' '(' Expr ')' diff --git a/crates/xtask/src/sync_pg.rs b/crates/xtask/src/sync_pg.rs index 2c257351c..cce40a7c7 100644 --- a/crates/xtask/src/sync_pg.rs +++ b/crates/xtask/src/sync_pg.rs @@ -26,6 +26,7 @@ const START_END_MARKERS: &[(&str, &str)] = &[ "-- TO is used for the bound but not the INTERVAL:", " WHERE id = '[1,2)';", ), + ("-- => is disallowed as an operator name now", ");"), ]; const IGNORED_LINES: &[&str] = &[ diff --git a/postgres/regression_suite/create_operator.sql b/postgres/regression_suite/create_operator.sql index d993ea166..957460b5d 100644 --- a/postgres/regression_suite/create_operator.sql +++ b/postgres/regression_suite/create_operator.sql @@ -39,11 +39,11 @@ DROP OPERATOR ###### (NONE, int4); DROP OPERATOR ###### (int4, NONE); DROP OPERATOR ###### (int4, int8); --- => is disallowed as an operator name now -CREATE OPERATOR => ( - rightarg = int8, - procedure = factorial -); +-- -- => is disallowed as an operator name now +-- CREATE OPERATOR => ( +-- rightarg = int8, +-- procedure = factorial +-- ); -- lexing of <=, >=, <>, != has a number of edge cases -- (=> is tested elsewhere)