#
# 1. One level of match patterns
# 2. Two levels of match patterns
# 3. Three levels of match patterns
# 4. Scalar match pattern
# 5. Custom match and replace functions
# 6. Dynamic name matching
# 7. Multiple patterns for a rule
# 8. Negated expressions
# 9. Custom replace function at top-level
# 10. Generate match for all list match operators
# 11. Generate no match for all list match operators that support it
#
optgen explorer test.opt
[Join]
define InnerJoin {
    Left  Expr
    Right Expr
    On    Expr
}

[Join]
define InnerJoinApply {
    Left  Expr
    Right Expr
    On    Expr
}

define Select {
    Input  Expr
    Filter Expr
}

define GroupBy {
    Input        Expr
    Aggregations Expr
    GroupingCols ColSet
}

define Scan {
    Def ScanOpDef
}

[Scalar, HasConditions]
define Filters {
    Conditions ExprList
}

[Scalar, HasConditions]
define And {
    Conditions ExprList
}

define List {
    Items ExprList
}

define Op {
    Empty  Expr
    Single Expr
}

[CommuteJoin, Explore]
(InnerJoin $r:* $s:*) => (InnerJoin $s $r)

[AssociateJoin, Explore]
(InnerJoin
    (InnerJoin $r:* $s:* (Filters|HasConditions|And $lowerConditions:*))
    $t:*
    $filters:(Filters $upperConditions:*) & (IsCorrelated $filters $r) & (IsCorrelated $filters $t)
)
=>
(InnerJoin
    (InnerJoin
        $r
        $t
        (Filters (ConstructConditionsNotUsing $s $lowerConditions $upperConditions))
    )
    $s
    (Filters (ConstructConditionsUsing $s $lowerConditions $upperConditions))
)

[PushDownGroupBy, Explore]
(Select
    (GroupBy
        $input:(InnerJoin|Join $left:* $right:* $on:*)
        $aggregations:*
        $def:*
    )
    $filter:* & ^(IsCorrelated $filter $right)
)
=>
(InnerJoin
    (Select
        (GroupBy
            $left
            $aggregations
            $def
        )
        $filter
    )
    $right
    $on
)

[GenerateIndexScans, Explore]
(Scan $def:* & (IsPrimaryScan $def)) => (GenerateIndexScans $def)

[List, Explore]
(List $any:[
    ...
    (List $first:[
        (List $last:[
            ...
            (List $single:[
                (List $empty:[])
            ])
        ])
        ...
    ])
    ...
])
=>
(Construct $any $first $last $single $empty)

[ListNot, Explore]
(Op
    $empty:(List ^[])
    $single:(List ^[ * ])
)
=>
(Op $empty $single)
----
----
// Code generated by optgen; [omitted]

package xform

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

func (_e *explorer) exploreExpr(_state *exploreState, _eid memo.ExprID) (_fullyExplored bool) {
	_expr := _e.mem.Expr(_eid)
	switch _expr.Operator() {
	case opt.InnerJoinOp:
		return _e.exploreInnerJoin(_state, _eid)
	case opt.SelectOp:
		return _e.exploreSelect(_state, _eid)
	case opt.ScanOp:
		return _e.exploreScan(_state, _eid)
	case opt.ListOp:
		return _e.exploreList(_state, _eid)
	case opt.OpOp:
		return _e.exploreOp(_state, _eid)
	}

	// No rules for other operator types.
	return true
}

func (_e *explorer) exploreInnerJoin(_rootState *exploreState, _root memo.ExprID) (_fullyExplored bool) {
	_rootExpr := _e.mem.Expr(_root).AsInnerJoin()
	_fullyExplored = true

	// [CommuteJoin]
	{
		if _root.Expr >= _rootState.start {
			r := _rootExpr.Left()
			s := _rootExpr.Right()
			if _e.o.matchedRule == nil || _e.o.matchedRule(opt.CommuteJoin) {
				_expr := memo.MakeInnerJoinExpr(
					s,
					r,
				)
				_before := _e.mem.ExprCount(_root.Group)
				_e.mem.MemoizeDenormExpr(_e.evalCtx, _root.Group, memo.Expr(_expr))
				if _e.o.appliedRule != nil {
					_after := _e.mem.ExprCount(_root.Group)
					_e.o.appliedRule(opt.CommuteJoin, _root.Group, _root.Expr, _after-_before)
				}
			}
		}
	}

	// [AssociateJoin]
	{
		_partlyExplored := _root.Expr < _rootState.start
		_state := _e.exploreGroup(_rootExpr.Left())
		if !_state.fullyExplored {
			_fullyExplored = false
		}
		start := memo.ExprOrdinal(0)
		if _partlyExplored {
			start = _state.start
		}
		for _ord := start; _ord < _state.end; _ord++ {
			_eid := memo.ExprID{Group: _rootExpr.Left(), Expr: _ord}
			_innerJoinExpr := _e.mem.Expr(_eid).AsInnerJoin()
			if _innerJoinExpr != nil {
				r := _innerJoinExpr.Left()
				s := _innerJoinExpr.Right()
				_eid := memo.MakeNormExprID(_innerJoinExpr.On())
				_expr := _e.mem.Expr(_eid)
				if _expr.Operator() == opt.FiltersOp || _expr.IsHasConditions() || _expr.Operator() == opt.AndOp {
					lowerConditions := _expr.ChildGroup(_e.Memo(), 0)
					t := _rootExpr.Right()
					filters := _rootExpr.On()
					_eid := memo.MakeNormExprID(_rootExpr.On())
					_filtersExpr := _e.mem.Expr(_eid).AsFilters()
					if _filtersExpr != nil {
						upperConditions := _filtersExpr.Conditions()
						if _e.funcs.IsCorrelated(filters, r) {
							if _e.funcs.IsCorrelated(filters, t) {
								if _e.o.matchedRule == nil || _e.o.matchedRule(opt.AssociateJoin) {
									_expr := memo.MakeInnerJoinExpr(
										_e.f.ConstructInnerJoin(
											r,
											t,
											_e.f.ConstructFilters(
												_e.funcs.ConstructConditionsNotUsing(s, lowerConditions, upperConditions),
											),
										),
										s,
										_e.f.ConstructFilters(
											_e.funcs.ConstructConditionsUsing(s, lowerConditions, upperConditions),
										),
									)
									_before := _e.mem.ExprCount(_root.Group)
									_e.mem.MemoizeDenormExpr(_e.evalCtx, _root.Group, memo.Expr(_expr))
									if _e.o.appliedRule != nil {
										_after := _e.mem.ExprCount(_root.Group)
										_e.o.appliedRule(opt.AssociateJoin, _root.Group, _root.Expr, _after-_before)
									}
								}
							}
						}
					}
				}
			}
		}
	}

	return _fullyExplored
}

func (_e *explorer) exploreSelect(_rootState *exploreState, _root memo.ExprID) (_fullyExplored bool) {
	_rootExpr := _e.mem.Expr(_root).AsSelect()
	_fullyExplored = true

	// [PushDownGroupBy]
	{
		_partlyExplored := _root.Expr < _rootState.start
		_state := _e.exploreGroup(_rootExpr.Input())
		if !_state.fullyExplored {
			_fullyExplored = false
		}
		for _ord := memo.ExprOrdinal(0); _ord < _state.end; _ord++ {
			_partlyExplored := _partlyExplored && _ord < _state.start
			_eid := memo.ExprID{Group: _rootExpr.Input(), Expr: _ord}
			_groupByExpr := _e.mem.Expr(_eid).AsGroupBy()
			if _groupByExpr != nil {
				input := _groupByExpr.Input()
				_state := _e.exploreGroup(_groupByExpr.Input())
				if !_state.fullyExplored {
					_fullyExplored = false
				}
				start := memo.ExprOrdinal(0)
				if _partlyExplored {
					start = _state.start
				}
				for _ord := start; _ord < _state.end; _ord++ {
					_eid := memo.ExprID{Group: _groupByExpr.Input(), Expr: _ord}
					_expr := _e.mem.Expr(_eid)
					if _expr.Operator() == opt.InnerJoinOp || _expr.IsJoin() {
						left := _expr.ChildGroup(_e.Memo(), 0)
						right := _expr.ChildGroup(_e.Memo(), 1)
						on := _expr.ChildGroup(_e.Memo(), 2)
						aggregations := _groupByExpr.Aggregations()
						def := _groupByExpr.GroupingCols()
						filter := _rootExpr.Filter()
						if !_e.funcs.IsCorrelated(filter, right) {
							if _e.o.matchedRule == nil || _e.o.matchedRule(opt.PushDownGroupBy) {
								_expr := memo.MakeInnerJoinExpr(
									_e.f.ConstructSelect(
										_e.f.ConstructGroupBy(
											left,
											aggregations,
											def,
										),
										filter,
									),
									right,
									on,
								)
								_before := _e.mem.ExprCount(_root.Group)
								_e.mem.MemoizeDenormExpr(_e.evalCtx, _root.Group, memo.Expr(_expr))
								if _e.o.appliedRule != nil {
									_after := _e.mem.ExprCount(_root.Group)
									_e.o.appliedRule(opt.PushDownGroupBy, _root.Group, _root.Expr, _after-_before)
								}
							}
						}
					}
				}
			}
		}
	}

	return _fullyExplored
}

func (_e *explorer) exploreScan(_rootState *exploreState, _root memo.ExprID) (_fullyExplored bool) {
	_rootExpr := _e.mem.Expr(_root).AsScan()
	_fullyExplored = true

	// [GenerateIndexScans]
	{
		if _root.Expr >= _rootState.start {
			def := _rootExpr.Def()
			if _e.funcs.IsPrimaryScan(def) {
				if _e.o.matchedRule == nil || _e.o.matchedRule(opt.GenerateIndexScans) {
					_exprs := _e.funcs.GenerateIndexScans(def)
					_before := _e.mem.ExprCount(_root.Group)
					for i := range _exprs {
						_e.mem.MemoizeDenormExpr(_e.evalCtx, _root.Group, _exprs[i])
					}
					if _e.o.appliedRule != nil {
						_after := _e.mem.ExprCount(_root.Group)
						_e.o.appliedRule(opt.GenerateIndexScans, _root.Group, _root.Expr, _after-_before)
					}
				}
			}
		}
	}

	return _fullyExplored
}

func (_e *explorer) exploreList(_rootState *exploreState, _root memo.ExprID) (_fullyExplored bool) {
	_rootExpr := _e.mem.Expr(_root).AsList()
	_fullyExplored = true

	// [List]
	{
		_partlyExplored := _root.Expr < _rootState.start
		any := _rootExpr.Items()
		for _, _item := range _e.mem.LookupList(_rootExpr.Items()) {
			_state := _e.exploreGroup(_item)
			if !_state.fullyExplored {
				_fullyExplored = false
			}
			for _ord := memo.ExprOrdinal(0); _ord < _state.end; _ord++ {
				_partlyExplored := _partlyExplored && _ord < _state.start
				_eid := memo.ExprID{Group: _item, Expr: _ord}
				_listExpr := _e.mem.Expr(_eid).AsList()
				if _listExpr != nil {
					first := _listExpr.Items()
					if _listExpr.Items().Length > 0 {
						_item := _e.mem.LookupList(_listExpr.Items())[0]
						_state := _e.exploreGroup(_item)
						if !_state.fullyExplored {
							_fullyExplored = false
						}
						for _ord := memo.ExprOrdinal(0); _ord < _state.end; _ord++ {
							_partlyExplored := _partlyExplored && _ord < _state.start
							_eid := memo.ExprID{Group: _item, Expr: _ord}
							_listExpr2 := _e.mem.Expr(_eid).AsList()
							if _listExpr2 != nil {
								last := _listExpr2.Items()
								if _listExpr2.Items().Length > 0 {
									_item := _e.mem.LookupList(_listExpr2.Items())[_listExpr2.Items().Length-1]
									_state := _e.exploreGroup(_item)
									if !_state.fullyExplored {
										_fullyExplored = false
									}
									for _ord := memo.ExprOrdinal(0); _ord < _state.end; _ord++ {
										_partlyExplored := _partlyExplored && _ord < _state.start
										_eid := memo.ExprID{Group: _item, Expr: _ord}
										_listExpr3 := _e.mem.Expr(_eid).AsList()
										if _listExpr3 != nil {
											single := _listExpr3.Items()
											if _listExpr3.Items().Length == 1 {
												_item := _e.mem.LookupList(_listExpr3.Items())[0]
												_state := _e.exploreGroup(_item)
												if !_state.fullyExplored {
													_fullyExplored = false
												}
												start := memo.ExprOrdinal(0)
												if _partlyExplored {
													start = _state.start
												}
												for _ord := start; _ord < _state.end; _ord++ {
													_eid := memo.ExprID{Group: _item, Expr: _ord}
													_listExpr4 := _e.mem.Expr(_eid).AsList()
													if _listExpr4 != nil {
														empty := _listExpr4.Items()
														if _listExpr4.Items().Length == 0 {
															if _e.o.matchedRule == nil || _e.o.matchedRule(opt.List) {
																_exprs := _e.funcs.Construct(any, first, last, single, empty)
																_before := _e.mem.ExprCount(_root.Group)
																for i := range _exprs {
																	_e.mem.MemoizeDenormExpr(_e.evalCtx, _root.Group, _exprs[i])
																}
																if _e.o.appliedRule != nil {
																	_after := _e.mem.ExprCount(_root.Group)
																	_e.o.appliedRule(opt.List, _root.Group, _root.Expr, _after-_before)
																}
															}
														}
													}
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}

	return _fullyExplored
}

func (_e *explorer) exploreOp(_rootState *exploreState, _root memo.ExprID) (_fullyExplored bool) {
	_rootExpr := _e.mem.Expr(_root).AsOp()
	_fullyExplored = true

	// [ListNot]
	{
		_partlyExplored := _root.Expr < _rootState.start
		empty := _rootExpr.Empty()
		_state := _e.exploreGroup(_rootExpr.Empty())
		if !_state.fullyExplored {
			_fullyExplored = false
		}
		for _ord := memo.ExprOrdinal(0); _ord < _state.end; _ord++ {
			_partlyExplored := _partlyExplored && _ord < _state.start
			_eid := memo.ExprID{Group: _rootExpr.Empty(), Expr: _ord}
			_listExpr := _e.mem.Expr(_eid).AsList()
			if _listExpr != nil {
				if _listExpr.Items().Length != 0 {
					single := _rootExpr.Single()
					_state := _e.exploreGroup(_rootExpr.Single())
					if !_state.fullyExplored {
						_fullyExplored = false
					}
					start := memo.ExprOrdinal(0)
					if _partlyExplored {
						start = _state.start
					}
					for _ord := start; _ord < _state.end; _ord++ {
						_eid := memo.ExprID{Group: _rootExpr.Single(), Expr: _ord}
						_listExpr2 := _e.mem.Expr(_eid).AsList()
						if _listExpr2 != nil {
							if _listExpr2.Items().Length != 1 {
								if _e.o.matchedRule == nil || _e.o.matchedRule(opt.ListNot) {
									_expr := memo.MakeOpExpr(
										empty,
										single,
									)
									_before := _e.mem.ExprCount(_root.Group)
									_e.mem.MemoizeDenormExpr(_e.evalCtx, _root.Group, memo.Expr(_expr))
									if _e.o.appliedRule != nil {
										_after := _e.mem.ExprCount(_root.Group)
										_e.o.appliedRule(opt.ListNot, _root.Group, _root.Expr, _after-_before)
									}
								}
							}
						}
					}
				}
			}
		}
	}

	return _fullyExplored
}
----
----

#
# Bind variables in replace pattern.
#
optgen explorer test.opt
[Join]
define InnerJoin {
    Left  Expr
    Right Expr
    On    Expr
}

[BindVarsOp, Explore]
(InnerJoin $left:* $right:* $on:*)
=>
(InnerJoin
    $varname:(SomeFunc $left)
    $varname2:(Select $varname (SomeOtherFunc $right))
    (MakeOn $varname $varname2)
)

[BindVarsCustom, Explore]
(InnerJoin $left:* $right:* $on:*)
=>
(TopLevelFunc
    $varname:(SomeFunc $left)
    $varname2:(Select $varname (SomeOtherFunc $right))
    (MakeOn $varname $varname2)
)
----
----
// Code generated by optgen; [omitted]

package xform

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

func (_e *explorer) exploreExpr(_state *exploreState, _eid memo.ExprID) (_fullyExplored bool) {
	_expr := _e.mem.Expr(_eid)
	switch _expr.Operator() {
	case opt.InnerJoinOp:
		return _e.exploreInnerJoin(_state, _eid)
	}

	// No rules for other operator types.
	return true
}

func (_e *explorer) exploreInnerJoin(_rootState *exploreState, _root memo.ExprID) (_fullyExplored bool) {
	_rootExpr := _e.mem.Expr(_root).AsInnerJoin()
	_fullyExplored = true

	// [BindVarsOp]
	{
		if _root.Expr >= _rootState.start {
			left := _rootExpr.Left()
			right := _rootExpr.Right()
			on := _rootExpr.On()
			if _e.o.matchedRule == nil || _e.o.matchedRule(opt.BindVarsOp) {
				varname := _e.funcs.SomeFunc(left)
				varname2 := _e.funcs.Select(varname, _e.funcs.SomeOtherFunc(right))
				_expr := memo.MakeInnerJoinExpr(
					varname,
					varname2,
					_e.funcs.MakeOn(varname, varname2),
				)
				_before := _e.mem.ExprCount(_root.Group)
				_e.mem.MemoizeDenormExpr(_e.evalCtx, _root.Group, memo.Expr(_expr))
				if _e.o.appliedRule != nil {
					_after := _e.mem.ExprCount(_root.Group)
					_e.o.appliedRule(opt.BindVarsOp, _root.Group, _root.Expr, _after-_before)
				}
			}
		}
	}

	// [BindVarsCustom]
	{
		if _root.Expr >= _rootState.start {
			left := _rootExpr.Left()
			right := _rootExpr.Right()
			on := _rootExpr.On()
			if _e.o.matchedRule == nil || _e.o.matchedRule(opt.BindVarsCustom) {
				varname := _e.funcs.SomeFunc(left)
				varname2 := _e.funcs.Select(varname, _e.funcs.SomeOtherFunc(right))
				_exprs := _e.funcs.TopLevelFunc(varname, varname2, _e.funcs.MakeOn(varname, varname2))
				_before := _e.mem.ExprCount(_root.Group)
				for i := range _exprs {
					_e.mem.MemoizeDenormExpr(_e.evalCtx, _root.Group, _exprs[i])
				}
				if _e.o.appliedRule != nil {
					_after := _e.mem.ExprCount(_root.Group)
					_e.o.appliedRule(opt.BindVarsCustom, _root.Group, _root.Expr, _after-_before)
				}
			}
		}
	}

	return _fullyExplored
}
----
----
