diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 0a3a57955180356f2c6b826390b12052d1efe0e4..b4f4e290bf87a4c5f93807aba5aa71783118781d 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -879,7 +879,7 @@ transformAExprOp(ParseState *pstate, A_Expr *a) else if (lexpr && IsA(lexpr, RowExpr) && rexpr && IsA(rexpr, RowExpr)) { - /* "row op row" */ + /* ROW() op ROW() is handled specially */ lexpr = transformExpr(pstate, lexpr); rexpr = transformExpr(pstate, rexpr); Assert(IsA(lexpr, RowExpr)); @@ -984,7 +984,7 @@ transformAExprDistinct(ParseState *pstate, A_Expr *a) if (lexpr && IsA(lexpr, RowExpr) && rexpr && IsA(rexpr, RowExpr)) { - /* "row op row" */ + /* ROW() op ROW() is handled specially */ return make_row_distinct_op(pstate, a->name, (RowExpr *) lexpr, (RowExpr *) rexpr, @@ -1083,7 +1083,6 @@ transformAExprIn(ParseState *pstate, A_Expr *a) List *rvars; List *rnonvars; bool useOr; - bool haveRowExpr; ListCell *l; /* @@ -1096,24 +1095,21 @@ transformAExprIn(ParseState *pstate, A_Expr *a) /* * We try to generate a ScalarArrayOpExpr from IN/NOT IN, but this is only - * possible if the inputs are all scalars (no RowExprs) and there is a - * suitable array type available. If not, we fall back to a boolean - * condition tree with multiple copies of the lefthand expression. Also, - * any IN-list items that contain Vars are handled as separate boolean - * conditions, because that gives the planner more scope for optimization - * on such clauses. + * possible if there is a suitable array type available. If not, we fall + * back to a boolean condition tree with multiple copies of the lefthand + * expression. Also, any IN-list items that contain Vars are handled as + * separate boolean conditions, because that gives the planner more scope + * for optimization on such clauses. * - * First step: transform all the inputs, and detect whether any are - * RowExprs or contain Vars. + * First step: transform all the inputs, and detect whether any contain + * Vars. */ lexpr = transformExpr(pstate, a->lexpr); - haveRowExpr = (lexpr && IsA(lexpr, RowExpr)); rexprs = rvars = rnonvars = NIL; foreach(l, (List *) a->rexpr) { Node *rexpr = transformExpr(pstate, lfirst(l)); - haveRowExpr |= (rexpr && IsA(rexpr, RowExpr)); rexprs = lappend(rexprs, rexpr); if (contain_vars_of_level(rexpr, 0)) rvars = lappend(rvars, rexpr); @@ -1123,9 +1119,9 @@ transformAExprIn(ParseState *pstate, A_Expr *a) /* * ScalarArrayOpExpr is only going to be useful if there's more than one - * non-Var righthand item. Also, it won't work for RowExprs. + * non-Var righthand item. */ - if (!haveRowExpr && list_length(rnonvars) > 1) + if (list_length(rnonvars) > 1) { List *allexprs; Oid scalar_type; @@ -1141,8 +1137,13 @@ transformAExprIn(ParseState *pstate, A_Expr *a) allexprs = list_concat(list_make1(lexpr), rnonvars); scalar_type = select_common_type(pstate, allexprs, NULL, NULL); - /* Do we have an array type to use? */ - if (OidIsValid(scalar_type)) + /* + * Do we have an array type to use? Aside from the case where there + * isn't one, we don't risk using ScalarArrayOpExpr when the common + * type is RECORD, because the RowExpr comparison logic below can cope + * with some cases of non-identical row types. + */ + if (OidIsValid(scalar_type) && scalar_type != RECORDOID) array_type = get_array_type(scalar_type); else array_type = InvalidOid; @@ -1193,14 +1194,10 @@ transformAExprIn(ParseState *pstate, A_Expr *a) Node *rexpr = (Node *) lfirst(l); Node *cmp; - if (haveRowExpr) + if (IsA(lexpr, RowExpr) && + IsA(rexpr, RowExpr)) { - if (!IsA(lexpr, RowExpr) || - !IsA(rexpr, RowExpr)) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("arguments of row IN must all be row expressions"), - parser_errposition(pstate, a->location))); + /* ROW() op ROW() is handled specially */ cmp = make_row_comparison_op(pstate, a->name, (List *) copyObject(((RowExpr *) lexpr)->args), @@ -1208,11 +1205,14 @@ transformAExprIn(ParseState *pstate, A_Expr *a) a->location); } else + { + /* Ordinary scalar operator */ cmp = (Node *) make_op(pstate, a->name, copyObject(lexpr), rexpr, a->location); + } cmp = coerce_to_boolean(pstate, cmp, "IN"); if (result == NULL) diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out index a5ddbdc140566346d2e91d06bc5145697b310751..e4b67e7c771c7b5695934ebb9b9af96ff856b015 100644 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@ -205,6 +205,25 @@ ERROR: could not determine interpretation of row comparison operator ~~ LINE 1: select ROW('ABC','DEF') ~~ ROW('DEF','ABC') as fail; ^ HINT: Row comparison operators must be associated with btree operator families. +-- Comparisons of ROW() expressions can cope with some type mismatches +select ROW(1,2) = ROW(1,2::int8); + ?column? +---------- + t +(1 row) + +select ROW(1,2) in (ROW(3,4), ROW(1,2)); + ?column? +---------- + t +(1 row) + +select ROW(1,2) in (ROW(3,4), ROW(1,2::int8)); + ?column? +---------- + t +(1 row) + -- Check row comparison with a subselect select unique1, unique2 from tenk1 where (unique1, unique2) < any (select ten, ten from tenk1 where hundred < 3) @@ -217,6 +236,16 @@ order by 1; (2 rows) -- Also check row comparison with an indexable condition +explain (costs off) +select thousand, tenthous from tenk1 +where (thousand, tenthous) >= (997, 5000) +order by thousand, tenthous; + QUERY PLAN +----------------------------------------------------------- + Index Only Scan using tenk1_thous_tenthous on tenk1 + Index Cond: (ROW(thousand, tenthous) >= ROW(997, 5000)) +(2 rows) + select thousand, tenthous from tenk1 where (thousand, tenthous) >= (997, 5000) order by thousand, tenthous; @@ -249,6 +278,26 @@ order by thousand, tenthous; 999 | 9999 (25 rows) +-- Check row comparisons with IN +select * from int8_tbl i8 where i8 in (row(123,456)); -- fail, type mismatch +ERROR: cannot compare dissimilar column types bigint and integer at record column 1 +explain (costs off) +select * from int8_tbl i8 +where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)'); + QUERY PLAN +------------------------------------------------------------------------------------------------------------- + Seq Scan on int8_tbl i8 + Filter: (i8.* = ANY (ARRAY[ROW(123::bigint, 456::bigint)::int8_tbl, '(4567890123456789,123)'::int8_tbl])) +(2 rows) + +select * from int8_tbl i8 +where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)'); + q1 | q2 +------------------+----- + 123 | 456 + 4567890123456789 | 123 +(2 rows) + -- Check some corner cases involving empty rowtypes select ROW(); row diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql index 55e1ff9a9e92c0d0292940458af8514a9131ecd3..3e0f156945335e44a8fee474a9dea95d29fd0043 100644 --- a/src/test/regress/sql/rowtypes.sql +++ b/src/test/regress/sql/rowtypes.sql @@ -95,6 +95,11 @@ select ROW('ABC','DEF') ~<=~ ROW('DEF','ABC') as true; select ROW('ABC','DEF') ~>=~ ROW('DEF','ABC') as false; select ROW('ABC','DEF') ~~ ROW('DEF','ABC') as fail; +-- Comparisons of ROW() expressions can cope with some type mismatches +select ROW(1,2) = ROW(1,2::int8); +select ROW(1,2) in (ROW(3,4), ROW(1,2)); +select ROW(1,2) in (ROW(3,4), ROW(1,2::int8)); + -- Check row comparison with a subselect select unique1, unique2 from tenk1 where (unique1, unique2) < any (select ten, ten from tenk1 where hundred < 3) @@ -102,10 +107,25 @@ where (unique1, unique2) < any (select ten, ten from tenk1 where hundred < 3) order by 1; -- Also check row comparison with an indexable condition +explain (costs off) +select thousand, tenthous from tenk1 +where (thousand, tenthous) >= (997, 5000) +order by thousand, tenthous; + select thousand, tenthous from tenk1 where (thousand, tenthous) >= (997, 5000) order by thousand, tenthous; +-- Check row comparisons with IN +select * from int8_tbl i8 where i8 in (row(123,456)); -- fail, type mismatch + +explain (costs off) +select * from int8_tbl i8 +where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)'); + +select * from int8_tbl i8 +where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)'); + -- Check some corner cases involving empty rowtypes select ROW(); select ROW() IS NULL;