#
# 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, Bool, List]
define Filters {
}

[Scalar, Bool, ListItem, ScalarProps]
define FiltersItem {
    Condition ScalarExpr
}

[Scalar, ListItem]
define KVOptionsItem {
    Value ScalarExpr
    Key   string
}

[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/opt"
	"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
	"github.com/cockroachdb/cockroach/pkg/sql/opt/props"
	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
	"github.com/cockroachdb/cockroach/pkg/sql/types"
	"github.com/cockroachdb/errors"
)

// ConstructSelect constructs an expression for the Select operator.
func (_f *Factory) ConstructSelect(
	input memo.RelExpr,
	filters memo.FiltersExpr,
) memo.RelExpr {
	opt.MaybeInjectOptimizerTestingPanic(_f.ctx, _f.evalCtx)
	_f.constructorStackDepth++
	if _f.constructorStackDepth > maxConstructorStackDepth {
		// If the constructor call stack depth exceeds the limit, call
		// onMaxConstructorStackDepthExceeded and skip all rules.
		_f.onMaxConstructorStackDepthExceeded()
		goto SKIP_RULES
	}

	// [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)
						}
						_f.constructorStackDepth--
						return _expr
					}
				}
			}
		}
	}

SKIP_RULES:
	e := _f.mem.MemoizeSelect(input, filters)
	expr := _f.onConstructRelational(e)
	_f.constructorStackDepth--
	return expr
}

// ConstructInnerJoin constructs an expression for the InnerJoin operator.
func (_f *Factory) ConstructInnerJoin(
	left memo.RelExpr,
	right memo.RelExpr,
	on memo.FiltersExpr,
) memo.RelExpr {
	opt.MaybeInjectOptimizerTestingPanic(_f.ctx, _f.evalCtx)
	_f.constructorStackDepth++
	if _f.constructorStackDepth > maxConstructorStackDepth {
		// If the constructor call stack depth exceeds the limit, call
		// onMaxConstructorStackDepthExceeded and skip all rules.
		_f.onMaxConstructorStackDepthExceeded()
		goto SKIP_RULES
	}

SKIP_RULES:
	e := _f.mem.MemoizeInnerJoin(left, right, on)
	expr := _f.onConstructRelational(e)
	_f.constructorStackDepth--
	return expr
}

// ConstructInnerJoinApply constructs an expression for the InnerJoinApply operator.
func (_f *Factory) ConstructInnerJoinApply(
	left memo.RelExpr,
	right memo.RelExpr,
	on memo.FiltersExpr,
) memo.RelExpr {
	opt.MaybeInjectOptimizerTestingPanic(_f.ctx, _f.evalCtx)
	_f.constructorStackDepth++
	if _f.constructorStackDepth > maxConstructorStackDepth {
		// If the constructor call stack depth exceeds the limit, call
		// onMaxConstructorStackDepthExceeded and skip all rules.
		_f.onMaxConstructorStackDepthExceeded()
		goto SKIP_RULES
	}

SKIP_RULES:
	e := _f.mem.MemoizeInnerJoinApply(left, right, on)
	expr := _f.onConstructRelational(e)
	_f.constructorStackDepth--
	return expr
}

// ConstructFiltersItem constructs an expression for the FiltersItem operator.
func (_f *Factory) ConstructFiltersItem(
	condition opt.ScalarExpr,
) memo.FiltersItem {
	opt.MaybeInjectOptimizerTestingPanic(_f.ctx, _f.evalCtx)
	item := memo.FiltersItem{Condition: condition}
	item.PopulateProps(_f.mem)
	return item
}

// ConstructKVOptionsItem constructs an expression for the KVOptionsItem operator.
func (_f *Factory) ConstructKVOptionsItem(
	value opt.ScalarExpr,
	key string,
) memo.KVOptionsItem {
	opt.MaybeInjectOptimizerTestingPanic(_f.ctx, _f.evalCtx)
	item := memo.KVOptionsItem{Value: value, Key: key}
	return item
}

// 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 call to the corresponding factory Construct methods. Here
// is example usage:
//
//	var replace func(e opt.Expr) opt.Expr
//	replace = func(e opt.Expr) opt.Expr {
//	  if e.Op() == opt.VariableOp {
//	    return getReplaceVar(e)
//	  }
//	  return factory.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 factory.Replace call to the top of the replace
// function rather than bottom.
func (f *Factory) Replace(e opt.Expr, replace ReplaceFunc) opt.Expr {
	opt.MaybeInjectOptimizerTestingPanic(f.ctx, f.evalCtx)
	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(errors.AssertionFailedf("unhandled op %s", errors.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] = f.ConstructFiltersItem(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) {
	opt.MaybeInjectOptimizerTestingPanic(f.ctx, f.evalCtx)
	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),
		)

	case *memo.FiltersExpr:
		newVal := f.copyAndReplaceDefaultFiltersExpr(*t, replace)
		return &newVal

	default:
		panic(errors.AssertionFailedf("unhandled op %s", errors.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)
		dst[i].PopulateProps(f.mem)
		f.mem.CheckExpr(&dst[i])
	}
	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 {
	opt.MaybeInjectOptimizerTestingPanic(f.ctx, f.evalCtx)
	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(errors.AssertionFailedf("cannot dynamically construct operator %s", errors.Safe(op)))
}
----
----

#
# Generate code for testing rules that manipulate a strongly-typed child
# expression. Try several different variations of the rule to test various
# static and dynamic matching and replacing variations.
#
optgen factory test.opt

[Scalar]
define Variable {
    Col ColumnID
}

[Scalar, Aggregate]
define Min {
    Input VariableExpr
}

[Scalar, Aggregate]
define Max {
    Input VariableExpr
}

[Scalar]
define AggDistinct {
    Input ScalarExpr
}

[EliminateAggDistinct1, Normalize]
(AggDistinct
    $input:(Min | Max)
)
=>
$input

[EliminateAggDistinct2, Normalize]
(AggDistinct
    $input:(Min | Max $variable:*)
)
=>
((OpName $input) $variable)

[EliminateAggDistinct3, Normalize]
(AggDistinct
    $input:(Min $variable:*)
)
=>
(Min $variable)
----
----
// Code generated by optgen; [omitted]

package norm

import (
	"github.com/cockroachdb/cockroach/pkg/sql/opt"
	"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
	"github.com/cockroachdb/cockroach/pkg/sql/opt/props"
	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
	"github.com/cockroachdb/cockroach/pkg/sql/types"
	"github.com/cockroachdb/errors"
)

// ConstructVariable constructs an expression for the Variable operator.
func (_f *Factory) ConstructVariable(
	col opt.ColumnID,
) opt.ScalarExpr {
	opt.MaybeInjectOptimizerTestingPanic(_f.ctx, _f.evalCtx)
	_f.constructorStackDepth++
	if _f.constructorStackDepth > maxConstructorStackDepth {
		// If the constructor call stack depth exceeds the limit, call
		// onMaxConstructorStackDepthExceeded and skip all rules.
		_f.onMaxConstructorStackDepthExceeded()
		goto SKIP_RULES
	}

SKIP_RULES:
	e := _f.mem.MemoizeVariable(col)
	expr := _f.onConstructScalar(e)
	_f.constructorStackDepth--
	return expr
}

// ConstructMin constructs an expression for the Min operator.
func (_f *Factory) ConstructMin(
	input *memo.VariableExpr,
) opt.ScalarExpr {
	opt.MaybeInjectOptimizerTestingPanic(_f.ctx, _f.evalCtx)
	_f.constructorStackDepth++
	if _f.constructorStackDepth > maxConstructorStackDepth {
		// If the constructor call stack depth exceeds the limit, call
		// onMaxConstructorStackDepthExceeded and skip all rules.
		_f.onMaxConstructorStackDepthExceeded()
		goto SKIP_RULES
	}

SKIP_RULES:
	e := _f.mem.MemoizeMin(input)
	expr := _f.onConstructScalar(e)
	_f.constructorStackDepth--
	return expr
}

// ConstructMax constructs an expression for the Max operator.
func (_f *Factory) ConstructMax(
	input *memo.VariableExpr,
) opt.ScalarExpr {
	opt.MaybeInjectOptimizerTestingPanic(_f.ctx, _f.evalCtx)
	_f.constructorStackDepth++
	if _f.constructorStackDepth > maxConstructorStackDepth {
		// If the constructor call stack depth exceeds the limit, call
		// onMaxConstructorStackDepthExceeded and skip all rules.
		_f.onMaxConstructorStackDepthExceeded()
		goto SKIP_RULES
	}

SKIP_RULES:
	e := _f.mem.MemoizeMax(input)
	expr := _f.onConstructScalar(e)
	_f.constructorStackDepth--
	return expr
}

// ConstructAggDistinct constructs an expression for the AggDistinct operator.
func (_f *Factory) ConstructAggDistinct(
	input opt.ScalarExpr,
) opt.ScalarExpr {
	opt.MaybeInjectOptimizerTestingPanic(_f.ctx, _f.evalCtx)
	_f.constructorStackDepth++
	if _f.constructorStackDepth > maxConstructorStackDepth {
		// If the constructor call stack depth exceeds the limit, call
		// onMaxConstructorStackDepthExceeded and skip all rules.
		_f.onMaxConstructorStackDepthExceeded()
		goto SKIP_RULES
	}

	// [EliminateAggDistinct1]
	{
		if input.Op() == opt.MinOp || input.Op() == opt.MaxOp {
			if _f.matchedRule == nil || _f.matchedRule(opt.EliminateAggDistinct1) {
				_expr := input
				if _f.appliedRule != nil {
					_f.appliedRule(opt.EliminateAggDistinct1, nil, _expr)
				}
				_f.constructorStackDepth--
				return _expr
			}
		}
	}

	// [EliminateAggDistinct2]
	{
		if input.Op() == opt.MinOp || input.Op() == opt.MaxOp {
			variable := input.Child(0).(*memo.VariableExpr)
			if _f.matchedRule == nil || _f.matchedRule(opt.EliminateAggDistinct2) {
				_expr := _f.DynamicConstruct(
					input.Op(),
					variable,
				).(opt.ScalarExpr)
				if _f.appliedRule != nil {
					_f.appliedRule(opt.EliminateAggDistinct2, nil, _expr)
				}
				_f.constructorStackDepth--
				return _expr
			}
		}
	}

	// [EliminateAggDistinct3]
	{
		_min, _ := input.(*memo.MinExpr)
		if _min != nil {
			variable := _min.Input
			if _f.matchedRule == nil || _f.matchedRule(opt.EliminateAggDistinct3) {
				_expr := _f.ConstructMin(
					variable,
				)
				if _f.appliedRule != nil {
					_f.appliedRule(opt.EliminateAggDistinct3, nil, _expr)
				}
				_f.constructorStackDepth--
				return _expr
			}
		}
	}

SKIP_RULES:
	e := _f.mem.MemoizeAggDistinct(input)
	expr := _f.onConstructScalar(e)
	_f.constructorStackDepth--
	return expr
}

// 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 call to the corresponding factory Construct methods. Here
// is example usage:
//
//	var replace func(e opt.Expr) opt.Expr
//	replace = func(e opt.Expr) opt.Expr {
//	  if e.Op() == opt.VariableOp {
//	    return getReplaceVar(e)
//	  }
//	  return factory.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 factory.Replace call to the top of the replace
// function rather than bottom.
func (f *Factory) Replace(e opt.Expr, replace ReplaceFunc) opt.Expr {
	opt.MaybeInjectOptimizerTestingPanic(f.ctx, f.evalCtx)
	switch t := e.(type) {
	case *memo.VariableExpr:
		return t

	case *memo.MinExpr:
		input := replace(t.Input).(*memo.VariableExpr)
		if input != t.Input {
			return f.ConstructMin(input)
		}
		return t

	case *memo.MaxExpr:
		input := replace(t.Input).(*memo.VariableExpr)
		if input != t.Input {
			return f.ConstructMax(input)
		}
		return t

	case *memo.AggDistinctExpr:
		input := replace(t.Input).(opt.ScalarExpr)
		if input != t.Input {
			return f.ConstructAggDistinct(input)
		}
		return t

	}
	panic(errors.AssertionFailedf("unhandled op %s", errors.Safe(e.Op())))
}

// 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) {
	opt.MaybeInjectOptimizerTestingPanic(f.ctx, f.evalCtx)
	switch t := src.(type) {
	case *memo.VariableExpr:
		return t

	case *memo.MinExpr:
		return f.ConstructMin(
			f.invokeReplace(t.Input, replace).(*memo.VariableExpr),
		)

	case *memo.MaxExpr:
		return f.ConstructMax(
			f.invokeReplace(t.Input, replace).(*memo.VariableExpr),
		)

	case *memo.AggDistinctExpr:
		return f.ConstructAggDistinct(
			f.invokeReplace(t.Input, replace).(opt.ScalarExpr),
		)

	default:
		panic(errors.AssertionFailedf("unhandled op %s", errors.Safe(src.Op())))
	}
}

// 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 {
	opt.MaybeInjectOptimizerTestingPanic(f.ctx, f.evalCtx)
	switch op {
	case opt.VariableOp:
		return f.ConstructVariable(
			*args[0].(*opt.ColumnID),
		)
	case opt.MinOp:
		return f.ConstructMin(
			args[0].(*memo.VariableExpr),
		)
	case opt.MaxOp:
		return f.ConstructMax(
			args[0].(*memo.VariableExpr),
		)
	case opt.AggDistinctOp:
		return f.ConstructAggDistinct(
			args[0].(opt.ScalarExpr),
		)
	}
	panic(errors.AssertionFailedf("cannot dynamically construct operator %s", errors.Safe(op)))
}
----
----

#
# Generate special code for WithBinding tag.
#
optgen factory test.opt
[Relational, WithBinding]
define With {
  Binding RelExpr
  Main    RelExpr
}
----
----
// Code generated by optgen; [omitted]

package norm

import (
	"github.com/cockroachdb/cockroach/pkg/sql/opt"
	"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
	"github.com/cockroachdb/cockroach/pkg/sql/opt/props"
	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
	"github.com/cockroachdb/cockroach/pkg/sql/types"
	"github.com/cockroachdb/errors"
)

// ConstructWith constructs an expression for the With operator.
func (_f *Factory) ConstructWith(
	binding memo.RelExpr,
	main memo.RelExpr,
) memo.RelExpr {
	opt.MaybeInjectOptimizerTestingPanic(_f.ctx, _f.evalCtx)
	_f.constructorStackDepth++
	if _f.constructorStackDepth > maxConstructorStackDepth {
		// If the constructor call stack depth exceeds the limit, call
		// onMaxConstructorStackDepthExceeded and skip all rules.
		_f.onMaxConstructorStackDepthExceeded()
		goto SKIP_RULES
	}

SKIP_RULES:
	e := _f.mem.MemoizeWith(binding, main)
	expr := _f.onConstructRelational(e)
	_f.constructorStackDepth--
	return expr
}

// 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 call to the corresponding factory Construct methods. Here
// is example usage:
//
//	var replace func(e opt.Expr) opt.Expr
//	replace = func(e opt.Expr) opt.Expr {
//	  if e.Op() == opt.VariableOp {
//	    return getReplaceVar(e)
//	  }
//	  return factory.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 factory.Replace call to the top of the replace
// function rather than bottom.
func (f *Factory) Replace(e opt.Expr, replace ReplaceFunc) opt.Expr {
	opt.MaybeInjectOptimizerTestingPanic(f.ctx, f.evalCtx)
	switch t := e.(type) {
	case *memo.WithExpr:
		binding := replace(t.Binding).(memo.RelExpr)
		main := replace(t.Main).(memo.RelExpr)
		if binding != t.Binding || main != t.Main {
			return f.ConstructWith(binding, main)
		}
		return t

	}
	panic(errors.AssertionFailedf("unhandled op %s", errors.Safe(e.Op())))
}

// 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) {
	opt.MaybeInjectOptimizerTestingPanic(f.ctx, f.evalCtx)
	switch t := src.(type) {
	case *memo.WithExpr:
		binding := f.invokeReplace(t.Binding, replace).(memo.RelExpr)
		if id := t.WithBindingID(); id != 0 {
			f.Metadata().AddWithBinding(id, binding)
		}
		return f.ConstructWith(
			binding,
			f.invokeReplace(t.Main, replace).(memo.RelExpr),
		)

	default:
		panic(errors.AssertionFailedf("unhandled op %s", errors.Safe(src.Op())))
	}
}

// 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 {
	opt.MaybeInjectOptimizerTestingPanic(f.ctx, f.evalCtx)
	switch op {
	case opt.WithOp:
		return f.ConstructWith(
			args[0].(memo.RelExpr),
			args[1].(memo.RelExpr),
		)
	}
	panic(errors.AssertionFailedf("cannot dynamically construct operator %s", errors.Safe(op)))
}
----
----

#
# Use strictly-typed variable aliases when a variable is bound to a single type
# of expression.
#
optgen factory test.opt
[Relational]
define Project {
    Input RelExpr
    Projections ProjectionsExpr
    Passthrough ColSet
}

[Scalar, List]
define Projections {
}

define Values {
    Rows ScalarListExpr
}

[Scalar, List]
define ScalarList {
}

[PruneValuesCols, Normalize]
(Project
    $input:(Values)
    $projections:*
    $passthrough:* &
        (CanPruneCols
            $input
            $needed:(UnionCols
                (ProjectionOuterCols $projections)
                $passthrough
            )
        )
)
=>
(Project (PruneCols $input $needed) $projections $passthrough)
----
----
// Code generated by optgen; [omitted]

package norm

import (
	"github.com/cockroachdb/cockroach/pkg/sql/opt"
	"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
	"github.com/cockroachdb/cockroach/pkg/sql/opt/props"
	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
	"github.com/cockroachdb/cockroach/pkg/sql/types"
	"github.com/cockroachdb/errors"
)

// ConstructProject constructs an expression for the Project operator.
func (_f *Factory) ConstructProject(
	input memo.RelExpr,
	projections memo.ProjectionsExpr,
	passthrough opt.ColSet,
) memo.RelExpr {
	opt.MaybeInjectOptimizerTestingPanic(_f.ctx, _f.evalCtx)
	_f.constructorStackDepth++
	if _f.constructorStackDepth > maxConstructorStackDepth {
		// If the constructor call stack depth exceeds the limit, call
		// onMaxConstructorStackDepthExceeded and skip all rules.
		_f.onMaxConstructorStackDepthExceeded()
		goto SKIP_RULES
	}

	// [PruneValuesCols]
	{
		_values, _ := input.(*memo.ValuesExpr)
		if _values != nil {
			needed := _f.funcs.UnionCols(_f.funcs.ProjectionOuterCols(projections), passthrough)
			if _f.funcs.CanPruneCols(_values, needed) {
				if _f.matchedRule == nil || _f.matchedRule(opt.PruneValuesCols) {
					_expr := _f.ConstructProject(
						_f.funcs.PruneCols(_values, needed),
						projections,
						passthrough,
					)
					if _f.appliedRule != nil {
						_f.appliedRule(opt.PruneValuesCols, nil, _expr)
					}
					_f.constructorStackDepth--
					return _expr
				}
			}
		}
	}

SKIP_RULES:
	e := _f.mem.MemoizeProject(input, projections, passthrough)
	expr := _f.onConstructRelational(e)
	_f.constructorStackDepth--
	return expr
}

// ConstructValues constructs an expression for the Values operator.
func (_f *Factory) ConstructValues(
	rows memo.ScalarListExpr,
) opt.ScalarExpr {
	opt.MaybeInjectOptimizerTestingPanic(_f.ctx, _f.evalCtx)
	_f.constructorStackDepth++
	if _f.constructorStackDepth > maxConstructorStackDepth {
		// If the constructor call stack depth exceeds the limit, call
		// onMaxConstructorStackDepthExceeded and skip all rules.
		_f.onMaxConstructorStackDepthExceeded()
		goto SKIP_RULES
	}

SKIP_RULES:
	e := _f.mem.MemoizeValues(rows)
	expr := _f.onConstructScalar(e)
	_f.constructorStackDepth--
	return expr
}

// 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 call to the corresponding factory Construct methods. Here
// is example usage:
//
//	var replace func(e opt.Expr) opt.Expr
//	replace = func(e opt.Expr) opt.Expr {
//	  if e.Op() == opt.VariableOp {
//	    return getReplaceVar(e)
//	  }
//	  return factory.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 factory.Replace call to the top of the replace
// function rather than bottom.
func (f *Factory) Replace(e opt.Expr, replace ReplaceFunc) opt.Expr {
	opt.MaybeInjectOptimizerTestingPanic(f.ctx, f.evalCtx)
	switch t := e.(type) {
	case *memo.ProjectExpr:
		input := replace(t.Input).(memo.RelExpr)
		projections, projectionsChanged := f.replaceProjectionsExpr(t.Projections, replace)
		if input != t.Input || projectionsChanged {
			return f.ConstructProject(input, projections, t.Passthrough)
		}
		return t

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

	case *memo.ValuesExpr:
		rows, rowsChanged := f.replaceScalarListExpr(t.Rows, replace)
		if rowsChanged {
			return f.ConstructValues(rows)
		}
		return t

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

	}
	panic(errors.AssertionFailedf("unhandled op %s", errors.Safe(e.Op())))
}

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

func (f *Factory) replaceScalarListExpr(list memo.ScalarListExpr, replace ReplaceFunc) (_ memo.ScalarListExpr, changed bool) {
	var newList []opt.ScalarExpr
	for i := range list {
		before := list[i]
		after := replace(before).(opt.ScalarExpr)
		if before != after {
			if newList == nil {
				newList = make([]opt.ScalarExpr, len(list))
				copy(newList, list[:i])
			}
			newList[i] = 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) {
	opt.MaybeInjectOptimizerTestingPanic(f.ctx, f.evalCtx)
	switch t := src.(type) {
	case *memo.ProjectExpr:
		return f.ConstructProject(
			f.invokeReplace(t.Input, replace).(memo.RelExpr),
			f.copyAndReplaceDefaultProjectionsExpr(t.Projections, replace),
			t.Passthrough,
		)

	case *memo.ValuesExpr:
		return f.ConstructValues(
			f.copyAndReplaceDefaultScalarListExpr(t.Rows, replace),
		)

	case *memo.ProjectionsExpr:
		newVal := f.copyAndReplaceDefaultProjectionsExpr(*t, replace)
		return &newVal

	case *memo.ScalarListExpr:
		newVal := f.copyAndReplaceDefaultScalarListExpr(*t, replace)
		return &newVal

	default:
		panic(errors.AssertionFailedf("unhandled op %s", errors.Safe(src.Op())))
	}
}

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

func (f *Factory) copyAndReplaceDefaultScalarListExpr(src memo.ScalarListExpr, replace ReplaceFunc) (dst memo.ScalarListExpr) {
	dst = make(memo.ScalarListExpr, len(src))
	for i := range src {
		dst[i] = f.invokeReplace(src[i], 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 {
	opt.MaybeInjectOptimizerTestingPanic(f.ctx, f.evalCtx)
	switch op {
	case opt.ProjectOp:
		return f.ConstructProject(
			args[0].(memo.RelExpr),
			*args[1].(*memo.ProjectionsExpr),
			*args[2].(*opt.ColSet),
		)
	case opt.ValuesOp:
		return f.ConstructValues(
			*args[0].(*memo.ScalarListExpr),
		)
	}
	panic(errors.AssertionFailedf("cannot dynamically construct operator %s", errors.Safe(op)))
}
----
----

#
# Do not use strictly-typed variable aliases when a variable is bound to a
# not (^) optgen expression.
#
optgen factory test.opt
[Scalar, Bool]
define And {
    Left ScalarExpr
    Right ScalarExpr
}

[Scalar, Bool]
define Range {
    And ScalarExpr
}

[SimplifyRange, Normalize]
(Range $input:^(And))
=>
$input
----
----
// Code generated by optgen; [omitted]

package norm

import (
	"github.com/cockroachdb/cockroach/pkg/sql/opt"
	"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
	"github.com/cockroachdb/cockroach/pkg/sql/opt/props"
	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
	"github.com/cockroachdb/cockroach/pkg/sql/types"
	"github.com/cockroachdb/errors"
)

// ConstructAnd constructs an expression for the And operator.
func (_f *Factory) ConstructAnd(
	left opt.ScalarExpr,
	right opt.ScalarExpr,
) opt.ScalarExpr {
	opt.MaybeInjectOptimizerTestingPanic(_f.ctx, _f.evalCtx)
	_f.constructorStackDepth++
	if _f.constructorStackDepth > maxConstructorStackDepth {
		// If the constructor call stack depth exceeds the limit, call
		// onMaxConstructorStackDepthExceeded and skip all rules.
		_f.onMaxConstructorStackDepthExceeded()
		goto SKIP_RULES
	}

SKIP_RULES:
	e := _f.mem.MemoizeAnd(left, right)
	expr := _f.onConstructScalar(e)
	_f.constructorStackDepth--
	return expr
}

// ConstructRange constructs an expression for the Range operator.
func (_f *Factory) ConstructRange(
	and opt.ScalarExpr,
) opt.ScalarExpr {
	opt.MaybeInjectOptimizerTestingPanic(_f.ctx, _f.evalCtx)
	_f.constructorStackDepth++
	if _f.constructorStackDepth > maxConstructorStackDepth {
		// If the constructor call stack depth exceeds the limit, call
		// onMaxConstructorStackDepthExceeded and skip all rules.
		_f.onMaxConstructorStackDepthExceeded()
		goto SKIP_RULES
	}

	// [SimplifyRange]
	{
		input := and
		_and, _ := input.(*memo.AndExpr)
		if _and == nil {
			if _f.matchedRule == nil || _f.matchedRule(opt.SimplifyRange) {
				_expr := input
				if _f.appliedRule != nil {
					_f.appliedRule(opt.SimplifyRange, nil, _expr)
				}
				_f.constructorStackDepth--
				return _expr
			}
		}
	}

SKIP_RULES:
	e := _f.mem.MemoizeRange(and)
	expr := _f.onConstructScalar(e)
	_f.constructorStackDepth--
	return expr
}

// 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 call to the corresponding factory Construct methods. Here
// is example usage:
//
//	var replace func(e opt.Expr) opt.Expr
//	replace = func(e opt.Expr) opt.Expr {
//	  if e.Op() == opt.VariableOp {
//	    return getReplaceVar(e)
//	  }
//	  return factory.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 factory.Replace call to the top of the replace
// function rather than bottom.
func (f *Factory) Replace(e opt.Expr, replace ReplaceFunc) opt.Expr {
	opt.MaybeInjectOptimizerTestingPanic(f.ctx, f.evalCtx)
	switch t := e.(type) {
	case *memo.AndExpr:
		left := replace(t.Left).(opt.ScalarExpr)
		right := replace(t.Right).(opt.ScalarExpr)
		if left != t.Left || right != t.Right {
			return f.ConstructAnd(left, right)
		}
		return t

	case *memo.RangeExpr:
		and := replace(t.And).(opt.ScalarExpr)
		if and != t.And {
			return f.ConstructRange(and)
		}
		return t

	}
	panic(errors.AssertionFailedf("unhandled op %s", errors.Safe(e.Op())))
}

// 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) {
	opt.MaybeInjectOptimizerTestingPanic(f.ctx, f.evalCtx)
	switch t := src.(type) {
	case *memo.AndExpr:
		return f.ConstructAnd(
			f.invokeReplace(t.Left, replace).(opt.ScalarExpr),
			f.invokeReplace(t.Right, replace).(opt.ScalarExpr),
		)

	case *memo.RangeExpr:
		return f.ConstructRange(
			f.invokeReplace(t.And, replace).(opt.ScalarExpr),
		)

	default:
		panic(errors.AssertionFailedf("unhandled op %s", errors.Safe(src.Op())))
	}
}

// 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 {
	opt.MaybeInjectOptimizerTestingPanic(f.ctx, f.evalCtx)
	switch op {
	case opt.AndOp:
		return f.ConstructAnd(
			args[0].(opt.ScalarExpr),
			args[1].(opt.ScalarExpr),
		)
	case opt.RangeOp:
		return f.ConstructRange(
			args[0].(opt.ScalarExpr),
		)
	}
	panic(errors.AssertionFailedf("cannot dynamically construct operator %s", errors.Safe(op)))
}
----
----

#
# Generate code for let expressions.
#
optgen factory test.opt
[Relational]
define Union {
    Left  RelExpr
    Right RelExpr
}

[Let, Normalize]
(Union
    $left:*
    $right:* &
      (Let ($a $b $ok):(Split $left $right) $ok)
)
=>
(Union $a $b)

[LetNestedInFunction, Normalize]
(Union
    $left:*
    $right:* &
      (IsOk (New (Let ($a $b):(Split $left $right) $a))) &
      (IsOk $b)
)
=>
(Union $a $b)

[LetNestedInFunctionWithBinding, Normalize]
(Union
    $left:*
    $right:* &
      (IsOK $newA:(New (Let ($a $b):(Split $left $right) $a))) &
      (IsOk $b)
)
=>
(Union $newA $b)

[LetInNestedMatch, Normalize]
(Union
    $left:(Union
        $innerLeft:*
        $innerRight:* &
            (Let ($a $b $ok):(Split $innerLeft $innerRight) $ok)
    )
    $right:* &
        (IsValid $a $b)
)
=>
(Union (Union $a $b) $right)

[LetNestedInFunctionAndLet, Normalize]
(Union
    $left:*
    $right:* &
        (Let ($a $b):(Split
            (Let ($newLeft):(New $left) $newLeft)
        ) $a) &
        (IsOk $b)
)
=>
(Union $a $b)

[LetInReplace, Normalize]
(Union $left:* $right:*)
=>
(Union
    (Let ($a $b):(Split $left $right) $a)
    $b
)
----
----
// Code generated by optgen; [omitted]

package norm

import (
	"github.com/cockroachdb/cockroach/pkg/sql/opt"
	"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
	"github.com/cockroachdb/cockroach/pkg/sql/opt/props"
	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
	"github.com/cockroachdb/cockroach/pkg/sql/types"
	"github.com/cockroachdb/errors"
)

// ConstructUnion constructs an expression for the Union operator.
func (_f *Factory) ConstructUnion(
	left memo.RelExpr,
	right memo.RelExpr,
) memo.RelExpr {
	opt.MaybeInjectOptimizerTestingPanic(_f.ctx, _f.evalCtx)
	_f.constructorStackDepth++
	if _f.constructorStackDepth > maxConstructorStackDepth {
		// If the constructor call stack depth exceeds the limit, call
		// onMaxConstructorStackDepthExceeded and skip all rules.
		_f.onMaxConstructorStackDepthExceeded()
		goto SKIP_RULES
	}

	// [Let]
	{
		a, b, ok := _f.funcs.Split(left, right)
		if ok {
			if _f.matchedRule == nil || _f.matchedRule(opt.Let) {
				_expr := _f.ConstructUnion(
					a,
					b,
				)
				if _f.appliedRule != nil {
					_f.appliedRule(opt.Let, nil, _expr)
				}
				_f.constructorStackDepth--
				return _expr
			}
		}
	}

	// [LetNestedInFunction]
	{
		a, b := _f.funcs.Split(left, right)
		if _f.funcs.IsOk(_f.funcs.New(a)) {
			if _f.funcs.IsOk(b) {
				if _f.matchedRule == nil || _f.matchedRule(opt.LetNestedInFunction) {
					_expr := _f.ConstructUnion(
						a,
						b,
					)
					if _f.appliedRule != nil {
						_f.appliedRule(opt.LetNestedInFunction, nil, _expr)
					}
					_f.constructorStackDepth--
					return _expr
				}
			}
		}
	}

	// [LetNestedInFunctionWithBinding]
	{
		a, b := _f.funcs.Split(left, right)
		newA := _f.funcs.New(a)
		if _f.funcs.IsOK(newA) {
			if _f.funcs.IsOk(b) {
				if _f.matchedRule == nil || _f.matchedRule(opt.LetNestedInFunctionWithBinding) {
					_expr := _f.ConstructUnion(
						newA,
						b,
					)
					if _f.appliedRule != nil {
						_f.appliedRule(opt.LetNestedInFunctionWithBinding, nil, _expr)
					}
					_f.constructorStackDepth--
					return _expr
				}
			}
		}
	}

	// [LetInNestedMatch]
	{
		_union, _ := left.(*memo.UnionExpr)
		if _union != nil {
			innerLeft := _union.Left
			innerRight := _union.Right
			a, b, ok := _f.funcs.Split(innerLeft, innerRight)
			if ok {
				if _f.funcs.IsValid(a, b) {
					if _f.matchedRule == nil || _f.matchedRule(opt.LetInNestedMatch) {
						_expr := _f.ConstructUnion(
							_f.ConstructUnion(
								a,
								b,
							),
							right,
						)
						if _f.appliedRule != nil {
							_f.appliedRule(opt.LetInNestedMatch, nil, _expr)
						}
						_f.constructorStackDepth--
						return _expr
					}
				}
			}
		}
	}

	// [LetNestedInFunctionAndLet]
	{
		newLeft := _f.funcs.New(left)
		a, b := _f.funcs.Split(newLeft)
		if a {
			if _f.funcs.IsOk(b) {
				if _f.matchedRule == nil || _f.matchedRule(opt.LetNestedInFunctionAndLet) {
					_expr := _f.ConstructUnion(
						a,
						b,
					)
					if _f.appliedRule != nil {
						_f.appliedRule(opt.LetNestedInFunctionAndLet, nil, _expr)
					}
					_f.constructorStackDepth--
					return _expr
				}
			}
		}
	}

	// [LetInReplace]
	{
		if _f.matchedRule == nil || _f.matchedRule(opt.LetInReplace) {
			a, b := _f.funcs.Split(left, right)
			_expr := _f.ConstructUnion(
				a,
				b,
			)
			if _f.appliedRule != nil {
				_f.appliedRule(opt.LetInReplace, nil, _expr)
			}
			_f.constructorStackDepth--
			return _expr
		}
	}

SKIP_RULES:
	e := _f.mem.MemoizeUnion(left, right)
	expr := _f.onConstructRelational(e)
	_f.constructorStackDepth--
	return expr
}

// 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 call to the corresponding factory Construct methods. Here
// is example usage:
//
//	var replace func(e opt.Expr) opt.Expr
//	replace = func(e opt.Expr) opt.Expr {
//	  if e.Op() == opt.VariableOp {
//	    return getReplaceVar(e)
//	  }
//	  return factory.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 factory.Replace call to the top of the replace
// function rather than bottom.
func (f *Factory) Replace(e opt.Expr, replace ReplaceFunc) opt.Expr {
	opt.MaybeInjectOptimizerTestingPanic(f.ctx, f.evalCtx)
	switch t := e.(type) {
	case *memo.UnionExpr:
		left := replace(t.Left).(memo.RelExpr)
		right := replace(t.Right).(memo.RelExpr)
		if left != t.Left || right != t.Right {
			return f.ConstructUnion(left, right)
		}
		return t

	}
	panic(errors.AssertionFailedf("unhandled op %s", errors.Safe(e.Op())))
}

// 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) {
	opt.MaybeInjectOptimizerTestingPanic(f.ctx, f.evalCtx)
	switch t := src.(type) {
	case *memo.UnionExpr:
		return f.ConstructUnion(
			f.invokeReplace(t.Left, replace).(memo.RelExpr),
			f.invokeReplace(t.Right, replace).(memo.RelExpr),
		)

	default:
		panic(errors.AssertionFailedf("unhandled op %s", errors.Safe(src.Op())))
	}
}

// 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 {
	opt.MaybeInjectOptimizerTestingPanic(f.ctx, f.evalCtx)
	switch op {
	case opt.UnionOp:
		return f.ConstructUnion(
			args[0].(memo.RelExpr),
			args[1].(memo.RelExpr),
		)
	}
	panic(errors.AssertionFailedf("cannot dynamically construct operator %s", errors.Safe(op)))
}
----
----
