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 }