@@ -8,9 +8,12 @@ package memo
88import (
99 "context"
1010
11+ "github.com/cockroachdb/cockroach/pkg/sql/catalog/colinfo"
1112 "github.com/cockroachdb/cockroach/pkg/sql/opt"
13+ "github.com/cockroachdb/cockroach/pkg/sql/opt/props"
1214 "github.com/cockroachdb/cockroach/pkg/sql/sem/eval"
1315 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
16+ "github.com/cockroachdb/cockroach/pkg/sql/types"
1417 "github.com/cockroachdb/cockroach/pkg/util/intsets"
1518 "github.com/cockroachdb/errors"
1619)
@@ -540,3 +543,126 @@ func ExtractTailCalls(expr opt.Expr, tailCalls map[opt.ScalarExpr]struct{}) {
540543 }
541544 }
542545}
546+
547+ // ExtractExactScalarExprsForColumns extracts scalar expressions that tightly
548+ // constrain the given columns from the given filters. Unlike constraint
549+ // builder, this function:
550+ // - constrains to scalar expressions instead of constants
551+ // - always constrains tightly
552+ // - in fact, always constrains _exactly_ (that is, to scalar expressions that
553+ // produce not only equivalent values, but _exactly_ the same bytes)
554+ //
555+ // If a filter does not exactly constrain one of the given columns, or
556+ // references more than one of the given columns, the filter will be added to
557+ // remainingFilters. ExtractExactScalarExprsForColumns returns the mapping from
558+ // constrained columns to scalar expressions, the remaining filters, and a
559+ // boolean indicating whether all columns were constrained.
560+ func ExtractExactScalarExprsForColumns (
561+ cols opt.ColSet ,
562+ filters FiltersExpr ,
563+ evalCtx * eval.Context ,
564+ md * opt.Metadata ,
565+ constructIsNotNull func (opt.ColumnID ) FiltersItem ,
566+ constructFiltersItem func (opt.ScalarExpr ) FiltersItem ,
567+ ) (constraints map [opt.ColumnID ]opt.ScalarExpr , remainingFilters FiltersExpr , ok bool ) {
568+ constraints = make (map [opt.ColumnID ]opt.ScalarExpr )
569+
570+ // useExprAsConstraint adds the scalar expression to the constraints map.
571+ useExprAsConstraint := func (col opt.ColumnID , e opt.ScalarExpr ) bool {
572+ // Filters on other columns become remainingFilters.
573+ if ! cols .Contains (col ) {
574+ return false
575+ }
576+ // Only use the first constraining filter found. If there is already a
577+ // constraining filter, any additional filters become part of
578+ // remainingFilters.
579+ if _ , ok := constraints [col ]; ok {
580+ return false
581+ }
582+ // In the general case, the = operator (SQL equivalence) does not imply
583+ // exactly the same bytes. The scalar expression is exactly the same when:
584+ // - the column does not have composite type
585+ // - the scalar type is identical to the column type OR the scalar is NULL
586+ colType := md .ColumnMeta (col ).Type
587+ if colinfo .CanHaveCompositeKeyEncoding (colType ) {
588+ // TODO(michae2): If ExtractConstDatum(e) returns a non-composite datum,
589+ // we could still constrain using this expression.
590+ return false
591+ }
592+ if ! e .DataType ().Identical (colType ) && e .DataType ().Family () != types .UnknownFamily {
593+ // TODO(michae2): Explore whether we can use identical canonical types
594+ // here. Also add support for tuples.
595+ return false
596+ }
597+ // If this expression references other columns we're trying to constrain, it
598+ // will have to be a remainingFilter.
599+ var sharedProps props.Shared
600+ BuildSharedProps (e , & sharedProps , evalCtx )
601+ if sharedProps .OuterCols .Intersects (cols ) {
602+ return false
603+ }
604+ constraints [col ] = e
605+ return true
606+ }
607+
608+ // constrainColsInExpr walks a filter predicate, adding scalar expressions for
609+ // any columns constrained to a single value by the predicate.
610+ var constrainColsInExpr func (opt.ScalarExpr )
611+ constrainColsInExpr = func (e opt.ScalarExpr ) {
612+ switch t := e .(type ) {
613+ case * VariableExpr :
614+ // `WHERE col` will be true if col is a boolean-typed column that has the
615+ // value True.
616+ if md .ColumnMeta (t .Col ).Type .Family () == types .BoolFamily {
617+ if useExprAsConstraint (t .Col , TrueSingleton ) {
618+ return
619+ }
620+ }
621+ case * NotExpr :
622+ // `WHERE NOT col` will be true if col is a boolean-typed column that has
623+ // the value False.
624+ if v , ok := t .Input .(* VariableExpr ); ok {
625+ if md .ColumnMeta (v .Col ).Type .Family () == types .BoolFamily {
626+ if useExprAsConstraint (v .Col , FalseSingleton ) {
627+ return
628+ }
629+ }
630+ }
631+ case * AndExpr :
632+ constrainColsInExpr (t .Left )
633+ constrainColsInExpr (t .Right )
634+ return
635+ case * EqExpr :
636+ if v , ok := t .Left .(* VariableExpr ); ok {
637+ // `WHERE col = <expr>` will be true if col has the value <expr> and
638+ // <expr> IS NOT NULL.
639+ if useExprAsConstraint (v .Col , t .Right ) {
640+ // Because NULL = NULL evaluates to NULL, rather than true, we must
641+ // also guard against the scalar expression evaluating to NULL.
642+ remainingFilters = append (remainingFilters , constructIsNotNull (v .Col ))
643+ return
644+ }
645+ }
646+ // TODO(michae2): Handle tuple of variables on LHS of EqExpr.
647+ case * IsExpr :
648+ if v , ok := t .Left .(* VariableExpr ); ok {
649+ // `WHERE col IS NOT DISTINCT FROM <expr>` will be true if col has the
650+ // value <expr>.
651+ if useExprAsConstraint (v .Col , t .Right ) {
652+ return
653+ }
654+ }
655+ // TODO(michae2): Handle tuple of variables on LHS of IsExpr.
656+ }
657+ // TODO(michae2): Add case for *InExpr.
658+
659+ // If the filter did not constrain a column, add it to remainingFilters.
660+ remainingFilters = append (remainingFilters , constructFiltersItem (e ))
661+ }
662+
663+ for i := range filters {
664+ constrainColsInExpr (filters [i ].Condition )
665+ }
666+
667+ return constraints , remainingFilters , len (constraints ) == cols .Len ()
668+ }
0 commit comments