github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/schemaexpr/check_constraint_test.go (about) 1 // Copyright 2020 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 schemaexpr 12 13 import ( 14 "context" 15 "testing" 16 17 "github.com/cockroachdb/cockroach/pkg/sql/parser" 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 TestCheckConstraintBuilder_Build(t *testing.T) { 24 ctx := context.Background() 25 semaCtx := tree.MakeSemaContext() 26 27 // Trick to get the init() for the builtins package to run. 28 _ = builtins.AllBuiltinNames 29 30 database := tree.Name("foo") 31 table := tree.Name("bar") 32 tn := tree.MakeTableName(database, table) 33 34 desc := testTableDesc( 35 string(table), 36 []testCol{{"a", types.Bool}, {"b", types.Int}}, 37 []testCol{{"c", types.String}}, 38 ) 39 40 builder := NewCheckConstraintBuilder(ctx, tn, &desc, &semaCtx) 41 builder.MarkNameInUse("check_a3") 42 43 testData := []struct { 44 name string 45 expr string 46 expectedValid bool 47 expectedExpr string 48 expectedName string 49 }{ 50 // Respect custom names. 51 {"chk_1", "a", true, "a", "chk_1"}, 52 53 // Use unique default names when there is no custom name. 54 {"", "a", true, "a", "check_a"}, 55 {"", "a", true, "a", "check_a1"}, 56 {"", "a", true, "a", "check_a2"}, 57 {"", "a AND b = 0", true, "a AND (b = 0:::INT8)", "check_a_b"}, 58 {"", "a AND b = 1", true, "a AND (b = 1:::INT8)", "check_a_b1"}, 59 {"", "a AND b = 1", true, "a AND (b = 1:::INT8)", "check_a_b2"}, 60 61 // Respect that "check_a3" has been marked, so the next check constraint 62 // with "a" should be "check_a4". 63 {"", "a", true, "a", "check_a4"}, 64 {"", "a", true, "a", "check_a5"}, 65 66 // Allow expressions that result in a bool. 67 {"ck", "a", true, "a", "ck"}, 68 {"ck", "b = 0", true, "b = 0:::INT8", "ck"}, 69 {"ck", "a AND b = 0", true, "a AND (b = 0:::INT8)", "ck"}, 70 {"ck", "a IS NULL", true, "a IS NULL", "ck"}, 71 {"ck", "b IN (1, 2)", true, "b IN (1:::INT8, 2:::INT8)", "ck"}, 72 73 // Allow immutable functions. 74 {"ck", "abs(b) > 0", true, "abs(b) > 0:::INT8", "ck"}, 75 {"ck", "c || c = 'foofoo'", true, "(c || c) = 'foofoo':::STRING", "ck"}, 76 {"ck", "lower(c) = 'bar'", true, "lower(c) = 'bar':::STRING", "ck"}, 77 78 // Allow mutable functions. 79 {"ck", "b > random()", true, "b > random()", "ck"}, 80 81 // Disallow references to columns not in the table. 82 {"", "d", false, "", ""}, 83 {"", "t.a", false, "", ""}, 84 85 // Disallow expressions that do not result in a bool. 86 {"", "b", false, "", ""}, 87 {"", "abs(b)", false, "", ""}, 88 {"", "lower(c)", false, "", ""}, 89 90 // Disallow subqueries. 91 {"", "exists(select 1)", false, "", ""}, 92 {"", "b IN (select 1)", false, "", ""}, 93 94 // Disallow aggregate, window, and set returning functions. 95 {"", "sum(b) > 10", false, "", ""}, 96 {"", "row_number() OVER () > 1", false, "", ""}, 97 {"", "generate_series(1, 1) > 2", false, "", ""}, 98 99 // Dequalify column names. 100 {"ck", "bar.a", true, "a", "ck"}, 101 {"ck", "foo.bar.a", true, "a", "ck"}, 102 {"ck", "bar.b = 0", true, "b = 0:::INT8", "ck"}, 103 {"ck", "foo.bar.b = 0", true, "b = 0:::INT8", "ck"}, 104 {"ck", "bar.a AND foo.bar.b = 0", true, "a AND (b = 0:::INT8)", "ck"}, 105 } 106 107 for _, d := range testData { 108 t.Run(d.expr, func(t *testing.T) { 109 expr, err := parser.ParseExpr(d.expr) 110 if err != nil { 111 t.Fatalf("%s: unexpected error: %s", d.expr, err) 112 } 113 114 ckDef := &tree.CheckConstraintTableDef{Name: tree.Name(d.name), Expr: expr} 115 116 ck, err := builder.Build(ckDef) 117 118 if !d.expectedValid { 119 if err == nil { 120 t.Fatalf("%s: expected invalid expression, but was valid", d.expr) 121 } 122 // The input expression is invalid so there is no need to check 123 // the output ck. 124 return 125 } 126 127 if err != nil { 128 t.Fatalf("%s: expected valid expression, but found error: %s", d.expr, err) 129 } 130 131 if ck.Name != d.expectedName || ck.Expr != d.expectedExpr { 132 t.Errorf( 133 `%s: expected "%s CHECK %s", got "%s CHECK %s"`, 134 d.expr, 135 d.expectedName, 136 d.expectedExpr, 137 ck.Name, 138 ck.Expr, 139 ) 140 } 141 }) 142 } 143 } 144 145 func TestCheckConstraintBuilder_DefaultName(t *testing.T) { 146 ctx := context.Background() 147 semaCtx := tree.MakeSemaContext() 148 149 database := tree.Name("foo") 150 table := tree.Name("bar") 151 tn := tree.MakeTableName(database, table) 152 153 desc := testTableDesc( 154 string(table), 155 []testCol{{"a", types.Bool}, {"b", types.Int}}, 156 []testCol{{"c", types.String}, {"d", types.String}, {"e", types.String}}, 157 ) 158 159 builder := NewCheckConstraintBuilder(ctx, tn, &desc, &semaCtx) 160 161 testData := []struct { 162 expr string 163 expected string 164 }{ 165 {"a > 0", "check_a"}, 166 {"a > 0 AND b = 'foo'", "check_a_b"}, 167 {"a > 0 AND (b = 'foo' OR a < 20)", "check_a_b_a"}, 168 {"a > 0 AND a < 20", "check_a_a"}, 169 {"((a AND (b OR c)) AND d) OR e", "check_a_b_c_d_e"}, 170 } 171 172 for _, d := range testData { 173 t.Run(d.expr, func(t *testing.T) { 174 expr, err := parser.ParseExpr(d.expr) 175 if err != nil { 176 t.Fatalf("%s: unexpected error: %s", d.expr, err) 177 } 178 179 r, err := builder.DefaultName(expr) 180 if err != nil { 181 t.Fatalf("%s: expected success, but found error: %s", d.expr, err) 182 } 183 184 if r != d.expected { 185 t.Errorf("%s: expected %q, got %q", d.expr, d.expected, r) 186 } 187 }) 188 } 189 }