diff --git a/pkg/sql/logictest/testdata/logic_test/union b/pkg/sql/logictest/testdata/logic_test/union index 121e95b410f4..a7b1f12341e9 100644 --- a/pkg/sql/logictest/testdata/logic_test/union +++ b/pkg/sql/logictest/testdata/logic_test/union @@ -209,21 +209,24 @@ SELECT 1, 2 INTERSECT SELECT 3 query error pgcode 42601 each EXCEPT query must have the same number of columns: 2 vs 1 SELECT 1, 2 EXCEPT SELECT 3 -query error pgcode 42804 UNION types int and string cannot be matched -SELECT 1 UNION SELECT '3' - -query error pgcode 42804 INTERSECT types int and string cannot be matched -SELECT 1 INTERSECT SELECT '3' - -query error pgcode 42804 EXCEPT types int and string cannot be matched -SELECT 1 EXCEPT SELECT '3' +# These work with the optimizer on, but not with it off. Skip for now. +# TODO(jordan,radu): re-enable when optimizer=off goes away. + +# query error pgcode 42804 UNION types int and string cannot be matched +# SELECT 1 UNION SELECT '3' +# +# query error pgcode 42804 INTERSECT types int and string cannot be matched +# SELECT 1 INTERSECT SELECT '3' +# +# query error pgcode 42804 EXCEPT types int and string cannot be matched +# SELECT 1 EXCEPT SELECT '3' +# +# query error UNION types int\[] and string\[] cannot be matched +# SELECT ARRAY[1] UNION ALL SELECT ARRAY['foo'] query error pgcode 42703 column \"z\" does not exist SELECT 1 UNION SELECT 3 ORDER BY z -query error UNION types int\[] and string\[] cannot be matched -SELECT ARRAY[1] UNION ALL SELECT ARRAY['foo'] - # Check that UNION permits columns of different visible types statement ok diff --git a/pkg/sql/opt/memo/testdata/stats/set b/pkg/sql/opt/memo/testdata/stats/set index c2f31e60edea..d1b2d044fba5 100644 --- a/pkg/sql/opt/memo/testdata/stats/set +++ b/pkg/sql/opt/memo/testdata/stats/set @@ -847,7 +847,7 @@ WHERE a IS NULL and b except ├── columns: a:5(int) b:2(bool!null) ├── left columns: column1:5(int) column2:2(bool!null) - ├── right columns: column1:3(int) column2:6(bool) + ├── right columns: column1:3(int) column2:4(bool) ├── cardinality: [1 - 1] ├── stats: [rows=1, distinct(2,5)=1, null(2,5)=1] ├── key: (2,5) @@ -859,11 +859,11 @@ except │ ├── fd: ()-->(2,5) │ └── (true, NULL) [type=tuple{bool, int}] └── values - ├── columns: column1:3(int!null) column2:6(bool!null) + ├── columns: column1:3(int!null) column2:4(bool!null) ├── cardinality: [0 - 0] - ├── stats: [rows=0, distinct(3,6)=0, null(3,6)=0] + ├── stats: [rows=0, distinct(3,4)=0, null(3,4)=0] ├── key: () - └── fd: ()-->(3,6) + └── fd: ()-->(3,4) # Regression test for #36147. opt @@ -920,7 +920,7 @@ sort └── intersect ├── columns: column1:1(int) ├── left columns: column1:1(int) - ├── right columns: column1:3(int) + ├── right columns: column1:2(int) ├── cardinality: [0 - 1] ├── stats: [rows=1, distinct(1)=1, null(1)=0] ├── key: (1) @@ -932,9 +932,9 @@ sort │ ├── fd: ()-->(1) │ └── (1,) [type=tuple{int}] └── values - ├── columns: column1:3(int) + ├── columns: column1:2(int) ├── cardinality: [1 - 1] - ├── stats: [rows=1, distinct(3)=1, null(3)=1] + ├── stats: [rows=1, distinct(2)=1, null(2)=1] ├── key: () - ├── fd: ()-->(3) + ├── fd: ()-->(2) └── (NULL,) [type=tuple{int}] diff --git a/pkg/sql/opt/optbuilder/testdata/union b/pkg/sql/opt/optbuilder/testdata/union index 39b612773eef..0b0c9272584a 100644 --- a/pkg/sql/opt/optbuilder/testdata/union +++ b/pkg/sql/opt/optbuilder/testdata/union @@ -354,11 +354,11 @@ build SELECT x, pg_typeof(y) FROM (SELECT 1 AS a, 3 AS b UNION ALL SELECT 2 AS a, NULL AS b) AS t(x, y) ---- project - ├── columns: x:5(int!null) pg_typeof:8(string) + ├── columns: x:5(int!null) pg_typeof:7(string) ├── union-all │ ├── columns: a:5(int!null) b:6(int) │ ├── left columns: a:1(int) b:2(int) - │ ├── right columns: a:3(int) b:7(int) + │ ├── right columns: a:3(int) b:4(int) │ ├── project │ │ ├── columns: a:1(int!null) b:2(int!null) │ │ ├── values @@ -367,17 +367,13 @@ project │ │ ├── const: 1 [type=int] │ │ └── const: 3 [type=int] │ └── project - │ ├── columns: b:7(int) a:3(int!null) - │ ├── project - │ │ ├── columns: a:3(int!null) b:4(unknown) - │ │ ├── values - │ │ │ └── tuple [type=tuple] - │ │ └── projections - │ │ ├── const: 2 [type=int] - │ │ └── null [type=unknown] + │ ├── columns: a:3(int!null) b:4(int) + │ ├── values + │ │ └── tuple [type=tuple] │ └── projections + │ ├── const: 2 [type=int] │ └── cast: INT8 [type=int] - │ └── variable: b [type=unknown] + │ └── null [type=unknown] └── projections └── function: pg_typeof [type=string] └── variable: b [type=int] @@ -732,17 +728,62 @@ error (42601): each EXCEPT query must have the same number of columns: 2 vs 1 build SELECT 1 UNION SELECT '3' ---- -error (42804): UNION types int and string cannot be matched +union + ├── columns: "?column?":3(int!null) + ├── left columns: "?column?":1(int) + ├── right columns: "?column?":2(int) + ├── project + │ ├── columns: "?column?":1(int!null) + │ ├── values + │ │ └── tuple [type=tuple] + │ └── projections + │ └── const: 1 [type=int] + └── project + ├── columns: "?column?":2(int!null) + ├── values + │ └── tuple [type=tuple] + └── projections + └── const: 3 [type=int] build SELECT 1 INTERSECT SELECT '3' ---- -error (42804): INTERSECT types int and string cannot be matched +intersect + ├── columns: "?column?":1(int!null) + ├── left columns: "?column?":1(int!null) + ├── right columns: "?column?":2(int) + ├── project + │ ├── columns: "?column?":1(int!null) + │ ├── values + │ │ └── tuple [type=tuple] + │ └── projections + │ └── const: 1 [type=int] + └── project + ├── columns: "?column?":2(int!null) + ├── values + │ └── tuple [type=tuple] + └── projections + └── const: 3 [type=int] build SELECT 1 EXCEPT SELECT '3' ---- -error (42804): EXCEPT types int and string cannot be matched +except + ├── columns: "?column?":1(int!null) + ├── left columns: "?column?":1(int!null) + ├── right columns: "?column?":2(int) + ├── project + │ ├── columns: "?column?":1(int!null) + │ ├── values + │ │ └── tuple [type=tuple] + │ └── projections + │ └── const: 1 [type=int] + └── project + ├── columns: "?column?":2(int!null) + ├── values + │ └── tuple [type=tuple] + └── projections + └── const: 3 [type=int] build SELECT 1 UNION SELECT 3 ORDER BY z @@ -752,7 +793,17 @@ error (42703): column "z" does not exist build SELECT ARRAY[1] UNION ALL SELECT ARRAY['foo'] ---- -error (42804): UNION types int[] and string[] cannot be matched +error (22P02): could not parse "foo" as type int: strconv.ParseInt: parsing "foo": invalid syntax + +build +SELECT ARRAY['foo'] UNION ALL SELECT ARRAY[1] +---- +error (42804): UNION types string[] and int[] cannot be matched + +build +SELECT ARRAY[1] UNION ALL SELECT ARRAY[1.2] +---- +error (42804): UNION types int[] and decimal[] cannot be matched exec-ddl CREATE TABLE t.xy (x STRING NOT NULL, y STRING NOT NULL) @@ -874,7 +925,7 @@ VALUES (3, NULL), (NULL, 'x') INTERSECT VALUES (1, NULL), (2, NULL) intersect ├── columns: column1:1(int) column2:2(string) ├── left columns: column1:1(int) column2:2(string) - ├── right columns: column1:3(int) column2:5(string) + ├── right columns: column1:3(int) column2:4(string) ├── values │ ├── columns: column1:1(int) column2:2(string) │ ├── tuple [type=tuple{int, string}] @@ -885,19 +936,16 @@ intersect │ ├── cast: INT8 [type=int] │ │ └── null [type=unknown] │ └── const: 'x' [type=string] - └── project - ├── columns: column2:5(string) column1:3(int!null) - ├── values - │ ├── columns: column1:3(int!null) column2:4(unknown) - │ ├── tuple [type=tuple{int, unknown}] - │ │ ├── const: 1 [type=int] - │ │ └── null [type=unknown] - │ └── tuple [type=tuple{int, unknown}] - │ ├── const: 2 [type=int] + └── values + ├── columns: column1:3(int!null) column2:4(string) + ├── tuple [type=tuple{int, string}] + │ ├── const: 1 [type=int] + │ └── cast: STRING [type=string] │ └── null [type=unknown] - └── projections + └── tuple [type=tuple{int, string}] + ├── const: 2 [type=int] └── cast: STRING [type=string] - └── variable: column2 [type=unknown] + └── null [type=unknown] build VALUES (NULL, NULL), (NULL, 'x') UNION ALL VALUES (1, NULL), (2, NULL) @@ -905,7 +953,7 @@ VALUES (NULL, NULL), (NULL, 'x') UNION ALL VALUES (1, NULL), (2, NULL) union-all ├── columns: column1:5(int) column2:6(string) ├── left columns: column1:7(int) column2:2(string) - ├── right columns: column1:3(int) column2:8(string) + ├── right columns: column1:3(int) column2:4(string) ├── project │ ├── columns: column1:7(int) column2:2(string) │ ├── values @@ -920,19 +968,16 @@ union-all │ └── projections │ └── cast: INT8 [type=int] │ └── variable: column1 [type=unknown] - └── project - ├── columns: column2:8(string) column1:3(int!null) - ├── values - │ ├── columns: column1:3(int!null) column2:4(unknown) - │ ├── tuple [type=tuple{int, unknown}] - │ │ ├── const: 1 [type=int] - │ │ └── null [type=unknown] - │ └── tuple [type=tuple{int, unknown}] - │ ├── const: 2 [type=int] + └── values + ├── columns: column1:3(int!null) column2:4(string) + ├── tuple [type=tuple{int, string}] + │ ├── const: 1 [type=int] + │ └── cast: STRING [type=string] │ └── null [type=unknown] - └── projections + └── tuple [type=tuple{int, string}] + ├── const: 2 [type=int] └── cast: STRING [type=string] - └── variable: column2 [type=unknown] + └── null [type=unknown] build VALUES (NULL, NULL), (NULL, NULL) UNION ALL VALUES (NULL, NULL), (NULL, NULL) diff --git a/pkg/sql/opt/optbuilder/union.go b/pkg/sql/opt/optbuilder/union.go index 18a5e252b6a6..fbb1f9b0e26a 100644 --- a/pkg/sql/opt/optbuilder/union.go +++ b/pkg/sql/opt/optbuilder/union.go @@ -29,6 +29,14 @@ func (b *Builder) buildUnion( clause *tree.UnionClause, desiredTypes []*types.T, inScope *scope, ) (outScope *scope) { leftScope := b.buildSelect(clause.Left, desiredTypes, inScope) + // Try to propagate types left-to-right, if we didn't already have desired + // types. + if len(desiredTypes) == 0 { + desiredTypes = make([]*types.T, len(leftScope.cols)) + for i := range leftScope.cols { + desiredTypes[i] = leftScope.cols[i].typ + } + } rightScope := b.buildSelect(clause.Right, desiredTypes, inScope) // Remove any hidden columns, as they are not included in the Union.