#
# Generate code for interesting rule.
#
optgen factory test.opt
[Relational]
define Select {
    Input   RelExpr
    Filters FiltersExpr
}

[Relational, Join, JoinNonApply]
define InnerJoin {
    Left  RelExpr
    Right RelExpr
    On    FiltersExpr
}

[Relational, Join, JoinApply]
define InnerJoinApply {
    Left  RelExpr
    Right RelExpr
    On    FiltersExpr
}

[Scalar, Boolean, List]
define Filters {
}

[Scalar, Boolean, ListItem]
define FiltersItem {
    Condition ScalarExpr

    scalar ScalarProps
}

[PushSelectIntoJoinLeft, Normalize]
(Select
    $input:(InnerJoin | InnerJoinApply
        $left:*
        $right:*
        $on:*
    )
    $filters:[
        ...
        $item:* & (IsBoundBy $item $leftCols:(OutputCols $left))
        ...
    ]
)
=>
(Select
    ((OpName $input)
        (Select
            $left
            (ExtractBoundConditions $filters $leftCols)
        )
        $right
        $on
    )
    (ExtractUnboundConditions $filters $leftCols)
)
----
----
// Code generated by optgen; [omitted]

package norm

import (
	"github.com/cockroachdb/cockroach/pkg/sql/coltypes"
	"github.com/cockroachdb/cockroach/pkg/sql/opt"
	"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
	"github.com/cockroachdb/cockroach/pkg/sql/opt/props/physical"
	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
	"github.com/cockroachdb/cockroach/pkg/sql/sem/types"
	"github.com/cockroachdb/cockroach/pkg/util/log"
)

// ConstructSelect constructs an expression for the Select operator.
func (_f *Factory) ConstructSelect(
	input memo.RelExpr,
	filters memo.FiltersExpr,
) memo.RelExpr {
	// [PushSelectIntoJoinLeft]
	{
		if input.Op() == opt.InnerJoinOp || input.Op() == opt.InnerJoinApplyOp {
			left := input.Child(0).(memo.RelExpr)
			right := input.Child(1).(memo.RelExpr)
			on := *input.Child(2).(*memo.FiltersExpr)
			for i := range filters {
				item := &filters[i]
				leftCols := _f.funcs.OutputCols(left)
				if _f.funcs.IsBoundBy(item, leftCols) {
					if _f.matchedRule == nil || _f.matchedRule(opt.PushSelectIntoJoinLeft) {
						on := on
						_expr := _f.ConstructSelect(
							_f.DynamicConstruct(
								input.Op(),
								_f.ConstructSelect(
									left,
									_f.funcs.ExtractBoundConditions(filters, leftCols),
								),
								right,
								&on,
							).(memo.RelExpr),
							_f.funcs.ExtractUnboundConditions(filters, leftCols),
						)
						if _f.appliedRule != nil {
							_f.appliedRule(opt.PushSelectIntoJoinLeft, nil, _expr)
						}
						return _expr
					}
				}
			}
		}
	}

	e := _f.mem.MemoizeSelect(input, filters)
	return _f.onConstructRelational(e)
}

// ConstructInnerJoin constructs an expression for the InnerJoin operator.
func (_f *Factory) ConstructInnerJoin(
	left memo.RelExpr,
	right memo.RelExpr,
	on memo.FiltersExpr,
) memo.RelExpr {
	e := _f.mem.MemoizeInnerJoin(left, right, on)
	return _f.onConstructRelational(e)
}

// ConstructInnerJoinApply constructs an expression for the InnerJoinApply operator.
func (_f *Factory) ConstructInnerJoinApply(
	left memo.RelExpr,
	right memo.RelExpr,
	on memo.FiltersExpr,
) memo.RelExpr {
	e := _f.mem.MemoizeInnerJoinApply(left, right, on)
	return _f.onConstructRelational(e)
}

// Replace enables an expression subtree to be rewritten under the control of
// the caller. It passes each child of the given expression to the replace
// callback. The caller can continue traversing the expression tree within the
// callback by recursively calling Replace. It can also return a replacement
// expression; if it does, then Replace will rebuild the operator and its
// ancestors via a calls to the corresponding factory Construct methods. Here
// is example usage:
//
//   var replace func(e opt.Expr, replace ReplaceFunc) opt.Expr
//   replace = func(e opt.Expr, replace ReplaceFunc) opt.Expr {
//     if e.Op() == opt.VariableOp {
//       return getReplaceVar(e)
//     }
//     return e.Replace(e, replace)
//   }
//   replace(root, replace)
//
// Here, all variables in the tree are being replaced by some other expression
// in a pre-order traversal of the tree. Post-order traversal is trivially
// achieved by moving the e.Replace call to the top of the replace function
// rather than bottom.
func (f *Factory) Replace(e opt.Expr, replace ReplaceFunc) opt.Expr {
	switch t := e.(type) {
	case *memo.SelectExpr:
		input := replace(t.Input).(memo.RelExpr)
		filters, filtersChanged := f.replaceFiltersExpr(t.Filters, replace)
		if input != t.Input || filtersChanged {
			return f.ConstructSelect(input, filters)
		}
		return t

	case *memo.InnerJoinExpr:
		left := replace(t.Left).(memo.RelExpr)
		right := replace(t.Right).(memo.RelExpr)
		on, onChanged := f.replaceFiltersExpr(t.On, replace)
		if left != t.Left || right != t.Right || onChanged {
			return f.ConstructInnerJoin(left, right, on)
		}
		return t

	case *memo.InnerJoinApplyExpr:
		left := replace(t.Left).(memo.RelExpr)
		right := replace(t.Right).(memo.RelExpr)
		on, onChanged := f.replaceFiltersExpr(t.On, replace)
		if left != t.Left || right != t.Right || onChanged {
			return f.ConstructInnerJoinApply(left, right, on)
		}
		return t

	case *memo.FiltersExpr:
		if after, changed := f.replaceFiltersExpr(*t, replace); changed {
			return &after
		}
		return t

	}
	panic(pgerror.NewAssertionErrorf("unhandled op %s", log.Safe(e.Op())))
}

func (f *Factory) replaceFiltersExpr(list memo.FiltersExpr, replace ReplaceFunc) (_ memo.FiltersExpr, changed bool) {
	var newList []memo.FiltersItem
	for i := range list {
		before := list[i].Condition
		after := replace(before).(opt.ScalarExpr)
		if before != after {
			if newList == nil {
				newList = make([]memo.FiltersItem, len(list))
				copy(newList, list[:i])
			}
			newList[i].Condition = after
		} else if newList != nil {
			newList[i] = list[i]
		}
	}
	if newList == nil {
		return list, false
	}
	return newList, true
}

// CopyAndReplaceDefault performs the default traversal and cloning behavior
// for the CopyAndReplace method. It constructs a copy of the given source
// operator using children copied (and potentially remapped) by the given replace
// function. See comments for CopyAndReplace for more details.
func (f *Factory) CopyAndReplaceDefault(src opt.Expr, replace ReplaceFunc) (dst opt.Expr) {
	switch t := src.(type) {
	case *memo.SelectExpr:
		return f.ConstructSelect(
			f.invokeReplace(t.Input, replace).(memo.RelExpr),
			f.copyAndReplaceDefaultFiltersExpr(t.Filters, replace),
		)

	case *memo.InnerJoinExpr:
		return f.ConstructInnerJoin(
			f.invokeReplace(t.Left, replace).(memo.RelExpr),
			f.invokeReplace(t.Right, replace).(memo.RelExpr),
			f.copyAndReplaceDefaultFiltersExpr(t.On, replace),
		)

	case *memo.InnerJoinApplyExpr:
		return f.ConstructInnerJoinApply(
			f.invokeReplace(t.Left, replace).(memo.RelExpr),
			f.invokeReplace(t.Right, replace).(memo.RelExpr),
			f.copyAndReplaceDefaultFiltersExpr(t.On, replace),
		)

	}
	panic(pgerror.NewAssertionErrorf("unhandled op %s", log.Safe(src.Op())))
}

func (f *Factory) copyAndReplaceDefaultFiltersExpr(src memo.FiltersExpr, replace ReplaceFunc) (dst memo.FiltersExpr) {
	dst = make(memo.FiltersExpr, len(src))
	for i := range src {
		dst[i].Condition = f.invokeReplace(src[i].Condition, replace).(opt.ScalarExpr)
	}
	return dst
}

// invokeReplace wraps the user-provided replace function. See comments for
// CopyAndReplace for more details.
func (f *Factory) invokeReplace(src opt.Expr, replace ReplaceFunc) (dst opt.Expr) {
	if rel, ok := src.(memo.RelExpr); ok {
		src = rel.FirstExpr()
	}
	return replace(src)
}

func (f *Factory) DynamicConstruct(op opt.Operator, args ...interface{}) opt.Expr {
	switch op {
	case opt.SelectOp:
		return f.ConstructSelect(
			args[0].(memo.RelExpr),
			*args[1].(*memo.FiltersExpr),
		)
	case opt.InnerJoinOp:
		return f.ConstructInnerJoin(
			args[0].(memo.RelExpr),
			args[1].(memo.RelExpr),
			*args[2].(*memo.FiltersExpr),
		)
	case opt.InnerJoinApplyOp:
		return f.ConstructInnerJoinApply(
			args[0].(memo.RelExpr),
			args[1].(memo.RelExpr),
			*args[2].(*memo.FiltersExpr),
		)
	}
	panic(pgerror.NewAssertionErrorf("cannot dynamically construct operator %s", log.Safe(op)))
}
----
----
