github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/sem/tree/expr_test.go (about) 1 // Copyright 2015 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 tree_test 12 13 import ( 14 "context" 15 "reflect" 16 "testing" 17 18 "github.com/cockroachdb/cockroach/pkg/settings/cluster" 19 "github.com/cockroachdb/cockroach/pkg/sql/parser" 20 _ "github.com/cockroachdb/cockroach/pkg/sql/sem/builtins" 21 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 22 "github.com/cockroachdb/cockroach/pkg/sql/types" 23 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 24 "github.com/stretchr/testify/require" 25 ) 26 27 // TestUnresolvedNameString tests the string representation of tree.UnresolvedName and thus tree.Name. 28 func TestUnresolvedNameString(t *testing.T) { 29 defer leaktest.AfterTest(t)() 30 testCases := []struct { 31 in, out string 32 }{ 33 {"*", `"*"`}, 34 // Non-reserved keyword. 35 {"DATABASE", `"DATABASE"`}, 36 {"dAtAbAse", `"dAtAbAse"`}, 37 // Reserved keyword. 38 {"SELECT", `"SELECT"`}, 39 {"sElEcT", `"sElEcT"`}, 40 // Ident format: starts with [a-zA-Z_] or extended ascii, 41 // and is then followed by [a-zA-Z0-9$_] or extended ascii. 42 {"foo$09", "foo$09"}, 43 {"_Ab10", `"_Ab10"`}, 44 // Everything else quotes the string and escapes double quotes. 45 {".foobar", `".foobar"`}, 46 {`".foobar"`, `""".foobar"""`}, 47 {`\".foobar\"`, `"\"".foobar\"""`}, 48 } 49 50 for _, tc := range testCases { 51 q := tree.UnresolvedName{NumParts: 1, Parts: tree.NameParts{tc.in}} 52 if q.String() != tc.out { 53 t.Errorf("expected q.String() == %q, got %q", tc.out, q.String()) 54 } 55 } 56 } 57 58 // TestCastFromNull checks every type can be cast from NULL. 59 func TestCastFromNull(t *testing.T) { 60 defer leaktest.AfterTest(t)() 61 for _, typ := range types.Scalar { 62 castExpr := tree.CastExpr{Expr: tree.DNull, Type: typ} 63 res, err := castExpr.Eval(nil) 64 require.NoError(t, err) 65 require.Equal(t, tree.DNull, res) 66 } 67 } 68 69 // TestExprString verifies that converting an expression to a string and back 70 // doesn't change the (normalized) expression. 71 func TestExprString(t *testing.T) { 72 defer leaktest.AfterTest(t)() 73 defer tree.MockNameTypes(map[string]*types.T{ 74 "a": types.Bool, 75 "b": types.Bool, 76 "c": types.Bool, 77 "d": types.Bool, 78 "e": types.Bool, 79 "f": types.Int, 80 "g": types.Int, 81 "h": types.Int, 82 "i": types.Int, 83 "j": types.Int, 84 "k": types.Int, 85 })() 86 testExprs := []string{ 87 `a AND b`, 88 `a AND b OR c`, 89 `(a AND b) OR c`, 90 `a AND (b OR c)`, 91 `a AND NOT ((b OR c) AND (d AND e))`, 92 `~-f`, 93 `-2*(f+3)*g`, 94 `f&g<<(g+h)&i > 0 AND (g&i)+h>>(i&f) > 0`, 95 `f&(g<<g+h)&i > 0 AND g&(i+h>>i)&f > 0`, 96 `f = g|h`, 97 `f != g|h`, 98 `NOT a AND b`, 99 `NOT (a AND b)`, 100 `(NOT a) AND b`, 101 `NOT (a = NOT b = c)`, 102 `NOT NOT a = b`, 103 `NOT NOT (a = b)`, 104 `NOT (NOT a) < b`, 105 `NOT (NOT a = b)`, 106 `(NOT NOT a) >= b`, 107 `(a OR (g BETWEEN (h+i) AND (j+k))) AND b`, 108 `(1 >= 2) IS OF (BOOL)`, 109 `(1 >= 2) = (2 IS OF (BOOL))`, 110 `count(1) FILTER (WHERE true)`, 111 } 112 ctx := context.Background() 113 for _, exprStr := range testExprs { 114 expr, err := parser.ParseExpr(exprStr) 115 if err != nil { 116 t.Fatalf("%s: %v", exprStr, err) 117 } 118 typedExpr, err := tree.TypeCheck(ctx, expr, nil, types.Any) 119 if err != nil { 120 t.Fatalf("%s: %v", expr, err) 121 } 122 // str may differ than exprStr (we may be adding some parens). 123 str := typedExpr.String() 124 expr2, err := parser.ParseExpr(str) 125 if err != nil { 126 t.Fatalf("%s: %v", exprStr, err) 127 } 128 typedExpr2, err := tree.TypeCheck(ctx, expr2, nil, types.Any) 129 if err != nil { 130 t.Fatalf("%s: %v", expr2, err) 131 } 132 // Verify that when we stringify the expression again, the string is the 133 // same. This is important because we don't want cycles of parsing and 134 // printing an expression to keep adding parens. 135 if str2 := typedExpr2.String(); str != str2 { 136 t.Errorf("Print/parse/print cycle changes the string: `%s` vs `%s`", str, str2) 137 } 138 // Compare the normalized expressions. 139 ctx := tree.NewTestingEvalContext(cluster.MakeTestingClusterSettings()) 140 defer ctx.Mon.Stop(context.Background()) 141 normalized, err := ctx.NormalizeExpr(typedExpr) 142 if err != nil { 143 t.Fatalf("%s: %v", exprStr, err) 144 } 145 normalized2, err := ctx.NormalizeExpr(typedExpr2) 146 if err != nil { 147 t.Fatalf("%s: %v", exprStr, err) 148 } 149 if !reflect.DeepEqual(tree.StripMemoizedFuncs(normalized), tree.StripMemoizedFuncs(normalized2)) { 150 t.Errorf("normalized expressions differ\n"+ 151 "original: %s\n"+ 152 "intermediate: %s\n"+ 153 "before: %#v\n"+ 154 "after: %#v", exprStr, str, normalized, normalized2) 155 } 156 } 157 } 158 159 func TestStripParens(t *testing.T) { 160 defer leaktest.AfterTest(t)() 161 testExprs := []struct { 162 in, out string 163 }{ 164 {`1`, `1`}, 165 {`(1)`, `1`}, 166 {`((1))`, `1`}, 167 {`(1) + (2)`, `(1) + (2)`}, 168 {`((1) + (2))`, `(1) + (2)`}, 169 } 170 for i, test := range testExprs { 171 expr, err := parser.ParseExpr(test.in) 172 if err != nil { 173 t.Fatalf("%d: %v", i, err) 174 } 175 stripped := tree.StripParens(expr) 176 if str := stripped.String(); str != test.out { 177 t.Fatalf("%d: expected tree.StripParens(%s) = %s, but found %s", i, test.in, test.out, str) 178 } 179 } 180 }