github.com/dolthub/go-mysql-server@v0.18.0/sql/planbuilder/project.go (about) 1 // Copyright 2023 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package planbuilder 16 17 import ( 18 "strings" 19 20 ast "github.com/dolthub/vitess/go/vt/sqlparser" 21 22 "github.com/dolthub/go-mysql-server/sql" 23 "github.com/dolthub/go-mysql-server/sql/expression" 24 "github.com/dolthub/go-mysql-server/sql/plan" 25 "github.com/dolthub/go-mysql-server/sql/transform" 26 ) 27 28 func (b *Builder) analyzeProjectionList(inScope, outScope *scope, selectExprs ast.SelectExprs) { 29 b.analyzeSelectList(inScope, outScope, selectExprs) 30 } 31 32 func (b *Builder) analyzeSelectList(inScope, outScope *scope, selectExprs ast.SelectExprs) { 33 // todo ideally we would not create new expressions here. 34 // we want to in-place identify aggregations, expand stars. 35 // use inScope to construct projections for projScope 36 37 // interleave tempScope between inScope and parent, namespace for 38 // alias accumulation within SELECT 39 tempScope := inScope.replace() 40 inScope.parent = tempScope 41 42 // need to transfer aggregation state from out -> in 43 var exprs []sql.Expression 44 for _, se := range selectExprs { 45 pe := b.selectExprToExpression(inScope, se) 46 47 // TODO two passes for symbol res and semantic validation 48 var aRef string 49 var subqueryFound bool 50 inScopeAliasRef := transform.InspectExpr(pe, func(e sql.Expression) bool { 51 var id columnId 52 switch e := e.(type) { 53 case *expression.GetField: 54 if e.Table() == "" { 55 id = columnId(e.Id()) 56 aRef = e.Name() 57 } 58 case *expression.Alias: 59 id = columnId(e.Id()) 60 aRef = e.Name() 61 case *plan.Subquery: 62 subqueryFound = true 63 } 64 if aRef != "" { 65 collisionId, ok := tempScope.exprs[strings.ToLower(aRef)] 66 return ok && id == collisionId 67 } 68 return false 69 }) 70 if inScopeAliasRef { 71 err := sql.ErrMisusedAlias.New(aRef) 72 b.handleErr(err) 73 } 74 if subqueryFound { 75 outScope.refsSubquery = true 76 77 } 78 79 switch e := pe.(type) { 80 case *expression.GetField: 81 exprs = append(exprs, e) 82 id, ok := inScope.getExpr(e.String(), true) 83 if !ok { 84 err := sql.ErrColumnNotFound.New(e.String()) 85 b.handleErr(err) 86 } 87 e = e.WithIndex(int(id)).(*expression.GetField) 88 outScope.addColumn(scopeColumn{tableId: inScope.tables[e.Table()], table: e.Table(), db: e.Database(), col: e.Name(), scalar: e, typ: e.Type(), nullable: e.IsNullable(), id: id}) 89 case *expression.Star: 90 tableName := strings.ToLower(e.Table) 91 if tableName == "" && len(inScope.cols) == 1 && inScope.cols[0].col == "" && inScope.cols[0].table == "dual" { 92 err := sql.ErrNoTablesUsed.New() 93 b.handleErr(err) 94 } 95 startLen := len(outScope.cols) 96 for _, c := range inScope.cols { 97 // unqualified columns that are redirected should not be replaced 98 if col, ok := inScope.redirectCol[c.col]; tableName == "" && ok && col != c { 99 continue 100 } 101 if strings.EqualFold(c.table, tableName) || tableName == "" { 102 gf := c.scalarGf() 103 exprs = append(exprs, gf) 104 id, ok := inScope.getExpr(gf.String(), true) 105 if !ok { 106 err := sql.ErrColumnNotFound.New(gf.String()) 107 b.handleErr(err) 108 } 109 c.id = id 110 c.scalar = gf 111 outScope.addColumn(c) 112 } 113 } 114 if tableName != "" && len(outScope.cols) == startLen { 115 err := sql.ErrTableNotFound.New(tableName) 116 b.handleErr(err) 117 } 118 case *expression.Alias: 119 var col scopeColumn 120 if a, ok := e.Child.(*expression.Alias); ok { 121 if _, ok := tempScope.exprs[a.Name()]; ok { 122 // can't ref alias within the same scope 123 err := sql.ErrMisusedAlias.New(e.Name()) 124 b.handleErr(err) 125 } 126 col = scopeColumn{col: e.Name(), scalar: e, typ: e.Type(), nullable: e.IsNullable()} 127 } else if gf, ok := e.Child.(*expression.GetField); ok && gf.Table() == "" { 128 // potential alias only if table is empty 129 if _, ok := tempScope.exprs[gf.Name()]; ok { 130 // can't ref alias within the same scope 131 err := sql.ErrMisusedAlias.New(e.Name()) 132 b.handleErr(err) 133 } 134 id, ok := inScope.getExpr(gf.String(), true) 135 if !ok { 136 err := sql.ErrColumnNotFound.New(gf.String()) 137 b.handleErr(err) 138 } 139 col = scopeColumn{id: id, tableId: gf.TableId(), col: e.Name(), db: gf.Database(), table: gf.Table(), scalar: e, typ: gf.Type(), nullable: gf.IsNullable()} 140 } else if sq, ok := e.Child.(*plan.Subquery); ok { 141 col = scopeColumn{col: e.Name(), scalar: e, typ: sq.Type(), nullable: sq.IsNullable()} 142 } else { 143 col = scopeColumn{col: e.Name(), scalar: e, typ: e.Type(), nullable: e.IsNullable()} 144 } 145 if e.Unreferencable() { 146 outScope.addColumn(col) 147 } else { 148 id := outScope.newColumn(col) 149 col.id = id 150 e = e.WithId(sql.ColumnId(id)).(*expression.Alias) 151 outScope.cols[len(outScope.cols)-1].scalar = e 152 col.scalar = e 153 tempScope.addColumn(col) 154 } 155 exprs = append(exprs, e) 156 default: 157 exprs = append(exprs, pe) 158 col := scopeColumn{col: pe.String(), scalar: pe, typ: pe.Type()} 159 outScope.newColumn(col) 160 } 161 } 162 163 inScope.parent = tempScope.parent 164 } 165 166 // selectExprToExpression binds dependencies in a scalar expression in a SELECT clause. 167 // We differentiate inScope from localScope in cases where we want to differentiate 168 // leading aliases in the same SELECT clause from inner-scope columns of the same name. 169 func (b *Builder) selectExprToExpression(inScope *scope, se ast.SelectExpr) sql.Expression { 170 switch e := se.(type) { 171 case *ast.StarExpr: 172 if e.TableName.IsEmpty() { 173 return expression.NewStar() 174 } 175 return expression.NewQualifiedStar(strings.ToLower(e.TableName.Name.String())) 176 case *ast.AliasedExpr: 177 expr := b.buildScalar(inScope, e.Expr) 178 if !e.As.IsEmpty() { 179 return expression.NewAlias(e.As.String(), expr) 180 } 181 if selectExprNeedsAlias(e, expr) { 182 return expression.NewAlias(e.InputExpression, expr).AsUnreferencable() 183 } 184 return expr 185 default: 186 b.handleErr(sql.ErrUnsupportedSyntax.New(ast.String(e))) 187 } 188 return nil 189 } 190 191 func (b *Builder) buildProjection(inScope, outScope *scope) { 192 projections := make([]sql.Expression, len(outScope.cols)) 193 for i, sc := range outScope.cols { 194 projections[i] = sc.scalar 195 } 196 proj, err := b.f.buildProject(plan.NewProject(projections, inScope.node), outScope.refsSubquery) 197 if err != nil { 198 b.handleErr(err) 199 } 200 outScope.node = proj 201 } 202 203 func selectExprNeedsAlias(e *ast.AliasedExpr, expr sql.Expression) bool { 204 if len(e.InputExpression) == 0 { 205 return false 206 } 207 208 // We want to avoid unnecessary wrapping of aliases, but not at the cost of blowing up parse time. So we examine 209 // the expression tree to see if is likely to need an alias without first serializing the expression being 210 // examined, which can be very expensive in memory. 211 complex := false 212 sql.Inspect(expr, func(expr sql.Expression) bool { 213 switch expr.(type) { 214 case *plan.Subquery, *expression.UnresolvedFunction, *expression.Case, *expression.InTuple, *plan.InSubquery, *expression.HashInTuple: 215 complex = true 216 return false 217 default: 218 return true 219 } 220 }) 221 222 return complex || e.InputExpression != expr.String() 223 }