#
# Generate code for relational, scalar, enforcer, and private operators.
#
optgen exprs test.opt

# Project is a projection operator.
[Relational]
define Project {
    # Input is a relational input.
    Input RelExpr

    # Projections are the projected columns.
    Projections ProjectionsExpr

    Passthrough ColSet
}

[Scalar, List]
define Projections {
}

[Scalar, ListItem]
define ProjectionsItem {
    Element ScalarExpr

    _ ColPrivate
}

[Private]
define ColPrivate {
    # Col is a column ID.
    Col ColumnID

    # scalar are props.
    scalar ScalarProps
}

[Enforcer]
define Sort {
}
----
----
// Code generated by optgen; [omitted]

package memo

import (
	"unsafe"

	"github.com/cockroachdb/cockroach/pkg/sql/coltypes"
	"github.com/cockroachdb/cockroach/pkg/sql/opt"
	"github.com/cockroachdb/cockroach/pkg/sql/opt/constraint"
	"github.com/cockroachdb/cockroach/pkg/sql/opt/props"
	"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"
)

// ProjectExpr is a projection operator.
type ProjectExpr struct {
	// Input is a relational input.
	Input RelExpr

	// Projections are the projected columns.
	Projections ProjectionsExpr
	Passthrough opt.ColSet

	grp  exprGroup
	next RelExpr
}

var _ RelExpr = &ProjectExpr{}

func (e *ProjectExpr) Op() opt.Operator {
	return opt.ProjectOp
}

func (e *ProjectExpr) ChildCount() int {
	return 2
}

func (e *ProjectExpr) Child(nth int) opt.Expr {
	switch nth {
	case 0:
		return e.Input
	case 1:
		return &e.Projections
	}
	panic(pgerror.NewAssertionErrorf("child index out of range"))
}

func (e *ProjectExpr) Private() interface{} {
	return &e.Passthrough
}

func (e *ProjectExpr) String() string {
	f := MakeExprFmtCtx(ExprFmtHideQualifications, e.Memo())
	f.FormatExpr(e)
	return f.Buffer.String()
}

func (e *ProjectExpr) SetChild(nth int, child opt.Expr) {
	switch nth {
	case 0:
		e.Input = child.(RelExpr)
		return
	case 1:
		e.Projections = *child.(*ProjectionsExpr)
		return
	}
	panic(pgerror.NewAssertionErrorf("child index out of range"))
}

func (e *ProjectExpr) Memo() *Memo {
	return e.grp.memo()
}

func (e *ProjectExpr) Relational() *props.Relational {
	return e.grp.relational()
}

func (e *ProjectExpr) FirstExpr() RelExpr {
	return e.grp.firstExpr()
}

func (e *ProjectExpr) NextExpr() RelExpr {
	return e.next
}

func (e *ProjectExpr) RequiredPhysical() *physical.Required {
	return e.grp.bestProps().required
}

func (e *ProjectExpr) ProvidedPhysical() *physical.Provided {
	return &e.grp.bestProps().provided
}

func (e *ProjectExpr) Cost() Cost {
	return e.grp.bestProps().cost
}

func (e *ProjectExpr) group() exprGroup {
	return e.grp
}

func (e *ProjectExpr) bestProps() *bestProps {
	return e.grp.bestProps()
}

func (e *ProjectExpr) setNext(member RelExpr) {
	if e.next != nil {
		panic(pgerror.NewAssertionErrorf("expression already has its next defined: %s", e))
	}
	e.next = member
}

func (e *ProjectExpr) setGroup(member RelExpr) {
	if e.grp != nil {
		panic(pgerror.NewAssertionErrorf("expression is already in a group: %s", e))
	}
	e.grp = member.group()
	LastGroupMember(member).setNext(e)
}

type projectGroup struct {
	mem   *Memo
	rel   props.Relational
	first ProjectExpr
	best  bestProps
}

var _ exprGroup = &projectGroup{}

func (g *projectGroup) memo() *Memo {
	return g.mem
}

func (g *projectGroup) relational() *props.Relational {
	return &g.rel
}

func (g *projectGroup) firstExpr() RelExpr {
	return &g.first
}

func (g *projectGroup) bestProps() *bestProps {
	return &g.best
}

type ProjectionsExpr []ProjectionsItem

var EmptyProjectionsExpr = ProjectionsExpr{}

var _ opt.ScalarExpr = &ProjectionsExpr{}

func (e *ProjectionsExpr) ID() opt.ScalarID {
	panic(pgerror.NewAssertionErrorf("lists have no id"))
}

func (e *ProjectionsExpr) Op() opt.Operator {
	return opt.ProjectionsOp
}

func (e *ProjectionsExpr) ChildCount() int {
	return len(*e)
}

func (e *ProjectionsExpr) Child(nth int) opt.Expr {
	return &(*e)[nth]
}

func (e *ProjectionsExpr) Private() interface{} {
	return nil
}

func (e *ProjectionsExpr) String() string {
	f := MakeExprFmtCtx(ExprFmtHideQualifications, nil)
	f.FormatExpr(e)
	return f.Buffer.String()
}

func (e *ProjectionsExpr) SetChild(nth int, child opt.Expr) {
	(*e)[nth] = *child.(*ProjectionsItem)
}

func (e *ProjectionsExpr) DataType() types.T {
	return types.Any
}

type ProjectionsItem struct {
	Element opt.ScalarExpr
	ColPrivate

	Typ types.T
	id  opt.ScalarID
}

var _ opt.ScalarExpr = &ProjectionsItem{}

func (e *ProjectionsItem) ID() opt.ScalarID {
	return e.id
}

func (e *ProjectionsItem) Op() opt.Operator {
	return opt.ProjectionsItemOp
}

func (e *ProjectionsItem) ChildCount() int {
	return 1
}

func (e *ProjectionsItem) Child(nth int) opt.Expr {
	switch nth {
	case 0:
		return e.Element
	}
	panic(pgerror.NewAssertionErrorf("child index out of range"))
}

func (e *ProjectionsItem) Private() interface{} {
	return &e.ColPrivate
}

func (e *ProjectionsItem) String() string {
	f := MakeExprFmtCtx(ExprFmtHideQualifications, nil)
	f.FormatExpr(e)
	return f.Buffer.String()
}

func (e *ProjectionsItem) SetChild(nth int, child opt.Expr) {
	switch nth {
	case 0:
		e.Element = child.(opt.ScalarExpr)
		return
	}
	panic(pgerror.NewAssertionErrorf("child index out of range"))
}

func (e *ProjectionsItem) DataType() types.T {
	return e.Typ
}

func (e *ProjectionsItem) ScalarProps(mem *Memo) *props.Scalar {
	if !e.scalar.Populated {
		mem.logPropsBuilder.buildProjectionsItemProps(e, &e.scalar)
	}
	return &e.scalar
}

type ColPrivate struct {
	// Col is a column ID.
	Col opt.ColumnID

	// scalar are props.
	scalar props.Scalar
}

type SortExpr struct {
	Input RelExpr
	best  bestProps
}

func (e *SortExpr) Op() opt.Operator {
	return opt.SortOp
}

func (e *SortExpr) ChildCount() int {
	return 1
}

func (e *SortExpr) Child(nth int) opt.Expr {
	if nth == 0 {
		return e.Input
	}
	panic(pgerror.NewAssertionErrorf("child index out of range"))
}

func (e *SortExpr) Private() interface{} {
	return nil
}

func (e *SortExpr) String() string {
	f := MakeExprFmtCtx(ExprFmtHideQualifications, e.Memo())
	f.FormatExpr(e)
	return f.Buffer.String()
}

func (e *SortExpr) SetChild(nth int, child opt.Expr) {
	if nth == 0 {
		e.Input = child.(RelExpr)
		return
	}
	panic(pgerror.NewAssertionErrorf("child index out of range"))
}

func (e *SortExpr) Memo() *Memo {
	return e.Input.Memo()
}

func (e *SortExpr) Relational() *props.Relational {
	return e.Input.Relational()
}

func (e *SortExpr) FirstExpr() RelExpr {
	return e.Input.FirstExpr()
}

func (e *SortExpr) NextExpr() RelExpr {
	return nil
}

func (e *SortExpr) RequiredPhysical() *physical.Required {
	return e.best.required
}

func (e *SortExpr) ProvidedPhysical() *physical.Provided {
	return &e.best.provided
}

func (e *SortExpr) Cost() Cost {
	return e.best.cost
}

func (e *SortExpr) bestProps() *bestProps {
	return &e.best
}

func (e *SortExpr) group() exprGroup {
	return e.Input.group()
}

func (e *SortExpr) setNext(member RelExpr) {
	panic(pgerror.NewAssertionErrorf("setNext cannot be called on enforcers"))
}

func (e *SortExpr) setGroup(member exprGroup) {
	panic(pgerror.NewAssertionErrorf("setGroup cannot be called on enforcers"))
}

func (m *Memo) MemoizeProject(
	input RelExpr,
	projections ProjectionsExpr,
	passthrough opt.ColSet,
) RelExpr {
	const size = int64(unsafe.Sizeof(projectGroup{}))
	grp := &projectGroup{mem: m, first: ProjectExpr{
		Input:       input,
		Projections: projections,
		Passthrough: passthrough,
	}}
	e := &grp.first
	e.grp = grp
	interned := m.interner.InternProject(e)
	if interned == e {
		m.logPropsBuilder.buildProjectProps(e, &grp.rel)
		m.memEstimate += size
		m.checkExpr(e)
	}
	return interned.FirstExpr()
}

func (m *Memo) AddProjectToGroup(e *ProjectExpr, grp RelExpr) *ProjectExpr {
	const size = int64(unsafe.Sizeof(ProjectExpr{}))
	interned := m.interner.InternProject(e)
	if interned == e {
		e.setGroup(grp)
		m.memEstimate += size
		m.checkExpr(e)
	} else if interned.group() != grp.group() {
		// This is a group collision, do nothing.
		return nil
	}
	return interned
}

func (in *interner) InternExpr(e opt.Expr) opt.Expr {
	switch t := e.(type) {
	case *ProjectExpr:
		return in.InternProject(t)
	case *ProjectionsExpr:
		return in.InternProjections(t)
	case *ProjectionsItem:
		return in.InternProjectionsItem(t)
	default:
		panic(pgerror.NewAssertionErrorf("unhandled op: %s", e.Op()))
	}
}

func (in *interner) InternProject(val *ProjectExpr) *ProjectExpr {
	in.hasher.Init()
	in.hasher.HashOperator(opt.ProjectOp)
	in.hasher.HashRelExpr(val.Input)
	in.hasher.HashProjectionsExpr(val.Projections)
	in.hasher.HashColSet(val.Passthrough)

	in.cache.Start(in.hasher.hash)
	for in.cache.Next() {
		if existing, ok := in.cache.Item().(*ProjectExpr); ok {
			if in.hasher.IsRelExprEqual(val.Input, existing.Input) &&
				in.hasher.IsProjectionsExprEqual(val.Projections, existing.Projections) &&
				in.hasher.IsColSetEqual(val.Passthrough, existing.Passthrough) {
				return existing
			}
		}
	}

	in.cache.Add(val)
	return val
}

func (in *interner) InternProjections(val *ProjectionsExpr) *ProjectionsExpr {
	in.hasher.Init()
	in.hasher.HashOperator(opt.ProjectionsOp)
	in.hasher.HashProjectionsExpr(*val)

	in.cache.Start(in.hasher.hash)
	for in.cache.Next() {
		if existing, ok := in.cache.Item().(*ProjectionsExpr); ok {
			if in.hasher.IsProjectionsExprEqual(*val, *existing) {
				return existing
			}
		}
	}

	in.cache.Add(val)
	return val
}

func (in *interner) InternProjectionsItem(val *ProjectionsItem) *ProjectionsItem {
	in.hasher.Init()
	in.hasher.HashOperator(opt.ProjectionsItemOp)
	in.hasher.HashScalarExpr(val.Element)
	in.hasher.HashColumnID(val.Col)

	in.cache.Start(in.hasher.hash)
	for in.cache.Next() {
		if existing, ok := in.cache.Item().(*ProjectionsItem); ok {
			if in.hasher.IsScalarExprEqual(val.Element, existing.Element) &&
				in.hasher.IsColumnIDEqual(val.Col, existing.Col) {
				return existing
			}
		}
	}

	in.cache.Add(val)
	return val
}

func (b *logicalPropsBuilder) buildProps(e RelExpr, rel *props.Relational) {
	switch t := e.(type) {
	case *ProjectExpr:
		b.buildProjectProps(t, rel)
	default:
		panic(pgerror.NewAssertionErrorf("unhandled type: %s", t.Op()))
	}
}
----
----
