func pushdownIndexesToTable(a *Analyzer, tableNode NameableNode, indexes map[string]*indexLookup) (sql.Node, error) {
	return plan.TransformUp(tableNode, func(n sql.Node) (sql.Node, error) {
		switch n := n.(type) {
		case *plan.ResolvedTable:
			table := getTable(tableNode)
			if table == nil {
				return n, nil
			}
			if _, ok := table.(sql.IndexAddressableTable); ok {
				indexLookup, ok := indexes[tableNode.Name()]
				if ok {
					a.Log("table %q transformed with pushdown of index", tableNode.Name())
					return plan.NewStaticIndexedTableAccess(n, indexLookup.lookup, indexLookup.indexes[0], indexLookup.exprs), nil
				}
			}
		}
		return n, nil
	})
}
pushdownIndexesToTable attempts to convert filter predicates to indexes on tables that implement
sql.IndexAddressableTable
package analyzer
import (
	"fmt"
	"strings"
	"github.com/dolthub/go-mysql-server/sql"
	"github.com/dolthub/go-mysql-server/sql/expression"
	"github.com/dolthub/go-mysql-server/sql/plan"
)
func pushdownFilters(ctx *sql.Context, a *Analyzer, n sql.Node, scope *Scope) (sql.Node, error) {
	span, ctx := ctx.Span("pushdown_filters")
	defer span.Finish()
	if !canDoPushdown(n) {
		return n, nil
	}
	return pushdownFiltersAtNode(ctx, a, n, scope)
}
func pushdownFiltersAtNode(ctx *sql.Context, a *Analyzer, n sql.Node, scope *Scope) (sql.Node, error) {
	indexes, err := getIndexesByTable(ctx, a, n, scope)
	if err != nil {
		return nil, err
	}
	tableAliases, err := getTableAliases(n, scope)
	if err != nil {
		return nil, err
	}
	n, err = convertFiltersToIndexedAccess(ctx, a, n, scope, indexes)
	if err != nil {
		return nil, err
	}
	return transformPushdownFilters(ctx, a, n, scope, tableAliases)
}
func pushdownSubqueryAliasFilters(ctx *sql.Context, a *Analyzer, n sql.Node, scope *Scope) (sql.Node, error) {
	span, ctx := ctx.Span("pushdown_subquery_alias_filters")
	defer span.Finish()
	if !canDoPushdown(n) {
		return n, nil
	}
	tableAliases, err := getTableAliases(n, scope)
	if err != nil {
		return nil, err
	}
	return transformPushdownSubqueryAliasFilters(ctx, a, n, scope, tableAliases)
}
func pushdownProjections(ctx *sql.Context, a *Analyzer, n sql.Node, scope *Scope) (sql.Node, error) {
	span, ctx := ctx.Span("pushdown_projections")
	defer span.Finish()
	if !canDoPushdown(n) {
		return n, nil
	}
	if !canProject(n, a) {
		return n, nil
	}
	return transformPushdownProjections(ctx, a, n, scope)
}
func canProject(n sql.Node, a *Analyzer) bool {
	switch n.(type) {
	case *plan.Update, *plan.RowUpdateAccumulator, *plan.DeleteFrom, *plan.Block, *plan.BeginEndBlock, *plan.TriggerBeginEndBlock:
		return false
	}
	containsSubquery := false
	plan.InspectExpressions(n, func(e sql.Expression) bool {
		if _, ok := e.(*plan.Subquery); ok {
			containsSubquery = true
			return false
		}
		return true
	})
	if containsSubquery {
		a.Log("skipping pushdown of projection for query with subquery")
		return false
	}
	containsIndexedJoin := false
	plan.Inspect(n, func(node sql.Node) bool {
		if _, ok := node.(*plan.IndexedJoin); ok {
			containsIndexedJoin = true
			return false
		}
		return true
	})
	if containsIndexedJoin {
		a.Log("skipping pushdown of projection for query with an indexed join")
		return false
	}
	alreadyPushedDown := false
	plan.Inspect(n, func(n sql.Node) bool {
		if n, ok := n.(*plan.DecoratedNode); ok && strings.Contains(n.String(), "Projected table access on") {
			alreadyPushedDown = true
			return false
		}
		return true
	})
	return !alreadyPushedDown
}
func canDoPushdown(n sql.Node) bool {
	if !n.Resolved() {
		return false
	}
	if plan.IsNoRowNode(n) {
		return false
	}
	switch n.(type) {
	case *plan.InsertInto:
		return false
	}
	return true
}
func filterPushdownChildSelector(c plan.TransformContext) bool {
	switch n := c.Parent.(type) {
	case *plan.TableAlias:
		return false
	case *plan.Window:
		return false
	case *plan.IndexedJoin:
		if n.JoinType() == plan.JoinTypeLeft || n.JoinType() == plan.JoinTypeRight {
			return c.ChildNum == 0
		}
		return true
	case *plan.LeftJoin:
		return c.ChildNum == 0
	case *plan.RightJoin:
		return c.ChildNum == 1
	}
	return true
}
func filterPushdownAboveTablesChildSelector(c plan.TransformContext) bool {
	if !filterPushdownChildSelector(c) {
		return false
	}
	switch c.Parent.(type) {
	case *plan.Filter:
		switch c.Node.(type) {
		case *plan.TableAlias, *plan.ResolvedTable, *plan.IndexedTableAccess, *plan.ValueDerivedTable:
			return false
		}
	}
	return true
}
func transformPushdownFilters(ctx *sql.Context, a *Analyzer, n sql.Node, scope *Scope, tableAliases TableAliases) (sql.Node, error) {
	applyFilteredTables := func(n *plan.Filter, filters *filterSet) (sql.Node, error) {
		return plan.TransformUpCtx(n, filterPushdownChildSelector, func(c plan.TransformContext) (sql.Node, error) {
			switch node := c.Node.(type) {
			case *plan.Filter:
				n, err := removePushedDownPredicates(ctx, a, node, filters)
				if err != nil {
					return nil, err
				}
				return FixFieldIndexesForExpressions(ctx, a, n, scope)
			case *plan.TableAlias, *plan.ResolvedTable, *plan.IndexedTableAccess, *plan.ValueDerivedTable:
				table, err := pushdownFiltersToTable(ctx, a, node.(NameableNode), scope, filters, tableAliases)
				if err != nil {
					return nil, err
				}
				return FixFieldIndexesForExpressions(ctx, a, table, scope)
			default:
				return FixFieldIndexesForExpressions(ctx, a, node, scope)
			}
		})
	}
	pushdownAboveTables := func(n sql.Node, filters *filterSet) (sql.Node, error) {
		return plan.TransformUpCtx(n, filterPushdownAboveTablesChildSelector, func(c plan.TransformContext) (sql.Node, error) {
			switch node := c.Node.(type) {
			case *plan.Filter:
				n, err := removePushedDownPredicates(ctx, a, node, filters)
				if err != nil {
					return nil, err
				}
				return FixFieldIndexesForExpressions(ctx, a, n, scope)
			case *plan.TableAlias, *plan.ResolvedTable, *plan.IndexedTableAccess, *plan.ValueDerivedTable:
				table, err := pushdownFiltersToAboveTable(ctx, a, node.(NameableNode), scope, filters)
				if err != nil {
					return nil, err
				}
				return FixFieldIndexesForExpressions(ctx, a, table, scope)
			default:
				return FixFieldIndexesForExpressions(ctx, a, node, scope)
			}
		})
	}
	return plan.TransformUp(n, func(n sql.Node) (sql.Node, error) {
		switch n := n.(type) {
		case *plan.Filter:
			filtersByTable := getFiltersByTable(n)
			filters := newFilterSet(n.Expression, filtersByTable, tableAliases)
			node, err := applyFilteredTables(n, filters)
			if err != nil {
				return nil, err
			}
			return pushdownAboveTables(node, filters)
		case *plan.Window:
			child, err := pushdownFiltersAtNode(ctx, a, n.Child, scope)
			if err != nil {
				return nil, err
			}
			return n.WithChildren(child)
		default:
			return n, nil
		}
	})
}
func transformPushdownSubqueryAliasFilters(ctx *sql.Context, a *Analyzer, n sql.Node, scope *Scope, tableAliases TableAliases) (sql.Node, error) {
	var filters *filterSet
	transformFilterNode := func(n *plan.Filter) (sql.Node, error) {
		return plan.TransformUpCtx(n, filterPushdownChildSelector, func(c plan.TransformContext) (sql.Node, error) {
			switch node := c.Node.(type) {
			case *plan.Filter:
				return removePushedDownPredicates(ctx, a, node, filters)
			case *plan.SubqueryAlias:
				return pushdownFiltersUnderSubqueryAlias(ctx, a, node, filters)
			default:
				return node, nil
			}
		})
	}
	return plan.TransformUp(n, func(n sql.Node) (sql.Node, error) {
		switch n := n.(type) {
		case *plan.Filter:
			filtersByTable := getFiltersByTable(n)
			filters = newFilterSet(n.Expression, filtersByTable, tableAliases)
			return transformFilterNode(n)
		default:
			return n, nil
		}
	})
}
func convertFiltersToIndexedAccess(
	ctx *sql.Context,
	a *Analyzer,
	n sql.Node,
	scope *Scope,
	indexes indexLookupsByTable,
) (sql.Node, error) {
	childSelector := func(c plan.TransformContext) bool {
		switch c.Node.(type) {
		case *plan.IndexedTableAccess:
			return false
		}
		switch c.Parent.(type) {
		case *plan.IndexedJoin:
			return c.ChildNum == 0
		case *plan.LeftJoin:
			return c.ChildNum == 0
		case *plan.RightJoin:
			return c.ChildNum == 1
		case *plan.TableAlias:
			return false
		case *plan.Window:
			return false
		}
		return true
	}
	node, err := plan.TransformUpCtx(n, childSelector, func(c plan.TransformContext) (sql.Node, error) {
		switch node := c.Node.(type) {
		case *plan.TableAlias:
			table, err := pushdownIndexesToTable(a, node, indexes)
			if err != nil {
				return nil, err
			}
			return plan.TransformUp(table, func(n sql.Node) (sql.Node, error) {
				ita, ok := n.(*plan.IndexedTableAccess)
				if !ok {
					return n, nil
				}
				newExprs, err := FixFieldIndexesOnExpressions(ctx, scope, a, table.Schema(), ita.Expressions()...)
				if err != nil {
					return nil, err
				}
				return ita.WithExpressions(newExprs...)
			})
		case *plan.ResolvedTable:
			table, err := pushdownIndexesToTable(a, node, indexes)
			if err != nil {
				return nil, err
			}
			return FixFieldIndexesForTableNode(ctx, a, table, scope)
		default:
			return FixFieldIndexesForExpressions(ctx, a, node, scope)
		}
	})
	if err != nil {
		return nil, err
	}
	return node, nil
}
func pushdownFiltersToTable(
	ctx *sql.Context,
	a *Analyzer,
	tableNode NameableNode,
	scope *Scope,
	filters *filterSet,
	tableAliases TableAliases,
) (sql.Node, error) {
	table := getTable(tableNode)
	if table == nil {
		return tableNode, nil
	}
	var newTableNode sql.Node = tableNode
	if ft, ok := table.(sql.FilteredTable); ok && len(filters.availableFiltersForTable(ctx, tableNode.Name())) > 0 {
		tableFilters := filters.availableFiltersForTable(ctx, tableNode.Name())
		handled := ft.HandledFilters(normalizeExpressions(ctx, tableAliases, tableFilters...))
		filters.markFiltersHandled(handled...)
		handled, err := FixFieldIndexesOnExpressions(ctx, scope, a, tableNode.Schema(), handled...)
		if err != nil {
			return nil, err
		}
		table = ft.WithFilters(ctx, handled)
		newTableNode = plan.NewDecoratedNode(
			fmt.Sprintf("Filtered table access on %v", handled),
			newTableNode)
		a.Log(
			"table %q transformed with pushdown of filters, %d filters handled of %d",
			tableNode.Name(),
			len(handled),
			len(tableFilters),
		)
	}
	switch tableNode.(type) {
	case *plan.ResolvedTable, *plan.TableAlias, *plan.IndexedTableAccess, *plan.ValueDerivedTable:
		return withTable(newTableNode, table)
	default:
		return nil, ErrInvalidNodeType.New("pushdownFiltersToTable", tableNode)
	}
}
func pushdownFiltersToAboveTable(
	ctx *sql.Context,
	a *Analyzer,
	tableNode NameableNode,
	scope *Scope,
	filters *filterSet,
) (sql.Node, error) {
	table := getTable(tableNode)
	if table == nil {
		return tableNode, nil
	}
	var pushedDownFilterExpression sql.Expression
	if tableFilters := filters.availableFiltersForTable(ctx, tableNode.Name()); len(tableFilters) > 0 {
		filters.markFiltersHandled(tableFilters...)
		handled, err := FixFieldIndexesOnExpressions(ctx, scope, a, tableNode.Schema(), tableFilters...)
		if err != nil {
			return nil, err
		}
		pushedDownFilterExpression = expression.JoinAnd(handled...)
		a.Log(
			"pushed down filters %s above table %q, %d filters handled of %d",
			handled,
			tableNode.Name(),
			len(handled),
			len(tableFilters),
		)
	}
	switch tableNode.(type) {
	case *plan.ResolvedTable, *plan.TableAlias, *plan.IndexedTableAccess, *plan.ValueDerivedTable:
		node, err := withTable(tableNode, table)
		if err != nil {
			return nil, err
		}
		if pushedDownFilterExpression != nil {
			return plan.NewFilter(pushedDownFilterExpression, node), nil
		}
		return node, nil
	default:
		return nil, ErrInvalidNodeType.New("pushdownFiltersToAboveTable", tableNode)
	}
}
func pushdownFiltersUnderSubqueryAlias(ctx *sql.Context, a *Analyzer, sa *plan.SubqueryAlias, filters *filterSet) (sql.Node, error) {
	handled := filters.availableFiltersForTable(ctx, sa.Name())
	if len(handled) == 0 {
		return sa, nil
	}
	filters.markFiltersHandled(handled...)
	schema := sa.Schema()
	handled, err := FixFieldIndexesOnExpressions(ctx, nil, a, schema, handled...)
	if err != nil {
		return nil, err
	}
	childSchema := sa.Child.Schema()
	expressionsForChild := make([]sql.Expression, len(handled))
	for i, h := range handled {
		expressionsForChild[i], err = expression.TransformUp(h, func(e sql.Expression) (sql.Expression, error) {
			if gt, ok := e.(*expression.GetField); ok {
				col := childSchema[gt.Index()]
				return gt.WithTable(col.Source).WithName(col.Name), nil
			}
			return e, nil
		})
	}
	return sa.WithChildren(plan.NewFilter(expression.JoinAnd(expressionsForChild...), sa.Child))
}