Repository: vitessio/vitess
Pull request #8800 (2021-09-13 12:11:04)

gen4: Simplify handling of ORDER BY 1 and GROUP BY 1
## Description
Refactoring of gen4. Instead of handling `ORDER BY 1` and `GROUP BY 1` in a few different places, we rewrite the query early on, so the rest of the planning process can deal with a simpler query.


## Related Issue(s)
#7280 

## Checklist
- [x] Tests were added or are not required
- [x] Documentation was added or is not required


Patch:
diff --git a/go/vt/vtgate/planbuilder/abstract/queryprojection.go b/go/vt/vtgate/planbuilder/abstract/queryprojection.go
index eda947dccb..7f9e322202 100644
--- a/go/vt/vtgate/planbuilder/abstract/queryprojection.go
+++ b/go/vt/vtgate/planbuilder/abstract/queryprojection.go
@@ -18,7 +18,6 @@ package abstract
 
 import (
 	"encoding/json"
-	"strconv"
 
 	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
 	"vitess.io/vitess/go/vt/sqlparser"
@@ -89,7 +88,7 @@ func CreateQPFromSelect(sel *sqlparser.Select, semTable *semantics.SemTable) (*Q
 	}
 
 	for _, group := range sel.GroupBy {
-		expr, weightStrExpr, err := qp.getSimplifiedExpr(group, semTable)
+		expr, weightStrExpr, err := qp.getSimplifiedExpr(group)
 		if err != nil {
 			return nil, err
 		}
@@ -150,7 +149,7 @@ func CreateQPFromUnion(union *sqlparser.Union, semTable *semantics.SemTable) (*Q
 func (qp *QueryProjection) addOrderBy(orderBy sqlparser.OrderBy, semTable *semantics.SemTable) error {
 	canPushDownSorting := true
 	for _, order := range orderBy {
-		expr, weightStrExpr, err := qp.getSimplifiedExpr(order.Expr, semTable)
+		expr, weightStrExpr, err := qp.getSimplifiedExpr(order.Expr)
 		if err != nil {
 			return err
 		}
@@ -216,28 +215,7 @@ func (qp *QueryProjection) getNonAggrExprNotMatchingGroupByExprs() sqlparser.Exp
 
 // getSimplifiedExpr takes an expression used in ORDER BY or GROUP BY, which can reference both aliased columns and
 // column offsets, and returns an expression that is simpler to evaluate
-func (qp *QueryProjection) getSimplifiedExpr(e sqlparser.Expr, semTable *semantics.SemTable) (expr sqlparser.Expr, weightStrExpr sqlparser.Expr, err error) {
-	// Order by is the column offset to be used from the select expressions
-	// Eg - select id from music order by 1
-	literalExpr, isLiteral := e.(*sqlparser.Literal)
-	if isLiteral && literalExpr.Type == sqlparser.IntVal {
-		num, _ := strconv.Atoi(literalExpr.Val)
-		if num > len(qp.SelectExprs) {
-			return nil, nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "column offset does not exist")
-		}
-		aliasedExpr := qp.SelectExprs[num-1].Col
-		expr = aliasedExpr.Expr
-		if !aliasedExpr.As.IsEmpty() {
-			// the column is aliased, so we'll add an expression ordering by the alias and not the underlying expression
-			expr = &sqlparser.ColName{
-				Name: aliasedExpr.As,
-			}
-			semTable.CopyDependencies(e, expr)
-		}
-
-		return expr, aliasedExpr.Expr, nil
-	}
-
+func (qp *QueryProjection) getSimplifiedExpr(e sqlparser.Expr) (expr sqlparser.Expr, weightStrExpr sqlparser.Expr, err error) {
 	// If the ORDER BY is against a column alias, we need to remember the expression
 	// behind the alias. The weightstring(.) calls needs to be done against that expression and not the alias.
 	// Eg - select music.foo as bar, weightstring(music.foo) from music order by bar
diff --git a/go/vt/vtgate/planbuilder/gen4_planner.go b/go/vt/vtgate/planbuilder/gen4_planner.go
index bcd0b6a9c2..b17ac73a36 100644
--- a/go/vt/vtgate/planbuilder/gen4_planner.go
+++ b/go/vt/vtgate/planbuilder/gen4_planner.go
@@ -98,17 +98,17 @@ func newBuildSelectPlan(selStmt sqlparser.SelectStatement, reservedVars *sqlpars
 	if ks, _ := vschema.DefaultKeyspace(); ks != nil {
 		ksName = ks.Name
 	}
-	semTable, err := semantics.Analyze(selStmt, ksName, vschema, starRewrite)
+	semTable, err := semantics.Analyze(selStmt, ksName, vschema)
 	if err != nil {
 		return nil, err
 	}
 
-	ctx := newPlanningContext(reservedVars, semTable, vschema)
-	err = queryRewrite(ctx, selStmt)
+	err = queryRewrite(semTable, reservedVars, selStmt)
 	if err != nil {
 		return nil, err
 	}
 
+	ctx := newPlanningContext(reservedVars, semTable, vschema)
 	opTree, err := abstract.CreateOperatorFromAST(selStmt, semTable)
 	if err != nil {
 		return nil, err
diff --git a/go/vt/vtgate/planbuilder/rewrite.go b/go/vt/vtgate/planbuilder/rewrite.go
index d70f9d98b8..bf787d8575 100644
--- a/go/vt/vtgate/planbuilder/rewrite.go
+++ b/go/vt/vtgate/planbuilder/rewrite.go
@@ -17,101 +17,21 @@ limitations under the License.
 package planbuilder
 
 import (
-	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
 	"vitess.io/vitess/go/vt/sqlparser"
-	"vitess.io/vitess/go/vt/vterrors"
 	"vitess.io/vitess/go/vt/vtgate/engine"
 	"vitess.io/vitess/go/vt/vtgate/semantics"
 )
 
 type rewriter struct {
-	err          error
 	semTable     *semantics.SemTable
 	reservedVars *sqlparser.ReservedVars
 	isInSubquery int
 }
 
-func starRewrite(statement sqlparser.SelectStatement, semTable *semantics.SemTable) error {
+func queryRewrite(semTable *semantics.SemTable, reservedVars *sqlparser.ReservedVars, statement sqlparser.SelectStatement) error {
 	r := rewriter{
-		semTable: semTable,
-	}
-	sqlparser.Rewrite(statement, r.starRewrite, nil)
-	return r.err
-}
-
-func (r *rewriter) starRewrite(cursor *sqlparser.Cursor) bool {
-	switch node := cursor.Node().(type) {
-	case *sqlparser.Select:
-		tables := r.semTable.GetSelectTables(node)
-		var selExprs sqlparser.SelectExprs
-		for _, selectExpr := range node.SelectExprs {
-			starExpr, isStarExpr := selectExpr.(*sqlparser.StarExpr)
-			if !isStarExpr {
-				selExprs = append(selExprs, selectExpr)
-				continue
-			}
-			starExpanded, colNames, err := expandTableColumns(tables, starExpr)
-			if err != nil {
-				r.err = err
-				return false
-			}
-			if !starExpanded {
-				selExprs = append(selExprs, selectExpr)
-				continue
-			}
-			selExprs = append(selExprs, colNames...)
-		}
-		node.SelectExprs = selExprs
-	}
-	return true
-}
-
-func expandTableColumns(tables []semantics.TableInfo, starExpr *sqlparser.StarExpr) (bool, sqlparser.SelectExprs, error) {
-	unknownTbl := true
-	var colNames sqlparser.SelectExprs
-	starExpanded := true
-	for _, tbl := range tables {
-		if !starExpr.TableName.IsEmpty() && !tbl.Matches(starExpr.TableName) {
-			continue
-		}
-		unknownTbl = false
-		if !tbl.Authoritative() {
-			starExpanded = false
-			break
-		}
-		tblName, err := tbl.Name()
-		if err != nil {
-			return false, nil, err
-		}
-
-		withAlias := len(tables) > 1
-		withQualifier := withAlias || !tbl.GetExpr().As.IsEmpty()
-		for _, col := range tbl.GetColumns() {
-			var colName *sqlparser.ColName
-			var alias sqlparser.ColIdent
-			if withQualifier {
-				colName = sqlparser.NewColNameWithQualifier(col.Name, tblName)
-			} else {
-				colName = sqlparser.NewColName(col.Name)
-			}
-			if withAlias {
-				alias = sqlparser.NewColIdent(col.Name)
-			}
-			colNames = append(colNames, &sqlparser.AliasedExpr{Expr: colName, As: alias})
-		}
-	}
-
-	if unknownTbl {
-		// This will only happen for case when starExpr has qualifier.
-		return false, nil, vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.BadDb, "Unknown table '%s'", sqlparser.String(starExpr.TableName))
-	}
-	return starExpanded, colNames, nil
-}
-
-func queryRewrite(ctx planningContext, statement sqlparser.SelectStatement) error {
-	r := rewriter{
-		semTable:     ctx.semTable,
-		reservedVars: ctx.reservedVars,
+		semTable:     semTable,
+		reservedVars: reservedVars,
 	}
 	sqlparser.Rewrite(statement, r.rewriteDown, r.rewriteUp)
 	return nil
diff --git a/go/vt/vtgate/semantics/analyzer.go b/go/vt/vtgate/semantics/analyzer.go
index 60bcefd4be..4a574d9160 100644
--- a/go/vt/vtgate/semantics/analyzer.go
+++ b/go/vt/vtgate/semantics/analyzer.go
@@ -57,15 +57,8 @@ func newAnalyzer(dbName string, si SchemaInformation) *analyzer {
 	return a
 }
 
-type rewriteFunc = func(statement sqlparser.SelectStatement, semTable *SemTable) error
-
-// NoRewrite is a helper implementation for tests
-var NoRewrite = func(statement sqlparser.SelectStatement, semTable *SemTable) error {
-	return nil
-}
-
 // Analyze analyzes the parsed query.
-func Analyze(statement sqlparser.SelectStatement, currentDb string, si SchemaInformation, rewrite rewriteFunc) (*SemTable, error) {
+func Analyze(statement sqlparser.SelectStatement, currentDb string, si SchemaInformation) (*SemTable, error) {
 	analyzer := newAnalyzer(currentDb, si)
 
 	// Analysis for initial scope
@@ -77,8 +70,8 @@ func Analyze(statement sqlparser.SelectStatement, currentDb string, si SchemaInf
 	// Creation of the semantic table
 	semTable := analyzer.newSemTable(statement)
 
-	// Rewriting operation (expand star)
-	if err = rewrite(statement, semTable); err != nil {
+	// Rewriting operation
+	if err = earlyRewrite(statement, semTable, analyzer.scoper); err != nil {
 		return nil, err
 	}
 	analyzer.hasRewritten = true
diff --git a/go/vt/vtgate/semantics/binder.go b/go/vt/vtgate/semantics/binder.go
index 05b17c554e..16c7c62e45 100644
--- a/go/vt/vtgate/semantics/binder.go
+++ b/go/vt/vtgate/semantics/binder.go
@@ -18,7 +18,6 @@ package semantics
 
 import (
 	"fmt"
-	"strconv"
 
 	"vitess.io/vitess/go/vt/vtgate/engine"
 
@@ -82,15 +81,6 @@ func (b *binder) down(cursor *sqlparser.Cursor) error {
 		}
 		b.subqueryMap[currScope.selectStmt] = append(b.subqueryMap[currScope.selectStmt], sq)
 		b.subqueryRef[node] = sq
-	case *sqlparser.Order:
-		return b.analyzeOrderByGroupByExprForLiteral(node.Expr, "order clause")
-	case sqlparser.GroupBy:
-		for _, grpExpr := range node {
-			err := b.analyzeOrderByGroupByExprForLiteral(grpExpr, "group statement")
-			if err != nil {
-				return err
-			}
-		}
 	case *sqlparser.ColName:
 		baseTableTS, ts, typ, err := b.resolveColumn(node, b.scoper.currentScope())
 		if err != nil {
@@ -123,32 +113,6 @@ func (b *binder) down(cursor *sqlparser.Cursor) error {
 	return nil
 }
 
-func (b *binder) analyzeOrderByGroupByExprForLiteral(input sqlparser.Expr, caller string) error {
-	l, ok := input.(*sqlparser.Literal)
-	if !ok {
-		return nil
-	}
-	if l.Type != sqlparser.IntVal {
-		return nil
-	}
-	currScope := b.scoper.currentScope()
-	num, err := strconv.Atoi(l.Val)
-	if err != nil {
-		return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "error parsing column number: %s", l.Val)
-	}
-	if num < 1 || num > len(currScope.selectStmt.SelectExprs) {
-		return vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.BadFieldError, "Unknown column '%d' in '%s'", num, caller)
-	}
-
-	expr, ok := currScope.selectStmt.SelectExprs[num-1].(*sqlparser.AliasedExpr)
-	if !ok {
-		return nil
-	}
-
-	b.exprRecursiveDeps[input] = b.exprRecursiveDeps.Dependencies(expr.Expr)
-	return nil
-}
-
 func (b *binder) resolveColumn(colName *sqlparser.ColName, current *scope) (TableSet, TableSet, *querypb.Type, error) {
 	if colName.Qualifier.IsEmpty() {
 		return b.resolveUnQualifiedColumn(current, colName)
diff --git a/go/vt/vtgate/semantics/early_rewriter.go b/go/vt/vtgate/semantics/early_rewriter.go
new file mode 100644
index 0000000000..79e1cff4df
--- /dev/null
+++ b/go/vt/vtgate/semantics/early_rewriter.go
@@ -0,0 +1,152 @@
+/*
+Copyright 2021 The Vitess Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package semantics
+
+import (
+	"strconv"
+
+	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
+	"vitess.io/vitess/go/vt/sqlparser"
+	"vitess.io/vitess/go/vt/vterrors"
+)
+
+type earlyRewriter struct {
+	err      error
+	semTable *SemTable
+	scoper   *scoper
+	clause   string
+}
+
+// earlyRewrite rewrites the query before the binder has had a chance to work on the query
+// it introduces new expressions that the binder will later need to bind correctly
+func earlyRewrite(statement sqlparser.SelectStatement, semTable *SemTable, scoper *scoper) error {
+	r := earlyRewriter{
+		semTable: semTable,
+		scoper:   scoper,
+	}
+	sqlparser.Rewrite(statement, r.rewrite, nil)
+	return r.err
+}
+
+func (r *earlyRewriter) rewrite(cursor *sqlparser.Cursor) bool {
+	switch node := cursor.Node().(type) {
+	case *sqlparser.Select:
+		tables := r.semTable.GetSelectTables(node)
+		var selExprs sqlparser.SelectExprs
+		for _, selectExpr := range node.SelectExprs {
+			starExpr, isStarExpr := selectExpr.(*sqlparser.StarExpr)
+			if !isStarExpr {
+				selExprs = append(selExprs, selectExpr)
+				continue
+			}
+			starExpanded, colNames, err := expandTableColumns(tables, starExpr)
+			if err != nil {
+				r.err = err
+				return false
+			}
+			if !starExpanded {
+				selExprs = append(selExprs, selectExpr)
+				continue
+			}
+			selExprs = append(selExprs, colNames...)
+		}
+		node.SelectExprs = selExprs
+	case *sqlparser.Order:
+		r.clause = "order clause"
+	case sqlparser.GroupBy:
+		r.clause = "group statement"
+
+	case *sqlparser.Literal:
+		currScope, found := r.scoper.specialExprScopes[node]
+		if !found {
+			break
+		}
+		num, err := strconv.Atoi(node.Val)
+		if err != nil {
+			r.err = vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "error parsing column number: %s", node.Val)
+			break
+		}
+		if num < 1 || num > len(currScope.selectStmt.SelectExprs) {
+			r.err = vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.BadFieldError, "Unknown column '%d' in '%s'", num, r.clause)
+			break
+		}
+
+		for i := 0; i < num; i++ {
+			expr := currScope.selectStmt.SelectExprs[i]
+			_, ok := expr.(*sqlparser.AliasedExpr)
+			if !ok {
+				r.err = vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "cannot use column offsets in %s when using `%s`", r.clause, sqlparser.String(expr))
+				return true
+			}
+		}
+
+		aliasedExpr, ok := currScope.selectStmt.SelectExprs[num-1].(*sqlparser.AliasedExpr)
+		if !ok {
+			r.err = vterrors.Errorf(vtrpcpb.Code_INTERNAL, "don't know how to handle %s", sqlparser.String(node))
+			break
+		}
+
+		if !aliasedExpr.As.IsEmpty() {
+			cursor.Replace(sqlparser.NewColName(aliasedExpr.As.String()))
+		} else {
+			cursor.Replace(aliasedExpr.Expr)
+		}
+	}
+	return true
+}
+
+func expandTableColumns(tables []TableInfo, starExpr *sqlparser.StarExpr) (bool, sqlparser.SelectExprs, error) {
+	unknownTbl := true
+	var colNames sqlparser.SelectExprs
+	starExpanded := true
+	for _, tbl := range tables {
+		if !starExpr.TableName.IsEmpty() && !tbl.Matches(starExpr.TableName) {
+			continue
+		}
+		unknownTbl = false
+		if !tbl.Authoritative() {
+			starExpanded = false
+			break
+		}
+		tblName, err := tbl.Name()
+		if err != nil {
+			return false, nil, err
+		}
+
+		withAlias := len(tables) > 1
+		withQualifier := withAlias || !tbl.GetExpr().As.IsEmpty()
+		for _, col := range tbl.GetColumns() {
+			var colName *sqlparser.ColName
+			var alias sqlparser.ColIdent
+			if withQualifier {
+				colName = sqlparser.NewColNameWithQualifier(col.Name, tblName)
+			} else {
+				colName = sqlparser.NewColName(col.Name)
+			}
+			if withAlias {
+				alias = sqlparser.NewColIdent(col.Name)
+			}
+			colNames = append(colNames, &sqlparser.AliasedExpr{Expr: colName, As: alias})
+		}
+	}
+
+	if unknownTbl {
+		// This will only happen for case when starExpr has qualifier.
+		return false, nil, vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.BadDb, "Unknown table '%s'", sqlparser.String(starExpr.TableName))
+	}
+	return starExpanded, colNames, nil
+}
diff --git a/go/vt/vtgate/semantics/scoper.go b/go/vt/vtgate/semantics/scoper.go
index 6c737e4258..10afefc4e3 100644
--- a/go/vt/vtgate/semantics/scoper.go
+++ b/go/vt/vtgate/semantics/scoper.go
@@ -29,6 +29,9 @@ type scoper struct {
 	wScope       map[*sqlparser.Select]*scope
 	sqlNodeScope map[scopeKey]*scope
 	scopes       []*scope
+
+	// These scopes are only used for rewriting ORDER BY 1 and GROUP BY 1
+	specialExprScopes map[*sqlparser.Literal]*scope
 }
 
 type scopeKey struct {
@@ -47,9 +50,10 @@ const (
 
 func newScoper() *scoper {
 	return &scoper{
-		rScope:       map[*sqlparser.Select]*scope{},
-		wScope:       map[*sqlparser.Select]*scope{},
-		sqlNodeScope: map[scopeKey]*scope{},
+		rScope:            map[*sqlparser.Select]*scope{},
+		wScope:            map[*sqlparser.Select]*scope{},
+		sqlNodeScope:      map[scopeKey]*scope{},
+		specialExprScopes: map[*sqlparser.Literal]*scope{},
 	}
 }
 
@@ -90,8 +94,20 @@ func (s *scoper) down(cursor *sqlparser.Cursor) {
 		wScope.tables = append(wScope.tables, createVTableInfoForExpressions(node))
 	case sqlparser.OrderBy:
 		s.changeScopeForNode(cursor, scopeKey{node: cursor.Parent(), typ: orderBy})
+		for _, order := range node {
+			lit := keepIntLiteral(order.Expr)
+			if lit != nil {
+				s.specialExprScopes[lit] = s.currentScope()
+			}
+		}
 	case sqlparser.GroupBy:
 		s.changeScopeForNode(cursor, scopeKey{node: cursor.Parent(), typ: groupBy})
+		for _, expr := range node {
+			lit := keepIntLiteral(expr)
+			if lit != nil {
+				s.specialExprScopes[lit] = s.currentScope()
+			}
+		}
 	case *sqlparser.Where:
 		if node.Type != sqlparser.HavingClause {
 			break
@@ -100,6 +116,17 @@ func (s *scoper) down(cursor *sqlparser.Cursor) {
 	}
 }
 
+func keepIntLiteral(e sqlparser.Expr) *sqlparser.Literal {
+	l, ok := e.(*sqlparser.Literal)
+	if !ok {
+		return nil
+	}
+	if l.Type != sqlparser.IntVal {
+		return nil
+	}
+	return l
+}
+
 func (s *scoper) up(cursor *sqlparser.Cursor) error {
 	switch node := cursor.Node().(type) {
 	case *sqlparser.Select, sqlparser.OrderBy, sqlparser.GroupBy:


Test patch:
diff --git a/go/vt/vtgate/planbuilder/abstract/operator_test.go b/go/vt/vtgate/planbuilder/abstract/operator_test.go
index 579cbbdae8..fbfc32f701 100644
--- a/go/vt/vtgate/planbuilder/abstract/operator_test.go
+++ b/go/vt/vtgate/planbuilder/abstract/operator_test.go
@@ -334,7 +334,7 @@ JoinPredicates:
 			tree, err := sqlparser.Parse(sql)
 			require.NoError(t, err)
 			stmt := tree.(sqlparser.SelectStatement)
-			semTable, err := semantics.Analyze(stmt, "", si, semantics.NoRewrite)
+			semTable, err := semantics.Analyze(stmt, "", si)
 			require.NoError(t, err)
 			optree, err := CreateOperatorFromAST(stmt, semTable)
 			require.NoError(t, err)
diff --git a/go/vt/vtgate/planbuilder/abstract/queryprojection_test.go b/go/vt/vtgate/planbuilder/abstract/queryprojection_test.go
index 1e3dee330c..96b018c91d 100644
--- a/go/vt/vtgate/planbuilder/abstract/queryprojection_test.go
+++ b/go/vt/vtgate/planbuilder/abstract/queryprojection_test.go
@@ -93,7 +93,7 @@ func TestQP(t *testing.T) {
 			require.NoError(t, err)
 
 			sel := stmt.(*sqlparser.Select)
-			_, err = semantics.Analyze(sel, "", &semantics.FakeSI{}, semantics.NoRewrite)
+			_, err = semantics.Analyze(sel, "", &semantics.FakeSI{})
 			require.NoError(t, err)
 
 			qp, err := CreateQPFromSelect(sel, semantics.NewSemTable())
@@ -200,7 +200,7 @@ func TestQPSimplifiedExpr(t *testing.T) {
 			ast, err := sqlparser.Parse(tc.query)
 			require.NoError(t, err)
 			sel := ast.(*sqlparser.Select)
-			_, err = semantics.Analyze(sel, "", &semantics.FakeSI{}, semantics.NoRewrite)
+			_, err = semantics.Analyze(sel, "", &semantics.FakeSI{})
 			require.NoError(t, err)
 
 			qp, err := CreateQPFromSelect(sel, semantics.NewSemTable())
diff --git a/go/vt/vtgate/planbuilder/plan_test.go b/go/vt/vtgate/planbuilder/plan_test.go
index 75ba90b938..ffc014d264 100644
--- a/go/vt/vtgate/planbuilder/plan_test.go
+++ b/go/vt/vtgate/planbuilder/plan_test.go
@@ -826,7 +826,7 @@ func exerciseAnalyzer(query, database string, s semantics.SchemaInformation) {
 		return
 	}
 
-	_, _ = semantics.Analyze(sel, database, s, starRewrite)
+	_, _ = semantics.Analyze(sel, database, s)
 }
 
 func BenchmarkSelectVsDML(b *testing.B) {
diff --git a/go/vt/vtgate/planbuilder/rewrite_test.go b/go/vt/vtgate/planbuilder/rewrite_test.go
index 888447a8b8..40722b5b8d 100644
--- a/go/vt/vtgate/planbuilder/rewrite_test.go
+++ b/go/vt/vtgate/planbuilder/rewrite_test.go
@@ -22,163 +22,10 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
-	"vitess.io/vitess/go/sqltypes"
 	"vitess.io/vitess/go/vt/sqlparser"
 	"vitess.io/vitess/go/vt/vtgate/semantics"
-	"vitess.io/vitess/go/vt/vtgate/vindexes"
 )
 
-func TestExpandStar(t *testing.T) {
-	schemaInfo := &semantics.FakeSI{
-		Tables: map[string]*vindexes.Table{
-			"t1": {
-				Name: sqlparser.NewTableIdent("t1"),
-				Columns: []vindexes.Column{{
-					Name: sqlparser.NewColIdent("a"),
-					Type: sqltypes.VarChar,
-				}, {
-					Name: sqlparser.NewColIdent("b"),
-					Type: sqltypes.VarChar,
-				}, {
-					Name: sqlparser.NewColIdent("c"),
-					Type: sqltypes.VarChar,
-				}},
-				ColumnListAuthoritative: true,
-			},
-			"t2": {
-				Name: sqlparser.NewTableIdent("t2"),
-				Columns: []vindexes.Column{{
-					Name: sqlparser.NewColIdent("c1"),
-					Type: sqltypes.VarChar,
-				}, {
-					Name: sqlparser.NewColIdent("c2"),
-					Type: sqltypes.VarChar,
-				}},
-				ColumnListAuthoritative: true,
-			},
-			"t3": { // non authoritative table.
-				Name: sqlparser.NewTableIdent("t3"),
-				Columns: []vindexes.Column{{
-					Name: sqlparser.NewColIdent("col"),
-					Type: sqltypes.VarChar,
-				}},
-				ColumnListAuthoritative: false,
-			},
-		},
-	}
-	cDB := "db"
-	tcases := []struct {
-		sql    string
-		expSQL string
-		expErr string
-	}{{
-		sql:    "select * from t1",
-		expSQL: "select a, b, c from t1",
-	}, {
-		sql:    "select t1.* from t1",
-		expSQL: "select a, b, c from t1",
-	}, {
-		sql:    "select *, 42, t1.* from t1",
-		expSQL: "select a, b, c, 42, a, b, c from t1",
-	}, {
-		sql:    "select 42, t1.* from t1",
-		expSQL: "select 42, a, b, c from t1",
-	}, {
-		sql:    "select * from t1, t2",
-		expSQL: "select t1.a as a, t1.b as b, t1.c as c, t2.c1 as c1, t2.c2 as c2 from t1, t2",
-	}, {
-		sql:    "select t1.* from t1, t2",
-		expSQL: "select t1.a as a, t1.b as b, t1.c as c from t1, t2",
-	}, {
-		sql:    "select *, t1.* from t1, t2",
-		expSQL: "select t1.a as a, t1.b as b, t1.c as c, t2.c1 as c1, t2.c2 as c2, t1.a as a, t1.b as b, t1.c as c from t1, t2",
-	}, { // aliased table
-		sql:    "select * from t1 a, t2 b",
-		expSQL: "select a.a as a, a.b as b, a.c as c, b.c1 as c1, b.c2 as c2 from t1 as a, t2 as b",
-	}, { // t3 is non-authoritative table
-		sql:    "select * from t3",
-		expSQL: "select * from t3",
-	}, { // t3 is non-authoritative table
-		sql:    "select * from t1, t2, t3",
-		expSQL: "select * from t1, t2, t3",
-	}, { // t3 is non-authoritative table
-		sql:    "select t1.*, t2.*, t3.* from t1, t2, t3",
-		expSQL: "select t1.a as a, t1.b as b, t1.c as c, t2.c1 as c1, t2.c2 as c2, t3.* from t1, t2, t3",
-	}, {
-		sql:    "select foo.* from t1, t2",
-		expErr: "Unknown table 'foo'",
-	}}
-	for _, tcase := range tcases {
-		t.Run(tcase.sql, func(t *testing.T) {
-			ast, err := sqlparser.Parse(tcase.sql)
-			require.NoError(t, err)
-			selectStatement, isSelectStatement := ast.(*sqlparser.Select)
-			require.True(t, isSelectStatement, "analyzer expects a select statement")
-			_, err = semantics.Analyze(selectStatement, cDB, schemaInfo, starRewrite)
-			if tcase.expErr == "" {
-				require.NoError(t, err)
-				assert.Equal(t, tcase.expSQL, sqlparser.String(selectStatement))
-			} else {
-				require.EqualError(t, err, tcase.expErr)
-			}
-		})
-	}
-}
-
-func TestSemTableDependenciesAfterExpandStar(t *testing.T) {
-	schemaInfo := &semantics.FakeSI{Tables: map[string]*vindexes.Table{
-		"t1": {
-			Name: sqlparser.NewTableIdent("t1"),
-			Columns: []vindexes.Column{{
-				Name: sqlparser.NewColIdent("a"),
-				Type: sqltypes.VarChar,
-			}},
-			ColumnListAuthoritative: true,
-		}}}
-	tcases := []struct {
-		sql         string
-		expSQL      string
-		sameTbl     int
-		otherTbl    int
-		expandedCol int
-	}{{
-		sql:      "select a, * from t1",
-		expSQL:   "select a, a from t1",
-		otherTbl: -1, sameTbl: 0, expandedCol: 1,
-	}, {
-		sql:      "select t2.a, t1.a, t1.* from t1, t2",
-		expSQL:   "select t2.a, t1.a, t1.a as a from t1, t2",
-		otherTbl: 0, sameTbl: 1, expandedCol: 2,
-	}, {
-		sql:      "select t2.a, t.a, t.* from t1 t, t2",
-		expSQL:   "select t2.a, t.a, t.a as a from t1 as t, t2",
-		otherTbl: 0, sameTbl: 1, expandedCol: 2,
-	}}
-	for _, tcase := range tcases {
-		t.Run(tcase.sql, func(t *testing.T) {
-			ast, err := sqlparser.Parse(tcase.sql)
-			require.NoError(t, err)
-			selectStatement, isSelectStatement := ast.(*sqlparser.Select)
-			require.True(t, isSelectStatement, "analyzer expects a select statement")
-			semTable, err := semantics.Analyze(selectStatement, "", schemaInfo, starRewrite)
-			require.NoError(t, err)
-			assert.Equal(t, tcase.expSQL, sqlparser.String(selectStatement))
-			if tcase.otherTbl != -1 {
-				assert.NotEqual(t,
-					semTable.BaseTableDependencies(selectStatement.SelectExprs[tcase.otherTbl].(*sqlparser.AliasedExpr).Expr),
-					semTable.BaseTableDependencies(selectStatement.SelectExprs[tcase.expandedCol].(*sqlparser.AliasedExpr).Expr),
-				)
-			}
-			if tcase.sameTbl != -1 {
-				assert.Equal(t,
-					semTable.BaseTableDependencies(selectStatement.SelectExprs[tcase.sameTbl].(*sqlparser.AliasedExpr).Expr),
-					semTable.BaseTableDependencies(selectStatement.SelectExprs[tcase.expandedCol].(*sqlparser.AliasedExpr).Expr),
-				)
-			}
-		})
-	}
-}
-
 func TestSubqueryRewrite(t *testing.T) {
 	tcases := []struct {
 		input  string
@@ -227,10 +74,9 @@ func TestSubqueryRewrite(t *testing.T) {
 			reservedVars := sqlparser.NewReservedVars("vtg", vars)
 			selectStatement, isSelectStatement := ast.(*sqlparser.Select)
 			require.True(t, isSelectStatement, "analyzer expects a select statement")
-			semTable, err := semantics.Analyze(selectStatement, "", &semantics.FakeSI{}, semantics.NoRewrite)
+			semTable, err := semantics.Analyze(selectStatement, "", &semantics.FakeSI{})
 			require.NoError(t, err)
-			ctx := newPlanningContext(reservedVars, semTable, nil)
-			err = queryRewrite(ctx, selectStatement)
+			err = queryRewrite(semTable, reservedVars, selectStatement)
 			require.NoError(t, err)
 			assert.Equal(t, tcase.output, sqlparser.String(selectStatement))
 		})
@@ -285,11 +131,11 @@ func TestHavingRewrite(t *testing.T) {
 	}}
 	for _, tcase := range tcases {
 		t.Run(tcase.input, func(t *testing.T) {
-			ctx, sel := prepTest(t, tcase.input)
-			err := queryRewrite(ctx, sel)
+			semTable, reservedVars, sel := prepTest(t, tcase.input)
+			err := queryRewrite(semTable, reservedVars, sel)
 			require.NoError(t, err)
 			assert.Equal(t, tcase.output, sqlparser.String(sel))
-			squeries, found := ctx.semTable.SubqueryMap[sel]
+			squeries, found := semTable.SubqueryMap[sel]
 			if len(tcase.sqs) > 0 {
 				assert.True(t, found, "no subquery found in the query")
 				assert.Equal(t, len(tcase.sqs), len(squeries), "number of subqueries not matched")
@@ -301,7 +147,7 @@ func TestHavingRewrite(t *testing.T) {
 	}
 }
 
-func prepTest(t *testing.T, sql string) (planningContext, *sqlparser.Select) {
+func prepTest(t *testing.T, sql string) (*semantics.SemTable, *sqlparser.ReservedVars, *sqlparser.Select) {
 	ast, vars, err := sqlparser.Parse2(sql)
 	require.NoError(t, err)
 
@@ -309,8 +155,8 @@ func prepTest(t *testing.T, sql string) (planningContext, *sqlparser.Select) {
 	require.True(t, isSelectStatement, "analyzer expects a select statement")
 
 	reservedVars := sqlparser.NewReservedVars("vtg", vars)
-	semTable, err := semantics.Analyze(sel, "", &semantics.FakeSI{}, semantics.NoRewrite)
+	semTable, err := semantics.Analyze(sel, "", &semantics.FakeSI{})
 	require.NoError(t, err)
 
-	return newPlanningContext(reservedVars, semTable, nil), sel
+	return semTable, reservedVars, sel
 }
diff --git a/go/vt/vtgate/planbuilder/testdata/postprocess_cases.txt b/go/vt/vtgate/planbuilder/testdata/postprocess_cases.txt
index 0689dd30ab..601c21c8bf 100644
--- a/go/vt/vtgate/planbuilder/testdata/postprocess_cases.txt
+++ b/go/vt/vtgate/planbuilder/testdata/postprocess_cases.txt
@@ -297,7 +297,25 @@ Gen4 plan same as above
     "Vindex": "user_index"
   }
 }
-Gen4 plan same as above
+{
+  "QueryType": "SELECT",
+  "Original": "select col from user where id = 1 order by 1",
+  "Instructions": {
+    "OperatorType": "Route",
+    "Variant": "SelectEqualUnique",
+    "Keyspace": {
+      "Name": "user",
+      "Sharded": true
+    },
+    "FieldQuery": "select col from `user` where 1 != 1",
+    "Query": "select col from `user` where id = 1 order by col asc",
+    "Table": "`user`",
+    "Values": [
+      1
+    ],
+    "Vindex": "user_index"
+  }
+}
 
 # ORDER BY on scatter
 "select col from user order by col"
diff --git a/go/vt/vtgate/semantics/analyzer_test.go b/go/vt/vtgate/semantics/analyzer_test.go
index ff45e77e03..4fc3ec945c 100644
--- a/go/vt/vtgate/semantics/analyzer_test.go
+++ b/go/vt/vtgate/semantics/analyzer_test.go
@@ -127,7 +127,7 @@ func TestBindingSingleTableNegative(t *testing.T) {
 		t.Run(query, func(t *testing.T) {
 			parse, err := sqlparser.Parse(query)
 			require.NoError(t, err)
-			_, err = Analyze(parse.(sqlparser.SelectStatement), "d", &FakeSI{}, NoRewrite)
+			_, err = Analyze(parse.(sqlparser.SelectStatement), "d", &FakeSI{})
 			require.Error(t, err)
 		})
 	}
@@ -323,7 +323,7 @@ func TestBindingSingleAliasedTable(t *testing.T) {
 					Tables: map[string]*vindexes.Table{
 						"t": {Name: sqlparser.NewTableIdent("t")},
 					},
-				}, NoRewrite)
+				})
 				require.Error(t, err)
 			})
 		}
@@ -362,7 +362,7 @@ func TestUnionColumns(t *testing.T) {
 			parse, err := sqlparser.Parse(query)
 			require.NoError(t, err)
 
-			_, err = Analyze(parse.(sqlparser.SelectStatement), "dbName", fakeSchemaInfo(), NoRewrite)
+			_, err = Analyze(parse.(sqlparser.SelectStatement), "dbName", fakeSchemaInfo())
 
 			require.Error(t, err)
 		})
@@ -485,7 +485,7 @@ func TestBindingMultiTable(t *testing.T) {
 						"tabl": {Name: sqlparser.NewTableIdent("tabl")},
 						"foo":  {Name: sqlparser.NewTableIdent("foo")},
 					},
-				}, NoRewrite)
+				})
 				require.Error(t, err)
 			})
 		}
@@ -513,7 +513,7 @@ func TestNotUniqueTableName(t *testing.T) {
 	for _, query := range queries {
 		t.Run(query, func(t *testing.T) {
 			parse, _ := sqlparser.Parse(query)
-			_, err := Analyze(parse.(sqlparser.SelectStatement), "test", &FakeSI{}, NoRewrite)
+			_, err := Analyze(parse.(sqlparser.SelectStatement), "test", &FakeSI{})
 			require.Error(t, err)
 			require.Contains(t, err.Error(), "Not unique table/alias")
 		})
@@ -528,7 +528,7 @@ func TestMissingTable(t *testing.T) {
 	for _, query := range queries {
 		t.Run(query, func(t *testing.T) {
 			parse, _ := sqlparser.Parse(query)
-			_, err := Analyze(parse.(sqlparser.SelectStatement), "", &FakeSI{}, NoRewrite)
+			_, err := Analyze(parse.(sqlparser.SelectStatement), "", &FakeSI{})
 			require.Error(t, err)
 			require.Contains(t, err.Error(), "symbol t.col not found")
 		})
@@ -628,7 +628,7 @@ func TestUnknownColumnMap2(t *testing.T) {
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			si := &FakeSI{Tables: test.schema}
-			tbl, err := Analyze(parse.(sqlparser.SelectStatement), "", si, NoRewrite)
+			tbl, err := Analyze(parse.(sqlparser.SelectStatement), "", si)
 			require.NoError(t, err)
 
 			if test.err {
@@ -667,7 +667,7 @@ func TestUnknownPredicate(t *testing.T) {
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			si := &FakeSI{Tables: test.schema}
-			_, err := Analyze(parse.(sqlparser.SelectStatement), "", si, NoRewrite)
+			_, err := Analyze(parse.(sqlparser.SelectStatement), "", si)
 			if test.err {
 				require.Error(t, err)
 			} else {
@@ -695,7 +695,7 @@ func TestScoping(t *testing.T) {
 				Tables: map[string]*vindexes.Table{
 					"t": {Name: sqlparser.NewTableIdent("t")},
 				},
-			}, NoRewrite)
+			})
 			if query.errorMessage == "" {
 				require.NoError(t, err)
 			} else {
@@ -759,7 +759,7 @@ func TestScopingWDerivedTables(t *testing.T) {
 				Tables: map[string]*vindexes.Table{
 					"t": {Name: sqlparser.NewTableIdent("t")},
 				},
-			}, NoRewrite)
+			})
 			if query.errorMessage != "" {
 				require.EqualError(t, err, query.errorMessage)
 			} else {
@@ -801,7 +801,7 @@ func TestScopingWVindexTables(t *testing.T) {
 				VindexTables: map[string]vindexes.Vindex{
 					"user_index": hash,
 				},
-			}, NoRewrite)
+			})
 			if query.errorMessage != "" {
 				require.EqualError(t, err, query.errorMessage)
 			} else {
@@ -819,7 +819,7 @@ func parseAndAnalyze(t *testing.T, query, dbName string) (sqlparser.Statement, *
 	parse, err := sqlparser.Parse(query)
 	require.NoError(t, err)
 
-	semTable, err := Analyze(parse.(sqlparser.SelectStatement), dbName, fakeSchemaInfo(), NoRewrite)
+	semTable, err := Analyze(parse.(sqlparser.SelectStatement), dbName, fakeSchemaInfo())
 	require.NoError(t, err)
 	return parse, semTable
 }
diff --git a/go/vt/vtgate/semantics/early_rewriter_test.go b/go/vt/vtgate/semantics/early_rewriter_test.go
new file mode 100644
index 0000000000..7865b49d4b
--- /dev/null
+++ b/go/vt/vtgate/semantics/early_rewriter_test.go
@@ -0,0 +1,232 @@
+/*
+Copyright 2021 The Vitess Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package semantics
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"vitess.io/vitess/go/sqltypes"
+	"vitess.io/vitess/go/vt/sqlparser"
+	"vitess.io/vitess/go/vt/vtgate/vindexes"
+)
+
+func TestExpandStar(t *testing.T) {
+	schemaInfo := &FakeSI{
+		Tables: map[string]*vindexes.Table{
+			"t1": {
+				Name: sqlparser.NewTableIdent("t1"),
+				Columns: []vindexes.Column{{
+					Name: sqlparser.NewColIdent("a"),
+					Type: sqltypes.VarChar,
+				}, {
+					Name: sqlparser.NewColIdent("b"),
+					Type: sqltypes.VarChar,
+				}, {
+					Name: sqlparser.NewColIdent("c"),
+					Type: sqltypes.VarChar,
+				}},
+				ColumnListAuthoritative: true,
+			},
+			"t2": {
+				Name: sqlparser.NewTableIdent("t2"),
+				Columns: []vindexes.Column{{
+					Name: sqlparser.NewColIdent("c1"),
+					Type: sqltypes.VarChar,
+				}, {
+					Name: sqlparser.NewColIdent("c2"),
+					Type: sqltypes.VarChar,
+				}},
+				ColumnListAuthoritative: true,
+			},
+			"t3": { // non authoritative table.
+				Name: sqlparser.NewTableIdent("t3"),
+				Columns: []vindexes.Column{{
+					Name: sqlparser.NewColIdent("col"),
+					Type: sqltypes.VarChar,
+				}},
+				ColumnListAuthoritative: false,
+			},
+		},
+	}
+	cDB := "db"
+	tcases := []struct {
+		sql    string
+		expSQL string
+		expErr string
+	}{{
+		sql:    "select * from t1",
+		expSQL: "select a, b, c from t1",
+	}, {
+		sql:    "select t1.* from t1",
+		expSQL: "select a, b, c from t1",
+	}, {
+		sql:    "select *, 42, t1.* from t1",
+		expSQL: "select a, b, c, 42, a, b, c from t1",
+	}, {
+		sql:    "select 42, t1.* from t1",
+		expSQL: "select 42, a, b, c from t1",
+	}, {
+		sql:    "select * from t1, t2",
+		expSQL: "select t1.a as a, t1.b as b, t1.c as c, t2.c1 as c1, t2.c2 as c2 from t1, t2",
+	}, {
+		sql:    "select t1.* from t1, t2",
+		expSQL: "select t1.a as a, t1.b as b, t1.c as c from t1, t2",
+	}, {
+		sql:    "select *, t1.* from t1, t2",
+		expSQL: "select t1.a as a, t1.b as b, t1.c as c, t2.c1 as c1, t2.c2 as c2, t1.a as a, t1.b as b, t1.c as c from t1, t2",
+	}, { // aliased table
+		sql:    "select * from t1 a, t2 b",
+		expSQL: "select a.a as a, a.b as b, a.c as c, b.c1 as c1, b.c2 as c2 from t1 as a, t2 as b",
+	}, { // t3 is non-authoritative table
+		sql:    "select * from t3",
+		expSQL: "select * from t3",
+	}, { // t3 is non-authoritative table
+		sql:    "select * from t1, t2, t3",
+		expSQL: "select * from t1, t2, t3",
+	}, { // t3 is non-authoritative table
+		sql:    "select t1.*, t2.*, t3.* from t1, t2, t3",
+		expSQL: "select t1.a as a, t1.b as b, t1.c as c, t2.c1 as c1, t2.c2 as c2, t3.* from t1, t2, t3",
+	}, {
+		sql:    "select foo.* from t1, t2",
+		expErr: "Unknown table 'foo'",
+	}}
+	for _, tcase := range tcases {
+		t.Run(tcase.sql, func(t *testing.T) {
+			ast, err := sqlparser.Parse(tcase.sql)
+			require.NoError(t, err)
+			selectStatement, isSelectStatement := ast.(*sqlparser.Select)
+			require.True(t, isSelectStatement, "analyzer expects a select statement")
+			_, err = Analyze(selectStatement, cDB, schemaInfo)
+			if tcase.expErr == "" {
+				require.NoError(t, err)
+				assert.Equal(t, tcase.expSQL, sqlparser.String(selectStatement))
+			} else {
+				require.EqualError(t, err, tcase.expErr)
+			}
+		})
+	}
+}
+
+func TestOrderByGroupByLiteral(t *testing.T) {
+	schemaInfo := &FakeSI{
+		Tables: map[string]*vindexes.Table{},
+	}
+	cDB := "db"
+	tcases := []struct {
+		sql    string
+		expSQL string
+		expErr string
+	}{{
+		sql:    "select 1 as id from t1 order by 1",
+		expSQL: "select 1 as id from t1 order by id asc",
+	}, {
+		sql:    "select t1.col from t1 order by 1",
+		expSQL: "select t1.col from t1 order by t1.col asc",
+	}, {
+		sql:    "select t1.col from t1 group by 1",
+		expSQL: "select t1.col from t1 group by t1.col",
+	}, {
+		sql:    "select t1.col as xyz from t1 group by 1",
+		expSQL: "select t1.col as xyz from t1 group by xyz",
+	}, {
+		sql:    "select t1.col as xyz, count(*) from t1 group by 1 order by 2",
+		expSQL: "select t1.col as xyz, count(*) from t1 group by xyz order by count(*) asc",
+	}, {
+		sql:    "select id from t1 group by 2",
+		expErr: "Unknown column '2' in 'group statement'",
+	}, {
+		sql:    "select id from t1 order by 2",
+		expErr: "Unknown column '2' in 'order clause'",
+	}, {
+		sql:    "select *, id from t1 order by 2",
+		expErr: "cannot use column offsets in order clause when using `*`",
+	}, {
+		sql:    "select *, id from t1 group by 2",
+		expErr: "cannot use column offsets in group statement when using `*`",
+	}}
+	for _, tcase := range tcases {
+		t.Run(tcase.sql, func(t *testing.T) {
+			ast, err := sqlparser.Parse(tcase.sql)
+			require.NoError(t, err)
+			selectStatement := ast.(*sqlparser.Select)
+			_, err = Analyze(selectStatement, cDB, schemaInfo)
+			if tcase.expErr == "" {
+				require.NoError(t, err)
+				assert.Equal(t, tcase.expSQL, sqlparser.String(selectStatement))
+			} else {
+				require.EqualError(t, err, tcase.expErr)
+			}
+		})
+	}
+}
+
+func TestSemTableDependenciesAfterExpandStar(t *testing.T) {
+	schemaInfo := &FakeSI{Tables: map[string]*vindexes.Table{
+		"t1": {
+			Name: sqlparser.NewTableIdent("t1"),
+			Columns: []vindexes.Column{{
+				Name: sqlparser.NewColIdent("a"),
+				Type: sqltypes.VarChar,
+			}},
+			ColumnListAuthoritative: true,
+		}}}
+	tcases := []struct {
+		sql         string
+		expSQL      string
+		sameTbl     int
+		otherTbl    int
+		expandedCol int
+	}{{
+		sql:      "select a, * from t1",
+		expSQL:   "select a, a from t1",
+		otherTbl: -1, sameTbl: 0, expandedCol: 1,
+	}, {
+		sql:      "select t2.a, t1.a, t1.* from t1, t2",
+		expSQL:   "select t2.a, t1.a, t1.a as a from t1, t2",
+		otherTbl: 0, sameTbl: 1, expandedCol: 2,
+	}, {
+		sql:      "select t2.a, t.a, t.* from t1 t, t2",
+		expSQL:   "select t2.a, t.a, t.a as a from t1 as t, t2",
+		otherTbl: 0, sameTbl: 1, expandedCol: 2,
+	}}
+	for _, tcase := range tcases {
+		t.Run(tcase.sql, func(t *testing.T) {
+			ast, err := sqlparser.Parse(tcase.sql)
+			require.NoError(t, err)
+			selectStatement, isSelectStatement := ast.(*sqlparser.Select)
+			require.True(t, isSelectStatement, "analyzer expects a select statement")
+			semTable, err := Analyze(selectStatement, "", schemaInfo)
+			require.NoError(t, err)
+			assert.Equal(t, tcase.expSQL, sqlparser.String(selectStatement))
+			if tcase.otherTbl != -1 {
+				assert.NotEqual(t,
+					semTable.BaseTableDependencies(selectStatement.SelectExprs[tcase.otherTbl].(*sqlparser.AliasedExpr).Expr),
+					semTable.BaseTableDependencies(selectStatement.SelectExprs[tcase.expandedCol].(*sqlparser.AliasedExpr).Expr),
+				)
+			}
+			if tcase.sameTbl != -1 {
+				assert.Equal(t,
+					semTable.BaseTableDependencies(selectStatement.SelectExprs[tcase.sameTbl].(*sqlparser.AliasedExpr).Expr),
+					semTable.BaseTableDependencies(selectStatement.SelectExprs[tcase.expandedCol].(*sqlparser.AliasedExpr).Expr),
+				)
+			}
+		})
+	}
+}