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

     1  // Copyright 2018 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 memo_test
    12  
    13  import (
    14  	"testing"
    15  
    16  	"github.com/cockroachdb/cockroach/pkg/sql/opt"
    17  	"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/sem/builtins"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    21  )
    22  
    23  func TestTyping(t *testing.T) {
    24  	runDataDrivenTest(t, "testdata/typing",
    25  		memo.ExprFmtHideMiscProps|
    26  			memo.ExprFmtHideConstraints|
    27  			memo.ExprFmtHideFuncDeps|
    28  			memo.ExprFmtHideRuleProps|
    29  			memo.ExprFmtHideStats|
    30  			memo.ExprFmtHideCost|
    31  			memo.ExprFmtHideQualifications|
    32  			memo.ExprFmtHideScalars,
    33  	)
    34  }
    35  
    36  func TestBinaryOverloadExists(t *testing.T) {
    37  	test := func(expected, actual bool) {
    38  		if expected != actual {
    39  			t.Errorf("expected %v, got %v", expected, actual)
    40  		}
    41  	}
    42  
    43  	test(true, memo.BinaryOverloadExists(opt.MinusOp, types.Date, types.Int))
    44  	test(true, memo.BinaryOverloadExists(opt.MinusOp, types.Date, types.Unknown))
    45  	test(true, memo.BinaryOverloadExists(opt.MinusOp, types.Unknown, types.Int))
    46  	test(false, memo.BinaryOverloadExists(opt.MinusOp, types.Int, types.Date))
    47  	test(true, memo.BinaryOverloadExists(opt.ConcatOp, types.IntArray, types.Int))
    48  	test(true, memo.BinaryOverloadExists(opt.ConcatOp, types.Unknown, types.IntArray))
    49  }
    50  
    51  func TestBinaryAllowsNullArgs(t *testing.T) {
    52  	test := func(expected, actual bool) {
    53  		if expected != actual {
    54  			t.Errorf("expected %v, got %v", expected, actual)
    55  		}
    56  	}
    57  
    58  	test(false, memo.BinaryAllowsNullArgs(opt.PlusOp, types.Int, types.Int))
    59  	test(false, memo.BinaryAllowsNullArgs(opt.PlusOp, types.Int, types.Unknown))
    60  	test(true, memo.BinaryOverloadExists(opt.ConcatOp, types.IntArray, types.Int))
    61  	test(true, memo.BinaryOverloadExists(opt.ConcatOp, types.Unknown, types.IntArray))
    62  }
    63  
    64  // TestTypingUnaryAssumptions ensures that unary overloads conform to certain
    65  // assumptions we're making in the type inference code:
    66  //   1. The return type can be inferred from the operator type and the data
    67  //      types of its operand.
    68  func TestTypingUnaryAssumptions(t *testing.T) {
    69  	for name, overloads := range tree.UnaryOps {
    70  		for i, overload := range overloads {
    71  			op := overload.(*tree.UnaryOp)
    72  
    73  			// Check for basic ambiguity where two different unary op overloads
    74  			// both allow equivalent operand types.
    75  			for i2, overload2 := range overloads {
    76  				if i == i2 {
    77  					continue
    78  				}
    79  
    80  				op2 := overload2.(*tree.UnaryOp)
    81  				if op.Typ.Equivalent(op2.Typ) {
    82  					format := "found equivalent operand type ambiguity for %s:\n%+v\n%+v"
    83  					t.Errorf(format, name, op, op2)
    84  				}
    85  			}
    86  		}
    87  	}
    88  }
    89  
    90  // TestTypingBinaryAssumptions ensures that binary overloads conform to certain
    91  // assumptions we're making in the type inference code:
    92  //   1. The return type can be inferred from the operator type and the data
    93  //      types of its operands.
    94  //   2. When of the operands is null, and if NullableArgs is true, then the
    95  //      return type can be inferred from just the non-null operand.
    96  func TestTypingBinaryAssumptions(t *testing.T) {
    97  	for name, overloads := range tree.BinOps {
    98  		for i, overload := range overloads {
    99  			op := overload.(*tree.BinOp)
   100  
   101  			// Check for basic ambiguity where two different binary op overloads
   102  			// both allow equivalent operand types.
   103  			for i2, overload2 := range overloads {
   104  				if i == i2 {
   105  					continue
   106  				}
   107  
   108  				op2 := overload2.(*tree.BinOp)
   109  				if op.LeftType.Equivalent(op2.LeftType) && op.RightType.Equivalent(op2.RightType) {
   110  					format := "found equivalent operand type ambiguity for %s:\n%+v\n%+v"
   111  					t.Errorf(format, name, op, op2)
   112  				}
   113  			}
   114  
   115  			// Handle ops that allow null operands. Check for ambiguity where
   116  			// the return type cannot be inferred from the non-null operand.
   117  			if op.NullableArgs {
   118  				for i2, overload2 := range overloads {
   119  					if i == i2 {
   120  						continue
   121  					}
   122  
   123  					op2 := overload2.(*tree.BinOp)
   124  					if !op2.NullableArgs {
   125  						continue
   126  					}
   127  
   128  					if op.LeftType == op2.LeftType && op.ReturnType != op2.ReturnType {
   129  						t.Errorf("found null operand ambiguity for %s:\n%+v\n%+v", name, op, op2)
   130  					}
   131  
   132  					if op.RightType == op2.RightType && op.ReturnType != op2.ReturnType {
   133  						t.Errorf("found null operand ambiguity for %s:\n%+v\n%+v", name, op, op2)
   134  					}
   135  				}
   136  			}
   137  		}
   138  	}
   139  }
   140  
   141  // TestTypingComparisonAssumptions ensures that comparison overloads conform to
   142  // certain assumptions we're making in the type inference code:
   143  //   1. All comparison ops will be present in tree.CmpOps after being mapped
   144  //      with NormalizeComparison.
   145  //   2. The overload can be inferred from the operator type and the data
   146  //      types of its operands.
   147  func TestTypingComparisonAssumptions(t *testing.T) {
   148  	for _, op := range opt.ComparisonOperators {
   149  		newOp, _, _ := memo.NormalizeComparison(op)
   150  		comp := opt.ComparisonOpReverseMap[newOp]
   151  		if _, ok := tree.CmpOps[comp]; !ok {
   152  			t.Errorf("could not find overload for %v", op)
   153  		}
   154  	}
   155  	for name, overloads := range tree.CmpOps {
   156  		for i, overload := range overloads {
   157  			op := overload.(*tree.CmpOp)
   158  
   159  			// Check for basic ambiguity where two different comparison op overloads
   160  			// both allow equivalent operand types.
   161  			for i2, overload2 := range overloads {
   162  				if i == i2 {
   163  					continue
   164  				}
   165  
   166  				op2 := overload2.(*tree.CmpOp)
   167  				if op.LeftType.Equivalent(op2.LeftType) && op.RightType.Equivalent(op2.RightType) {
   168  					format := "found equivalent operand type ambiguity for %s:\n%+v\n%+v"
   169  					t.Errorf(format, name, op, op2)
   170  				}
   171  			}
   172  		}
   173  	}
   174  }
   175  
   176  // TestTypingAggregateAssumptions ensures that aggregate overloads conform to
   177  // certain assumptions we're making in the type inference code:
   178  //   1. The return type can be inferred from the operator type and the data
   179  //      types of its operand.
   180  //   2. The return type of overloads is fixed.
   181  //   3. The return type for min/max aggregates is same as type of argument.
   182  func TestTypingAggregateAssumptions(t *testing.T) {
   183  	for _, name := range builtins.AllAggregateBuiltinNames {
   184  		if name == builtins.AnyNotNull ||
   185  			name == "percentile_disc" ||
   186  			name == "percentile_cont" {
   187  			// These are treated as special cases.
   188  			continue
   189  		}
   190  		_, overloads := builtins.GetBuiltinProperties(name)
   191  		for i, overload := range overloads {
   192  			// Check for basic ambiguity where two different aggregate function
   193  			// overloads both allow equivalent operand types.
   194  			for i2, overload2 := range overloads {
   195  				if i == i2 {
   196  					continue
   197  				}
   198  
   199  				if overload.Types.Match(overload2.Types.Types()) {
   200  					format := "found equivalent operand type ambiguity for %s: %+v"
   201  					t.Errorf(format, name, overload.Types.Types())
   202  				}
   203  			}
   204  
   205  			// Check for fixed return types.
   206  			retType := overload.ReturnType(nil)
   207  
   208  			// As per rule 3, max and min have slightly different rules. We allow
   209  			// max and min to have non-fixed return types to allow defining aggregate
   210  			// overloads that have container types as arguments.
   211  			if name == "min" || name == "max" {
   212  				// Evaluate the return typer.
   213  				types := overload.Types.Types()
   214  				args := make([]tree.TypedExpr, len(types))
   215  				for i, t := range types {
   216  					args[i] = &tree.TypedDummy{Typ: t}
   217  				}
   218  				retType = overload.ReturnType(args)
   219  				if retType != overload.Types.Types()[0] {
   220  					t.Errorf("return type differs from arg type for %s: %+v", name, overload.Types.Types())
   221  				}
   222  				continue
   223  			}
   224  
   225  			if retType == tree.UnknownReturnType {
   226  				t.Errorf("return type is not fixed for %s: %+v", name, overload.Types.Types())
   227  			}
   228  		}
   229  	}
   230  }