Skip to content
This repository was archived by the owner on Jan 28, 2021. It is now read-only.

Commit 33c1da4

Browse files
authored
sql/analyzer: refactor and fix bugs in qualify_columns rule (#706)
sql/analyzer: refactor and fix bugs in qualify_columns rule
2 parents 756e3bf + c059a12 commit 33c1da4

File tree

4 files changed

+130
-200
lines changed

4 files changed

+130
-200
lines changed

engine_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,6 +1060,10 @@ var queries = []struct {
10601060
`SELECT t.date_col FROM (SELECT CONVERT('2019-06-06 00:00:00', DATETIME) as date_col) t GROUP BY t.date_col`,
10611061
[]sql.Row{{time.Date(2019, time.June, 6, 0, 0, 0, 0, time.UTC)}},
10621062
},
1063+
{
1064+
`SELECT i AS foo FROM mytable ORDER BY mytable.i`,
1065+
[]sql.Row{{int64(1)}, {int64(2)}, {int64(3)}},
1066+
},
10631067
}
10641068

10651069
func TestQueries(t *testing.T) {

sql/analyzer/resolve_columns.go

Lines changed: 119 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -110,228 +110,156 @@ type column interface {
110110
}
111111

112112
func qualifyColumns(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error) {
113-
span, _ := ctx.Span("qualify_columns")
114-
defer span.Finish()
115-
116-
a.Log("qualify columns")
117-
tables := make(map[string]sql.Node)
118-
tableAliases := make(map[string]string)
119-
colIndex := make(map[string][]string)
120-
121-
indexCols := func(table string, schema sql.Schema) {
122-
for _, col := range schema {
123-
name := strings.ToLower(col.Name)
124-
colIndex[name] = append(colIndex[name], strings.ToLower(table))
125-
}
126-
}
127-
128-
var projects, seenProjects int
129-
plan.Inspect(n, func(n sql.Node) bool {
130-
if _, ok := n.(*plan.Project); ok {
131-
projects++
132-
}
133-
return true
134-
})
135-
136113
return n.TransformUp(func(n sql.Node) (sql.Node, error) {
137-
a.Log("transforming node of type: %T", n)
138-
switch n := n.(type) {
139-
case *plan.TableAlias:
140-
switch t := n.Child.(type) {
141-
case *plan.ResolvedTable, *plan.UnresolvedTable:
142-
name := strings.ToLower(t.(sql.Nameable).Name())
143-
tableAliases[strings.ToLower(n.Name())] = name
144-
default:
145-
tables[strings.ToLower(n.Name())] = n.Child
146-
indexCols(n.Name(), n.Schema())
147-
}
148-
case *plan.ResolvedTable, *plan.SubqueryAlias:
149-
name := strings.ToLower(n.(sql.Nameable).Name())
150-
tables[name] = n
151-
indexCols(name, n.Schema())
152-
}
153-
154114
exp, ok := n.(sql.Expressioner)
155-
if !ok {
115+
if !ok || n.Resolved() {
156116
return n, nil
157117
}
158118

159-
result, err := exp.TransformExpressions(func(e sql.Expression) (sql.Expression, error) {
160-
a.Log("transforming expression of type: %T", e)
161-
switch col := e.(type) {
162-
case *expression.UnresolvedColumn:
163-
// Skip this step for global and session variables
164-
if isGlobalOrSessionColumn(col) {
165-
return col, nil
166-
}
119+
columns := getNodeAvailableColumns(n)
120+
tables := getNodeAvailableTables(n)
167121

168-
col = expression.NewUnresolvedQualifiedColumn(col.Table(), col.Name())
169-
name := strings.ToLower(col.Name())
170-
table := strings.ToLower(col.Table())
171-
if table == "" {
172-
// If a column has no table, it might be an alias
173-
// defined in a child projection, so check that instead
174-
// of incorrectly qualify it.
175-
if isDefinedInChildProject(n, col) {
176-
return col, nil
177-
}
178-
179-
tables := dedupStrings(colIndex[name])
180-
switch len(tables) {
181-
case 0:
182-
// If there are no tables that have any column with the column
183-
// name let's just return it as it is. This may be an alias, so
184-
// we'll wait for the reorder of the projection.
185-
return col, nil
186-
case 1:
187-
col = expression.NewUnresolvedQualifiedColumn(
188-
tables[0],
189-
col.Name(),
190-
)
191-
default:
192-
if _, ok := n.(*plan.GroupBy); ok {
193-
return expression.NewUnresolvedColumn(col.Name()), nil
194-
}
195-
return nil, ErrAmbiguousColumnName.New(col.Name(), strings.Join(tables, ", "))
196-
}
197-
} else {
198-
if real, ok := tableAliases[table]; ok {
199-
col = expression.NewUnresolvedQualifiedColumn(
200-
real,
201-
col.Name(),
202-
)
203-
}
122+
return exp.TransformExpressions(func(e sql.Expression) (sql.Expression, error) {
123+
return qualifyExpression(e, columns, tables)
124+
})
125+
})
126+
}
204127

205-
if _, ok := tables[col.Table()]; !ok {
206-
if len(tables) == 0 {
207-
return nil, sql.ErrTableNotFound.New(col.Table())
208-
}
128+
func qualifyExpression(
129+
e sql.Expression,
130+
columns map[string][]string,
131+
tables map[string]string,
132+
) (sql.Expression, error) {
133+
switch col := e.(type) {
134+
case column:
135+
// Skip this step for global and session variables
136+
if isGlobalOrSessionColumn(col) {
137+
return col, nil
138+
}
209139

210-
similar := similartext.FindFromMap(tables, col.Table())
211-
return nil, sql.ErrTableNotFound.New(col.Table() + similar)
212-
}
140+
name, table := strings.ToLower(col.Name()), strings.ToLower(col.Table())
141+
availableTables := dedupStrings(columns[name])
142+
if table != "" {
143+
table, ok := tables[table]
144+
if !ok {
145+
if len(tables) == 0 {
146+
return nil, sql.ErrTableNotFound.New(col.Table())
213147
}
214148

215-
a.Log("column %q was qualified with table %q", col.Name(), col.Table())
149+
similar := similartext.FindFromMap(tables, col.Table())
150+
return nil, sql.ErrTableNotFound.New(col.Table() + similar)
151+
}
152+
153+
// If the table exists but it's not available for this node it
154+
// means some work is still needed, so just return the column
155+
// and let it be resolved in the next pass.
156+
if !stringContains(availableTables, table) {
216157
return col, nil
217-
case *expression.Star:
218-
if col.Table != "" {
219-
if real, ok := tableAliases[strings.ToLower(col.Table)]; ok {
220-
col = expression.NewQualifiedStar(real)
221-
}
158+
}
222159

223-
if _, ok := tables[strings.ToLower(col.Table)]; !ok {
224-
return nil, sql.ErrTableNotFound.New(col.Table)
225-
}
160+
return expression.NewUnresolvedQualifiedColumn(table, col.Name()), nil
161+
}
226162

227-
return col, nil
228-
}
229-
default:
230-
// If any other kind of expression has a star, just replace it
231-
// with an unqualified star because it cannot be expanded.
232-
return e.TransformUp(func(e sql.Expression) (sql.Expression, error) {
233-
if _, ok := e.(*expression.Star); ok {
234-
return expression.NewStar(), nil
235-
}
236-
return e, nil
237-
})
163+
switch len(availableTables) {
164+
case 0:
165+
// If there are no tables that have any column with the column
166+
// name let's just return it as it is. This may be an alias, so
167+
// we'll wait for the reorder of the projection.
168+
return col, nil
169+
case 1:
170+
return expression.NewUnresolvedQualifiedColumn(
171+
availableTables[0],
172+
col.Name(),
173+
), nil
174+
default:
175+
return nil, ErrAmbiguousColumnName.New(col.Name(), strings.Join(availableTables, ", "))
176+
}
177+
case *expression.Star:
178+
if col.Table != "" {
179+
if real, ok := tables[strings.ToLower(col.Table)]; ok {
180+
col = expression.NewQualifiedStar(real)
238181
}
239182

183+
if _, ok := tables[strings.ToLower(col.Table)]; !ok {
184+
return nil, sql.ErrTableNotFound.New(col.Table)
185+
}
186+
}
187+
return col, nil
188+
default:
189+
// If any other kind of expression has a star, just replace it
190+
// with an unqualified star because it cannot be expanded.
191+
return e.TransformUp(func(e sql.Expression) (sql.Expression, error) {
192+
if _, ok := e.(*expression.Star); ok {
193+
return expression.NewStar(), nil
194+
}
240195
return e, nil
241196
})
197+
}
198+
}
242199

243-
if err != nil {
244-
return nil, err
245-
}
200+
func getNodeAvailableColumns(n sql.Node) map[string][]string {
201+
var columns = make(map[string][]string)
202+
getColumnsInNodes(n.Children(), columns)
203+
return columns
204+
}
246205

247-
// We should ignore the topmost project, because some nodes are
248-
// reordered, such as Sort, and they would not be resolved well.
249-
if n, ok := result.(*plan.Project); ok && projects-seenProjects > 1 {
250-
seenProjects++
251-
252-
// We need to modify the indexed columns to only contain what is
253-
// projected in this project. If the column is not qualified by any
254-
// table, just keep the ones that are currently in the index.
255-
// If it is, then just make those tables available for the column.
256-
// If we don't do this, columns that are not projected will be
257-
// available in this step and may cause false errors or unintended
258-
// results.
259-
var projected = make(map[string][]string)
260-
for _, p := range n.Projections {
261-
var table, col string
262-
switch p := p.(type) {
263-
case column:
264-
table = p.Table()
265-
col = p.Name()
266-
default:
267-
continue
268-
}
206+
func getColumnsInNodes(nodes []sql.Node, columns map[string][]string) {
207+
indexCol := func(table, col string) {
208+
col = strings.ToLower(col)
209+
columns[col] = append(columns[col], strings.ToLower(table))
210+
}
269211

270-
col = strings.ToLower(col)
271-
table = strings.ToLower(table)
272-
if table != "" {
273-
projected[col] = append(projected[col], table)
274-
} else {
275-
projected[col] = append(projected[col], colIndex[col]...)
276-
}
212+
indexExpressions := func(exprs []sql.Expression) {
213+
for _, e := range exprs {
214+
switch e := e.(type) {
215+
case *expression.Alias:
216+
indexCol("", e.Name())
217+
case *expression.GetField:
218+
indexCol(e.Table(), e.Name())
219+
case *expression.UnresolvedColumn:
220+
indexCol(e.Table(), e.Name())
277221
}
222+
}
223+
}
278224

279-
colIndex = make(map[string][]string)
280-
for col, tables := range projected {
281-
colIndex[col] = dedupStrings(tables)
225+
for _, node := range nodes {
226+
switch n := node.(type) {
227+
case *plan.ResolvedTable, *plan.SubqueryAlias:
228+
for _, col := range n.Schema() {
229+
indexCol(col.Source, col.Name)
282230
}
231+
case *plan.Project:
232+
indexExpressions(n.Projections)
233+
case *plan.GroupBy:
234+
indexExpressions(n.Aggregate)
235+
default:
236+
getColumnsInNodes(n.Children(), columns)
283237
}
284-
285-
return result, nil
286-
})
238+
}
287239
}
288240

289-
func isDefinedInChildProject(n sql.Node, col *expression.UnresolvedColumn) bool {
290-
var x sql.Node
291-
for _, child := range n.Children() {
292-
plan.Inspect(child, func(n sql.Node) bool {
241+
func getNodeAvailableTables(n sql.Node) map[string]string {
242+
var tables = make(map[string]string)
243+
for _, c := range n.Children() {
244+
plan.Inspect(c, func(n sql.Node) bool {
293245
switch n := n.(type) {
294-
case *plan.SubqueryAlias:
246+
case *plan.SubqueryAlias, *plan.ResolvedTable:
247+
name := strings.ToLower(n.(sql.Nameable).Name())
248+
tables[name] = name
295249
return false
296-
case *plan.Project, *plan.GroupBy:
297-
if x == nil {
298-
x = n
250+
case *plan.TableAlias:
251+
switch t := n.Child.(type) {
252+
case *plan.ResolvedTable, *plan.UnresolvedTable:
253+
name := strings.ToLower(t.(sql.Nameable).Name())
254+
alias := strings.ToLower(n.Name())
255+
tables[alias] = name
299256
}
300-
return false
301-
default:
302-
return true
303257
}
304-
})
305-
306-
if x != nil {
307-
break
308-
}
309-
}
310258

311-
if x == nil {
312-
return false
313-
}
314-
315-
var found bool
316-
for _, expr := range x.(sql.Expressioner).Expressions() {
317-
switch expr := expr.(type) {
318-
case *expression.Alias:
319-
if strings.ToLower(expr.Name()) == strings.ToLower(col.Name()) {
320-
found = true
321-
}
322-
case column:
323-
if strings.ToLower(expr.Name()) == strings.ToLower(col.Name()) &&
324-
strings.ToLower(expr.Table()) == strings.ToLower(col.Table()) {
325-
found = true
326-
}
327-
}
328-
329-
if found {
330-
break
331-
}
259+
return true
260+
})
332261
}
333-
334-
return found
262+
return tables
335263
}
336264

337265
var errGlobalVariablesNotSupported = errors.NewKind("can't resolve global variable, %s was requested")
@@ -659,6 +587,6 @@ func dedupStrings(in []string) []string {
659587
return result
660588
}
661589

662-
func isGlobalOrSessionColumn(col *expression.UnresolvedColumn) bool {
590+
func isGlobalOrSessionColumn(col column) bool {
663591
return strings.HasPrefix(col.Name(), "@@") || strings.HasPrefix(col.Table(), "@@")
664592
}

sql/analyzer/resolve_columns_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ func TestQualifyColumns(t *testing.T) {
7979
require := require.New(t)
8080
f := getRule("qualify_columns")
8181

82-
table := mem.NewTable("mytable", sql.Schema{{Name: "i", Type: sql.Int32}})
83-
table2 := mem.NewTable("mytable2", sql.Schema{{Name: "i", Type: sql.Int32}})
84-
sessionTable := mem.NewTable("@@session", sql.Schema{{Name: "autocommit", Type: sql.Int64}})
85-
globalTable := mem.NewTable("@@global", sql.Schema{{Name: "max_allowed_packet", Type: sql.Int64}})
82+
table := mem.NewTable("mytable", sql.Schema{{Name: "i", Type: sql.Int32, Source: "mytable"}})
83+
table2 := mem.NewTable("mytable2", sql.Schema{{Name: "i", Type: sql.Int32, Source: "mytable2"}})
84+
sessionTable := mem.NewTable("@@session", sql.Schema{{Name: "autocommit", Type: sql.Int64, Source: "@@session"}})
85+
globalTable := mem.NewTable("@@global", sql.Schema{{Name: "max_allowed_packet", Type: sql.Int64, Source: "@@global"}})
8686

8787
node := plan.NewProject(
8888
[]sql.Expression{

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy