-
Notifications
You must be signed in to change notification settings - Fork 3.8k
/
union.go
193 lines (173 loc) · 6.59 KB
/
union.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
// Copyright 2018 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package optbuilder
import (
"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/types"
)
// buildUnion builds a set of memo groups that represent the given union
// clause.
//
// See Builder.buildStmt for a description of the remaining input and
// return values.
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.
leftScope.removeHiddenCols()
rightScope.removeHiddenCols()
outScope = inScope.push()
// propagateTypesLeft/propagateTypesRight indicate whether we need to wrap
// the left/right side in a projection to cast some of the columns to the
// correct type.
// For example:
// SELECT NULL UNION SELECT 1
// The type of NULL is unknown, and the type of 1 is int. We need to
// wrap the left side in a project operation with a Cast expression so the
// output column will have the correct type.
propagateTypesLeft, propagateTypesRight := b.checkTypesMatch(
leftScope, rightScope,
true, /* tolerateUnknownLeft */
true, /* tolerateUnknownRight */
clause.Type.String(),
)
if propagateTypesLeft {
leftScope = b.propagateTypes(leftScope /* dst */, rightScope /* src */)
}
if propagateTypesRight {
rightScope = b.propagateTypes(rightScope /* dst */, leftScope /* src */)
}
// For UNION, we have to synthesize new output columns (because they contain
// values from both the left and right relations). This is not necessary for
// INTERSECT or EXCEPT, since these operations are basically filters on the
// left relation.
if clause.Type == tree.UnionOp {
outScope.cols = make([]scopeColumn, 0, len(leftScope.cols))
for i := range leftScope.cols {
c := &leftScope.cols[i]
b.synthesizeColumn(outScope, string(c.name), c.typ, nil, nil /* scalar */)
}
} else {
outScope.appendColumnsFromScope(leftScope)
}
// Create the mapping between the left-side columns, right-side columns and
// new columns (if needed).
leftCols := colsToColList(leftScope.cols)
rightCols := colsToColList(rightScope.cols)
newCols := colsToColList(outScope.cols)
left := leftScope.expr.(memo.RelExpr)
right := rightScope.expr.(memo.RelExpr)
private := memo.SetPrivate{LeftCols: leftCols, RightCols: rightCols, OutCols: newCols}
if clause.All {
switch clause.Type {
case tree.UnionOp:
outScope.expr = b.factory.ConstructUnionAll(left, right, &private)
case tree.IntersectOp:
outScope.expr = b.factory.ConstructIntersectAll(left, right, &private)
case tree.ExceptOp:
outScope.expr = b.factory.ConstructExceptAll(left, right, &private)
}
} else {
switch clause.Type {
case tree.UnionOp:
outScope.expr = b.factory.ConstructUnion(left, right, &private)
case tree.IntersectOp:
outScope.expr = b.factory.ConstructIntersect(left, right, &private)
case tree.ExceptOp:
outScope.expr = b.factory.ConstructExcept(left, right, &private)
}
}
return outScope
}
// checkTypesMatch is used when the columns must match between two scopes (e.g.
// for a UNION). Throws an error if the scopes don't have the same number of
// columns, or when column types don't match 1-1, except:
// - if tolerateUnknownLeft is set and the left column has Unknown type while
// the right has a known type (in this case it returns propagateToLeft=true).
// - if tolerateUnknownRight is set and the right column has Unknown type while
// the right has a known type (in this case it returns propagateToRight=true).
//
// clauseTag is used only in error messages.
//
// TODO(dan): This currently checks whether the types are exactly the same,
// but Postgres is more lenient:
// http://www.postgresql.org/docs/9.5/static/typeconv-union-case.html.
func (b *Builder) checkTypesMatch(
leftScope, rightScope *scope,
tolerateUnknownLeft bool,
tolerateUnknownRight bool,
clauseTag string,
) (propagateToLeft, propagateToRight bool) {
if len(leftScope.cols) != len(rightScope.cols) {
panic(pgerror.Newf(
pgcode.Syntax,
"each %s query must have the same number of columns: %d vs %d",
clauseTag, len(leftScope.cols), len(rightScope.cols),
))
}
for i := range leftScope.cols {
l := &leftScope.cols[i]
r := &rightScope.cols[i]
if l.typ.Equivalent(r.typ) {
continue
}
if l.typ.Family() == types.UnknownFamily && tolerateUnknownLeft {
propagateToLeft = true
continue
}
if r.typ.Family() == types.UnknownFamily && tolerateUnknownRight {
propagateToRight = true
continue
}
panic(pgerror.Newf(
pgcode.DatatypeMismatch,
"%v types %s and %s cannot be matched", clauseTag, l.typ, r.typ,
))
}
return propagateToLeft, propagateToRight
}
// propagateTypes propagates the types of the source columns to the destination
// columns by wrapping the destination in a Project operation. The Project
// operation passes through columns that already have the correct type, and
// creates cast expressions for those that don't.
func (b *Builder) propagateTypes(dst, src *scope) *scope {
expr := dst.expr.(memo.RelExpr)
dstCols := dst.cols
dst = dst.push()
dst.cols = make([]scopeColumn, 0, len(dstCols))
for i := 0; i < len(dstCols); i++ {
dstType := dstCols[i].typ
srcType := src.cols[i].typ
if dstType.Family() == types.UnknownFamily && srcType.Family() != types.UnknownFamily {
// Create a new column which casts the old column to the correct type.
castExpr := b.factory.ConstructCast(b.factory.ConstructVariable(dstCols[i].id), srcType)
b.synthesizeColumn(dst, string(dstCols[i].name), srcType, nil /* expr */, castExpr)
} else {
// The column is already the correct type, so add it as a passthrough
// column.
dst.appendColumn(&dstCols[i])
}
}
dst.expr = b.constructProject(expr, dst.cols)
return dst
}