github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/norm/fold_constants_funcs.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package norm
    12  
    13  import (
    14  	"github.com/cockroachdb/cockroach/pkg/sql/opt"
    15  	"github.com/cockroachdb/cockroach/pkg/sql/opt/cat"
    16  	"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
    17  	"github.com/cockroachdb/cockroach/pkg/sql/parser"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/privilege"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    21  )
    22  
    23  // FoldNullUnary replaces the unary operator with a typed null value having the
    24  // same type as the unary operator would have.
    25  func (c *CustomFuncs) FoldNullUnary(op opt.Operator, input opt.ScalarExpr) opt.ScalarExpr {
    26  	return c.f.ConstructNull(memo.InferUnaryType(op, input.DataType()))
    27  }
    28  
    29  // FoldNullBinary replaces the binary operator with a typed null value having
    30  // the same type as the binary operator would have.
    31  func (c *CustomFuncs) FoldNullBinary(op opt.Operator, left, right opt.ScalarExpr) opt.ScalarExpr {
    32  	return c.f.ConstructNull(memo.InferBinaryType(op, left.DataType(), right.DataType()))
    33  }
    34  
    35  // AllowNullArgs returns true if the binary operator with the given inputs
    36  // allows one of those inputs to be null. If not, then the binary operator will
    37  // simply be replaced by null.
    38  func (c *CustomFuncs) AllowNullArgs(op opt.Operator, left, right opt.ScalarExpr) bool {
    39  	return memo.BinaryAllowsNullArgs(op, left.DataType(), right.DataType())
    40  }
    41  
    42  // IsListOfConstants returns true if elems is a list of constant values or
    43  // tuples.
    44  func (c *CustomFuncs) IsListOfConstants(elems memo.ScalarListExpr) bool {
    45  	for _, elem := range elems {
    46  		if !c.IsConstValueOrTuple(elem) {
    47  			return false
    48  		}
    49  	}
    50  	return true
    51  }
    52  
    53  // FoldArray evaluates an Array expression with constant inputs. It returns the
    54  // array as a Const datum with type TArray.
    55  func (c *CustomFuncs) FoldArray(elems memo.ScalarListExpr, typ *types.T) opt.ScalarExpr {
    56  	elemType := typ.ArrayContents()
    57  	a := tree.NewDArray(elemType)
    58  	a.Array = make(tree.Datums, len(elems))
    59  	for i := range a.Array {
    60  		a.Array[i] = memo.ExtractConstDatum(elems[i])
    61  		if a.Array[i] == tree.DNull {
    62  			a.HasNulls = true
    63  		} else {
    64  			a.HasNonNulls = true
    65  		}
    66  	}
    67  	return c.f.ConstructConst(a, typ)
    68  }
    69  
    70  // IsConstValueOrTuple returns true if the input is a constant or a tuple of
    71  // constants.
    72  func (c *CustomFuncs) IsConstValueOrTuple(input opt.ScalarExpr) bool {
    73  	return memo.CanExtractConstDatum(input)
    74  }
    75  
    76  // HasNullElement returns true if the input tuple has at least one constant,
    77  // null element. Note that it only returns true if one element is known to be
    78  // null. For example, given the tuple (1, x), it will return false because x is
    79  // not guaranteed to be null.
    80  func (c *CustomFuncs) HasNullElement(input opt.ScalarExpr) bool {
    81  	tup := input.(*memo.TupleExpr)
    82  	for _, e := range tup.Elems {
    83  		if e.Op() == opt.NullOp {
    84  			return true
    85  		}
    86  	}
    87  	return false
    88  }
    89  
    90  // HasAllNullElements returns true if the input tuple has only constant, null
    91  // elements, or if the tuple is empty (has 0 elements). Note that it only
    92  // returns true if all elements are known to be null. For example, given the
    93  // tuple (NULL, x), it will return false because x is not guaranteed to be
    94  // null.
    95  func (c *CustomFuncs) HasAllNullElements(input opt.ScalarExpr) bool {
    96  	tup := input.(*memo.TupleExpr)
    97  	for _, e := range tup.Elems {
    98  		if e.Op() != opt.NullOp {
    99  			return false
   100  		}
   101  	}
   102  	return true
   103  }
   104  
   105  // HasNonNullElement returns true if the input tuple has at least one constant,
   106  // non-null element. Note that it only returns true if one element is known to
   107  // be non-null. For example, given the tuple (NULL, x), it will return false
   108  // because x is not guaranteed to be non-null.
   109  func (c *CustomFuncs) HasNonNullElement(input opt.ScalarExpr) bool {
   110  	tup := input.(*memo.TupleExpr)
   111  	for _, e := range tup.Elems {
   112  		// It is guaranteed that the input has at least one non-null element if
   113  		// e is not null and it is either a constant value, array, or tuple.
   114  		// Note that it doesn't matter whether a nested tuple has non-null
   115  		// elements or not. For example, (NULL, (NULL, NULL)) IS NULL evaluates
   116  		// to false because one first-level element is not null - the second is
   117  		// a tuple.
   118  		if e.Op() != opt.NullOp && (opt.IsConstValueOp(e) || e.Op() == opt.TupleOp || e.Op() == opt.ArrayOp) {
   119  			return true
   120  		}
   121  	}
   122  	return false
   123  }
   124  
   125  // HasAllNonNullElements returns true if the input tuple has all constant,
   126  // non-null elements, or if the tuple is empty (has 0 elements). Note that it
   127  // only returns true if all elements are known to be non-null. For example,
   128  // given the tuple (1, x), it will return false because x is not guaranteed to
   129  // be non-null.
   130  func (c *CustomFuncs) HasAllNonNullElements(input opt.ScalarExpr) bool {
   131  	tup := input.(*memo.TupleExpr)
   132  	for _, e := range tup.Elems {
   133  		// It is not guaranteed that the input has all non-null elements if e
   134  		// is null or it is neither a constant value, array, nor tuple. Note
   135  		// that it doesn't matter whether a nested tuple has non-null elements
   136  		// or not. For example, (1, (NULL, NULL)) IS NOT NULL evaluates to true
   137  		// because all first-level elements are not null.
   138  		if e.Op() == opt.NullOp || !(opt.IsConstValueOp(e) || e.Op() == opt.TupleOp || e.Op() == opt.ArrayOp) {
   139  			return false
   140  		}
   141  	}
   142  	return true
   143  }
   144  
   145  // FoldBinary evaluates a binary expression with constant inputs. It returns
   146  // a constant expression as long as it finds an appropriate overload function
   147  // for the given operator and input types, and the evaluation causes no error.
   148  func (c *CustomFuncs) FoldBinary(op opt.Operator, left, right opt.ScalarExpr) opt.ScalarExpr {
   149  	lDatum, rDatum := memo.ExtractConstDatum(left), memo.ExtractConstDatum(right)
   150  
   151  	o, ok := memo.FindBinaryOverload(op, left.DataType(), right.DataType())
   152  	if !ok {
   153  		return nil
   154  	}
   155  
   156  	result, err := o.Fn(c.f.evalCtx, lDatum, rDatum)
   157  	if err != nil {
   158  		return nil
   159  	}
   160  	return c.f.ConstructConstVal(result, o.ReturnType)
   161  }
   162  
   163  // FoldUnary evaluates a unary expression with a constant input. It returns
   164  // a constant expression as long as it finds an appropriate overload function
   165  // for the given operator and input type, and the evaluation causes no error.
   166  func (c *CustomFuncs) FoldUnary(op opt.Operator, input opt.ScalarExpr) opt.ScalarExpr {
   167  	datum := memo.ExtractConstDatum(input)
   168  
   169  	o, ok := memo.FindUnaryOverload(op, input.DataType())
   170  	if !ok {
   171  		return nil
   172  	}
   173  
   174  	result, err := o.Fn(c.f.evalCtx, datum)
   175  	if err != nil {
   176  		return nil
   177  	}
   178  	return c.f.ConstructConstVal(result, o.ReturnType)
   179  }
   180  
   181  // foldStringToRegclassCast resolves a string that is a table name into an OID
   182  // by resolving the table name and returning its table ID. This permits the
   183  // optimizer to do intelligent things like push down filters that look like:
   184  // ... WHERE oid = 'my_table'::REGCLASS
   185  func (c *CustomFuncs) foldStringToRegclassCast(
   186  	input opt.ScalarExpr, typ *types.T,
   187  ) (opt.ScalarExpr, error) {
   188  	// Special case: we're casting a string to a REGCLASS oid, which is a
   189  	// table id lookup.
   190  	flags := cat.Flags{AvoidDescriptorCaches: false, NoTableStats: true}
   191  	datum := memo.ExtractConstDatum(input)
   192  	s := tree.MustBeDString(datum)
   193  	tn, err := parser.ParseQualifiedTableName(string(s))
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  	ds, resName, err := c.f.catalog.ResolveDataSource(c.f.evalCtx.Context, flags, tn)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	c.mem.Metadata().AddDependency(opt.DepByName(&resName), ds, privilege.SELECT)
   203  
   204  	regclassOid := tree.NewDOidWithName(tree.DInt(ds.PostgresDescriptorID()), types.RegClass, string(tn.ObjectName))
   205  	return c.f.ConstructConstVal(regclassOid, typ), nil
   206  
   207  }
   208  
   209  // FoldCast evaluates a cast expression with a constant input. It returns
   210  // a constant expression as long as the evaluation causes no error.
   211  func (c *CustomFuncs) FoldCast(input opt.ScalarExpr, typ *types.T) opt.ScalarExpr {
   212  	if typ.Family() == types.OidFamily {
   213  		if typ.Oid() == types.RegClass.Oid() && input.DataType().Family() == types.StringFamily {
   214  			expr, err := c.foldStringToRegclassCast(input, typ)
   215  			if err == nil {
   216  				return expr
   217  			}
   218  		}
   219  		// Save this cast for the execbuilder.
   220  		return nil
   221  	}
   222  
   223  	datum := memo.ExtractConstDatum(input)
   224  	texpr := tree.NewTypedCastExpr(datum, typ)
   225  
   226  	result, err := texpr.Eval(c.f.evalCtx)
   227  	if err != nil {
   228  		return nil
   229  	}
   230  
   231  	return c.f.ConstructConstVal(result, typ)
   232  }
   233  
   234  // isMonotonicConversion returns true if conversion of a value from FROM to
   235  // TO is monotonic.
   236  // That is, if a and b are values of type FROM, then
   237  //
   238  //   1. a = b implies a::TO = b::TO and
   239  //   2. a < b implies a::TO <= b::TO
   240  //
   241  // Property (1) can be violated by cases like:
   242  //
   243  //   '-0'::FLOAT = '0'::FLOAT, but '-0'::FLOAT::STRING != '0'::FLOAT::STRING
   244  //
   245  // Property (2) can be violated by cases like:
   246  //
   247  //   2 < 10, but  2::STRING > 10::STRING.
   248  //
   249  // Note that the stronger version of (2),
   250  //
   251  //   a < b implies a::TO < b::TO
   252  //
   253  // is not required, for instance this is not generally true of conversion from
   254  // a TIMESTAMP to a DATE, but certain such conversions can still generate spans
   255  // in some cases where values under FROM and TO are "the same" (such as where a
   256  // TIMESTAMP precisely falls on a date boundary).  We don't need this property
   257  // because we will subsequently check that the values can round-trip to ensure
   258  // that we don't lose any information by doing the conversion.
   259  // TODO(justin): fill this out with the complete set of such conversions.
   260  func isMonotonicConversion(from, to *types.T) bool {
   261  	switch from.Family() {
   262  	case types.TimestampFamily, types.TimestampTZFamily, types.DateFamily:
   263  		switch to.Family() {
   264  		case types.TimestampFamily, types.TimestampTZFamily, types.DateFamily:
   265  			return true
   266  		}
   267  		return false
   268  
   269  	case types.IntFamily, types.FloatFamily, types.DecimalFamily:
   270  		switch to.Family() {
   271  		case types.IntFamily, types.FloatFamily, types.DecimalFamily:
   272  			return true
   273  		}
   274  		return false
   275  	}
   276  
   277  	return false
   278  }
   279  
   280  // UnifyComparison attempts to convert a constant expression to the type of the
   281  // variable expression, if that conversion can round-trip and is monotonic.
   282  func (c *CustomFuncs) UnifyComparison(left, right opt.ScalarExpr) opt.ScalarExpr {
   283  	v := left.(*memo.VariableExpr)
   284  	cnst := right.(*memo.ConstExpr)
   285  
   286  	desiredType := v.DataType()
   287  	originalType := cnst.DataType()
   288  
   289  	// Don't bother if they're already the same.
   290  	if desiredType.Equivalent(originalType) {
   291  		return nil
   292  	}
   293  
   294  	if !isMonotonicConversion(originalType, desiredType) {
   295  		return nil
   296  	}
   297  
   298  	// Check that the datum can round-trip between the types. If this is true, it
   299  	// means we don't lose any information needed to generate spans, and combined
   300  	// with monotonicity means that it's safe to convert the RHS to the type of
   301  	// the LHS.
   302  	convertedDatum, err := tree.PerformCast(c.f.evalCtx, cnst.Value, desiredType)
   303  	if err != nil {
   304  		return nil
   305  	}
   306  
   307  	convertedBack, err := tree.PerformCast(c.f.evalCtx, convertedDatum, originalType)
   308  	if err != nil {
   309  		return nil
   310  	}
   311  
   312  	if convertedBack.Compare(c.f.evalCtx, cnst.Value) != 0 {
   313  		return nil
   314  	}
   315  
   316  	return c.f.ConstructConst(convertedDatum, desiredType)
   317  }
   318  
   319  // FoldComparison evaluates a comparison expression with constant inputs. It
   320  // returns a constant expression as long as it finds an appropriate overload
   321  // function for the given operator and input types, and the evaluation causes
   322  // no error.
   323  func (c *CustomFuncs) FoldComparison(op opt.Operator, left, right opt.ScalarExpr) opt.ScalarExpr {
   324  	lDatum, rDatum := memo.ExtractConstDatum(left), memo.ExtractConstDatum(right)
   325  
   326  	var flipped, not bool
   327  	o, flipped, not, ok := memo.FindComparisonOverload(op, left.DataType(), right.DataType())
   328  	if !ok {
   329  		return nil
   330  	}
   331  
   332  	if flipped {
   333  		lDatum, rDatum = rDatum, lDatum
   334  	}
   335  
   336  	result, err := o.Fn(c.f.evalCtx, lDatum, rDatum)
   337  	if err != nil {
   338  		return nil
   339  	}
   340  	if b, ok := result.(*tree.DBool); ok && not {
   341  		result = tree.MakeDBool(!*b)
   342  	}
   343  	return c.f.ConstructConstVal(result, types.Bool)
   344  }
   345  
   346  // FoldIndirection evaluates an array indirection operator with constant inputs.
   347  // It returns the referenced array element as a constant value, or nil if the
   348  // evaluation results in an error.
   349  func (c *CustomFuncs) FoldIndirection(input, index opt.ScalarExpr) opt.ScalarExpr {
   350  	// Index is 1-based, so convert to 0-based.
   351  	indexD := memo.ExtractConstDatum(index)
   352  
   353  	// Case 1: The input is a static array constructor.
   354  	if arr, ok := input.(*memo.ArrayExpr); ok {
   355  		if indexInt, ok := indexD.(*tree.DInt); ok {
   356  			indexI := int(*indexInt) - 1
   357  			if indexI >= 0 && indexI < len(arr.Elems) {
   358  				return arr.Elems[indexI]
   359  			}
   360  			return c.f.ConstructNull(arr.Typ.ArrayContents())
   361  		}
   362  		if indexD == tree.DNull {
   363  			return c.f.ConstructNull(arr.Typ.ArrayContents())
   364  		}
   365  		return nil
   366  	}
   367  
   368  	// Case 2: The input is a constant DArray.
   369  	if memo.CanExtractConstDatum(input) {
   370  		inputD := memo.ExtractConstDatum(input)
   371  		texpr := tree.NewTypedIndirectionExpr(inputD, indexD, input.DataType().ArrayContents())
   372  		result, err := texpr.Eval(c.f.evalCtx)
   373  		if err == nil {
   374  			return c.f.ConstructConstVal(result, texpr.ResolvedType())
   375  		}
   376  	}
   377  
   378  	return nil
   379  }
   380  
   381  // FoldColumnAccess tries to evaluate a tuple column access operator with a
   382  // constant tuple input (though tuple field values do not need to be constant).
   383  // It returns the referenced tuple field value, or nil if folding is not
   384  // possible or results in an error.
   385  func (c *CustomFuncs) FoldColumnAccess(input opt.ScalarExpr, idx memo.TupleOrdinal) opt.ScalarExpr {
   386  	// Case 1: The input is a static tuple constructor.
   387  	if tup, ok := input.(*memo.TupleExpr); ok {
   388  		return tup.Elems[idx]
   389  	}
   390  
   391  	// Case 2: The input is a constant DTuple.
   392  	if memo.CanExtractConstDatum(input) {
   393  		datum := memo.ExtractConstDatum(input)
   394  
   395  		texpr := tree.NewTypedColumnAccessExpr(datum, "" /* by-index access */, int(idx))
   396  		result, err := texpr.Eval(c.f.evalCtx)
   397  		if err == nil {
   398  			return c.f.ConstructConstVal(result, texpr.ResolvedType())
   399  		}
   400  	}
   401  
   402  	return nil
   403  }
   404  
   405  // FoldFunction evaluates a function expression with constant inputs. It
   406  // returns a constant expression as long as the function is contained in the
   407  // FoldFunctionWhitelist, and the evaluation causes no error.
   408  func (c *CustomFuncs) FoldFunction(
   409  	args memo.ScalarListExpr, private *memo.FunctionPrivate,
   410  ) opt.ScalarExpr {
   411  	// Non-normal function classes (aggregate, window, generator) cannot be
   412  	// folded into a single constant.
   413  	if private.Properties.Class != tree.NormalClass {
   414  		return nil
   415  	}
   416  	// Functions that aren't immutable and also not in the whitelist cannot
   417  	// be folded.
   418  	if _, ok := FoldFunctionWhitelist[private.Name]; !ok && private.Overload.Volatility > tree.VolatilityImmutable {
   419  		return nil
   420  	}
   421  
   422  	exprs := make(tree.TypedExprs, len(args))
   423  	for i := range exprs {
   424  		exprs[i] = memo.ExtractConstDatum(args[i])
   425  	}
   426  	funcRef := tree.WrapFunction(private.Name)
   427  	fn := tree.NewTypedFuncExpr(
   428  		funcRef,
   429  		0, /* aggQualifier */
   430  		exprs,
   431  		nil, /* filter */
   432  		nil, /* windowDef */
   433  		private.Typ,
   434  		private.Properties,
   435  		private.Overload,
   436  	)
   437  
   438  	result, err := fn.Eval(c.f.evalCtx)
   439  	if err != nil {
   440  		return nil
   441  	}
   442  	return c.f.ConstructConstVal(result, private.Typ)
   443  }
   444  
   445  // FoldFunctionWhitelist contains non-immutable functions that are nevertheless
   446  // known to be safe for folding.
   447  var FoldFunctionWhitelist = map[string]struct{}{
   448  	// The SQL statement is generated in the optbuilder phase, so the remaining
   449  	// function execution is immutable.
   450  	"addgeometrycolumn": {},
   451  
   452  	// Query plan cache is invalidated on location changes.
   453  	"crdb_internal.locality_value": {},
   454  }