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  }