diff --git a/docs/generated/sql/operators.md b/docs/generated/sql/operators.md
index 9f01f3508286..b7c2af9b6893 100644
--- a/docs/generated/sql/operators.md
+++ b/docs/generated/sql/operators.md
@@ -193,6 +193,7 @@
oid < int | bool |
oid < oid | bool |
pg_lsn < pg_lsn | bool |
+refcursor < refcursor | bool |
string < string | bool |
string[] < string[] | bool |
time < time | bool |
@@ -257,6 +258,7 @@
oid <= int | bool |
oid <= oid | bool |
pg_lsn <= pg_lsn | bool |
+refcursor <= refcursor | bool |
string <= string | bool |
string[] <= string[] | bool |
time <= time | bool |
@@ -320,6 +322,7 @@
oid = int | bool |
oid = oid | bool |
pg_lsn = pg_lsn | bool |
+refcursor = refcursor | bool |
string = string | bool |
string[] = string[] | bool |
time = time | bool |
@@ -400,6 +403,7 @@
jsonb IN tuple | bool |
oid IN tuple | bool |
pg_lsn IN tuple | bool |
+refcursor IN tuple | bool |
string IN tuple | bool |
time IN tuple | bool |
timestamp IN tuple | bool |
@@ -447,7 +451,7 @@
oid IS NOT DISTINCT FROM int | bool |
oid IS NOT DISTINCT FROM oid | bool |
pg_lsn IS NOT DISTINCT FROM pg_lsn | bool |
-refcursor IS NOT DISTINCT FROM unknown | bool |
+refcursor IS NOT DISTINCT FROM refcursor | bool |
string IS NOT DISTINCT FROM string | bool |
string[] IS NOT DISTINCT FROM string[] | bool |
time IS NOT DISTINCT FROM time | bool |
diff --git a/pkg/sql/logictest/testdata/logic_test/refcursor b/pkg/sql/logictest/testdata/logic_test/refcursor
index 3e1431529696..6f236c3eb864 100644
--- a/pkg/sql/logictest/testdata/logic_test/refcursor
+++ b/pkg/sql/logictest/testdata/logic_test/refcursor
@@ -394,59 +394,59 @@ NULL NULL NULL foo NULL NULL NULL NULL NULL NULL NULL NULL
# REFCURSOR doesn't support any comparisons (with an exception mentioned below).
subtest comparisons
-# TODO(drewk): The error code should be 42883.
-statement error pgcode 22023 pq: unsupported comparison operator
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR = 'foo'::REFCURSOR;
+# TODO(drewk): The error code should be 42883.
statement error pgcode 22023 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR = 'foo'::TEXT;
-statement error pgcode 22023 pq: unsupported comparison operator
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR = NULL;
-statement error pgcode 22023 pq: unsupported comparison operator
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR < 'foo'::REFCURSOR;
statement error pgcode 22023 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR < 'foo'::TEXT;
-statement error pgcode 22023 pq: unsupported comparison operator
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR < NULL;
-statement error pgcode 22023 pq: unsupported comparison operator
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR > 'foo'::REFCURSOR;
statement error pgcode 22023 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR > 'foo'::TEXT;
-statement error pgcode 22023 pq: unsupported comparison operator
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR > NULL;
-statement error pgcode 22023 pq: unsupported comparison operator
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR <= 'foo'::REFCURSOR;
statement error pgcode 22023 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR <= 'foo'::TEXT;
-statement error pgcode 22023 pq: unsupported comparison operator
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR <= NULL;
-statement error pgcode 22023 pq: unsupported comparison operator
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR >= 'foo'::REFCURSOR;
statement error pgcode 22023 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR >= 'foo'::TEXT;
-statement error pgcode 22023 pq: unsupported comparison operator
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR >= NULL;
-statement error pgcode 22023 pq: unsupported comparison operator
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR IS DISTINCT FROM 'foo'::REFCURSOR;
statement error pgcode 22023 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR IS DISTINCT FROM 'foo'::TEXT;
-statement error pgcode 22023 pq: unsupported comparison operator
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR IS NOT DISTINCT FROM 'foo'::REFCURSOR;
statement error pgcode 22023 pq: unsupported comparison operator
@@ -514,17 +514,17 @@ true
true
# Comparison with column is not allowed.
-statement error pgcode 22023 pq: unsupported comparison operator
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR IS DISTINCT FROM l FROM implicit_types;
-statement error pgcode 22023 pq: unsupported comparison operator
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR IS NOT DISTINCT FROM l FROM implicit_types;
# Comparison with typed NULL is not allowed.
-statement error pgcode 22023 pq: unsupported comparison operator
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR IS DISTINCT FROM (NULL::REFCURSOR);
-statement error pgcode 22023 pq: unsupported comparison operator
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR IS NOT DISTINCT FROM (NULL::REFCURSOR);
# Comparison with CASE expression is not allowed.
@@ -534,23 +534,18 @@ SELECT 'foo'::REFCURSOR IS DISTINCT FROM (CASE WHEN true THEN NULL ELSE 1 END);
statement error pgcode 22023 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR IS NOT DISTINCT FROM (CASE WHEN true THEN NULL ELSE 1 END);
-# The CASE operator is folded into an untyped NULL, so it's allowed.
-# TODO(drewk): Postgres doesn't allow this case.
-query B
+# The CASE operator is folded into an untyped NULL.
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR IS DISTINCT FROM (CASE WHEN true THEN NULL ELSE NULL END);
-----
-true
-query B
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR IS NOT DISTINCT FROM (CASE WHEN true THEN NULL ELSE NULL END);
-----
-false
-# The CASE operator is folded into a typed NULL, so it's not allowed.
-statement error pgcode 22023 pq: unsupported comparison operator
+# The CASE operator is folded into a typed NULL.
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR IS DISTINCT FROM (CASE WHEN true THEN NULL ELSE NULL::REFCURSOR END);
-statement error pgcode 22023 pq: unsupported comparison operator
+statement error pgcode 42883 pq: unsupported comparison operator
SELECT 'foo'::REFCURSOR IS NOT DISTINCT FROM (CASE WHEN true THEN NULL ELSE NULL::REFCURSOR END);
# Regression test for #112099 - REFCURSOR is not valid as an index column.
diff --git a/pkg/sql/sem/tree/eval.go b/pkg/sql/sem/tree/eval.go
index e9784271e94f..5146bf2d4d5e 100644
--- a/pkg/sql/sem/tree/eval.go
+++ b/pkg/sql/sem/tree/eval.go
@@ -1542,6 +1542,7 @@ var CmpOps = cmpOpFixups(map[treecmp.ComparisonOperatorSymbol]*CmpOpOverloads{
makeEqFn(types.Jsonb, types.Jsonb, volatility.Immutable),
makeEqFn(types.Oid, types.Oid, volatility.Leakproof),
makeEqFn(types.PGLSN, types.PGLSN, volatility.Leakproof),
+ makeEqFn(types.RefCursor, types.RefCursor, volatility.Leakproof),
makeEqFn(types.String, types.String, volatility.Leakproof),
makeEqFn(types.Time, types.Time, volatility.Leakproof),
makeEqFn(types.TimeTZ, types.TimeTZ, volatility.Leakproof),
@@ -1601,6 +1602,7 @@ var CmpOps = cmpOpFixups(map[treecmp.ComparisonOperatorSymbol]*CmpOpOverloads{
makeLtFn(types.Interval, types.Interval, volatility.Leakproof),
makeLtFn(types.Oid, types.Oid, volatility.Leakproof),
makeLtFn(types.PGLSN, types.PGLSN, volatility.Leakproof),
+ makeLtFn(types.RefCursor, types.RefCursor, volatility.Leakproof),
makeLtFn(types.String, types.String, volatility.Leakproof),
makeLtFn(types.Time, types.Time, volatility.Leakproof),
makeLtFn(types.TimeTZ, types.TimeTZ, volatility.Leakproof),
@@ -1659,6 +1661,7 @@ var CmpOps = cmpOpFixups(map[treecmp.ComparisonOperatorSymbol]*CmpOpOverloads{
makeLeFn(types.Interval, types.Interval, volatility.Leakproof),
makeLeFn(types.Oid, types.Oid, volatility.Leakproof),
makeLeFn(types.PGLSN, types.PGLSN, volatility.Leakproof),
+ makeLeFn(types.RefCursor, types.RefCursor, volatility.Leakproof),
makeLeFn(types.String, types.String, volatility.Leakproof),
makeLeFn(types.Time, types.Time, volatility.Leakproof),
makeLeFn(types.TimeTZ, types.TimeTZ, volatility.Leakproof),
@@ -1738,6 +1741,7 @@ var CmpOps = cmpOpFixups(map[treecmp.ComparisonOperatorSymbol]*CmpOpOverloads{
makeIsFn(types.Jsonb, types.Jsonb, volatility.Immutable),
makeIsFn(types.Oid, types.Oid, volatility.Leakproof),
makeIsFn(types.PGLSN, types.PGLSN, volatility.Leakproof),
+ makeIsFn(types.RefCursor, types.RefCursor, volatility.Leakproof),
makeIsFn(types.String, types.String, volatility.Leakproof),
makeIsFn(types.Time, types.Time, volatility.Leakproof),
makeIsFn(types.TimeTZ, types.TimeTZ, volatility.Leakproof),
@@ -1775,10 +1779,6 @@ var CmpOps = cmpOpFixups(map[treecmp.ComparisonOperatorSymbol]*CmpOpOverloads{
makeIsFn(types.Void, types.Unknown, volatility.Leakproof),
makeIsFn(types.Unknown, types.Void, volatility.Leakproof),
- // REFCURSOR cannot be compared with itself, but as a special case, it can
- // be compared with NULL using IS NOT DISTINCT FROM.
- makeIsFn(types.RefCursor, types.Unknown, volatility.Leakproof),
-
// Tuple comparison.
{
LeftType: types.AnyTuple,
@@ -1809,6 +1809,7 @@ var CmpOps = cmpOpFixups(map[treecmp.ComparisonOperatorSymbol]*CmpOpOverloads{
makeEvalTupleIn(types.Jsonb, volatility.Leakproof),
makeEvalTupleIn(types.Oid, volatility.Leakproof),
makeEvalTupleIn(types.PGLSN, volatility.Leakproof),
+ makeEvalTupleIn(types.RefCursor, volatility.Leakproof),
makeEvalTupleIn(types.String, volatility.Leakproof),
makeEvalTupleIn(types.Time, volatility.Leakproof),
makeEvalTupleIn(types.TimeTZ, volatility.Leakproof),
diff --git a/pkg/sql/sem/tree/type_check.go b/pkg/sql/sem/tree/type_check.go
index ca16911b703f..5f7e6bc77d02 100644
--- a/pkg/sql/sem/tree/type_check.go
+++ b/pkg/sql/sem/tree/type_check.go
@@ -928,6 +928,11 @@ func (expr *ComparisonExpr) TypeCheck(
expr.Left,
expr.Right,
)
+ if err == nil {
+ err = checkRefCursorComparison(
+ expr.SubOperator.Symbol, leftTyped.ResolvedType(), rightTyped.ResolvedType(),
+ )
+ }
} else {
leftTyped, rightTyped, cmpOp, alwaysNull, err = typeCheckComparisonOp(
ctx,
@@ -936,6 +941,11 @@ func (expr *ComparisonExpr) TypeCheck(
expr.Left,
expr.Right,
)
+ if err == nil {
+ err = checkRefCursorComparison(
+ expr.Operator.Symbol, leftTyped.ResolvedType(), rightTyped.ResolvedType(),
+ )
+ }
}
if err != nil {
return nil, err
@@ -1503,6 +1513,10 @@ func (expr *NullIfExpr) TypeCheck(
leftType, op, rightType)
return nil, decorateTypeCheckError(err, "incompatible NULLIF expressions")
}
+ err = checkRefCursorComparison(treecmp.EQ, leftType, rightType)
+ if err != nil {
+ return nil, err
+ }
expr.Expr1, expr.Expr2 = typedSubExprs[0], typedSubExprs[1]
expr.typ = retType
@@ -1584,10 +1598,20 @@ func (expr *RangeCond) TypeCheck(
ctx context.Context, semaCtx *SemaContext, desired *types.T,
) (TypedExpr, error) {
leftFromTyped, fromTyped, _, _, err := typeCheckComparisonOp(ctx, semaCtx, treecmp.MakeComparisonOperator(treecmp.GT), expr.Left, expr.From)
+ if err == nil {
+ err = checkRefCursorComparison(
+ treecmp.GT, leftFromTyped.ResolvedType(), fromTyped.ResolvedType(),
+ )
+ }
if err != nil {
return nil, err
}
leftToTyped, toTyped, _, _, err := typeCheckComparisonOp(ctx, semaCtx, treecmp.MakeComparisonOperator(treecmp.LT), expr.Left, expr.To)
+ if err == nil {
+ err = checkRefCursorComparison(
+ treecmp.LT, leftToTyped.ResolvedType(), toTyped.ResolvedType(),
+ )
+ }
if err != nil {
return nil, err
}
@@ -3446,3 +3470,42 @@ func CheckUnsupportedType(ctx context.Context, semaCtx *SemaContext, typ *types.
}
return semaCtx.UnsupportedTypeChecker.CheckType(ctx, typ)
}
+
+// checkRefCursorComparison checks whether the given types are or contain the
+// REFCURSOR data type, which is invalid for comparison. We don't simply remove
+// the relevant comparison overloads because we rely on their existence in
+// various locations throughout the codebase.
+func checkRefCursorComparison(op treecmp.ComparisonOperatorSymbol, left, right *types.T) error {
+ if left.Family() == types.RefCursorFamily || right.Family() == types.RefCursorFamily {
+ if (op == treecmp.IsNotDistinctFrom || op == treecmp.IsDistinctFrom) &&
+ (left == types.Unknown || right == types.Unknown) {
+ // Special case: "REFCURSOR IS [NOT] DISTINCT FROM NULL" is allowed.
+ return nil
+ }
+ return pgerror.Newf(pgcode.UndefinedFunction,
+ "unsupported comparison operator: %s %s %s", left, op, right,
+ )
+ }
+ var checkRecursive func(*types.T) bool
+ checkRecursive = func(typ *types.T) bool {
+ switch typ.Family() {
+ case types.RefCursorFamily:
+ return true
+ case types.TupleFamily:
+ for _, t := range typ.TupleContents() {
+ if checkRecursive(t) {
+ return true
+ }
+ }
+ case types.ArrayFamily:
+ return checkRecursive(typ.ArrayContents())
+ }
+ return false
+ }
+ if checkRecursive(left) || checkRecursive(right) {
+ return pgerror.Newf(pgcode.UndefinedFunction,
+ "could not identify a comparison function for type refcursor",
+ )
+ }
+ return nil
+}