github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/schemaexpr/check_constraint.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 "bytes" 15 "context" 16 "fmt" 17 "sort" 18 19 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 20 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 21 "github.com/cockroachdb/cockroach/pkg/sql/types" 22 ) 23 24 // CheckConstraintBuilder creates sqlbase.TableDescriptor_CheckConstraints from 25 // tree.CheckConstraintTableDefs. See Build for more details. 26 type CheckConstraintBuilder struct { 27 ctx context.Context 28 tableName tree.TableName 29 desc *sqlbase.MutableTableDescriptor 30 semaCtx *tree.SemaContext 31 32 // inUseNames keeps track of names that have been generated previously 33 // by generateName or marked as in-use by MarkNameInUse. It is used to 34 // prevent the CheckConstraintBuilder from generating constraints with 35 // non-unique names. 36 inUseNames map[string]struct{} 37 } 38 39 // NewCheckConstraintBuilder returns a CheckConstraintBuilder struct that can 40 // be used to build sqlbase.TableDescriptor_CheckConstraints. See Build for more 41 // details. 42 func NewCheckConstraintBuilder( 43 ctx context.Context, 44 tableName tree.TableName, 45 desc *sqlbase.MutableTableDescriptor, 46 semaCtx *tree.SemaContext, 47 ) CheckConstraintBuilder { 48 return CheckConstraintBuilder{ 49 ctx: ctx, 50 tableName: tableName, 51 desc: desc, 52 semaCtx: semaCtx, 53 inUseNames: make(map[string]struct{}), 54 } 55 } 56 57 // MarkNameInUse marks a name as in-use, preventing subsequent calls to Build 58 // from generating a check constraint with the same name. 59 func (b *CheckConstraintBuilder) MarkNameInUse(name string) { 60 b.inUseNames[name] = struct{}{} 61 } 62 63 // Build validates the input tree.CheckConstraintTableDef and, if valid, returns 64 // a sqlbase.TableDescriptor_CheckConstraint. If the input constraint does not 65 // have a name, Build generates a name based on the variables referenced in the 66 // check expression. 67 // 68 // A check constraint expression is valid if the following are true: 69 // 70 // - It results in a boolean. 71 // - It refers only to columns in the table. 72 // - It does not include subqueries. 73 // - It does not include aggregate, window, or set returning functions. 74 // 75 // Note that mutable functions are currently allowed, unlike in partial index 76 // predicates, but using them can lead to unexpected behavior. 77 func (b *CheckConstraintBuilder) Build( 78 c *tree.CheckConstraintTableDef, 79 ) (*sqlbase.TableDescriptor_CheckConstraint, error) { 80 name := string(c.Name) 81 82 if name == "" { 83 var err error 84 name, err = b.generateUniqueName(c.Expr) 85 if err != nil { 86 return nil, err 87 } 88 } 89 90 source := sqlbase.NewSourceInfoForSingleTable( 91 b.tableName, sqlbase.ResultColumnsFromColDescs( 92 b.desc.GetID(), 93 b.desc.TableDesc().AllNonDropColumns(), 94 ), 95 ) 96 97 // Strip the database and table names from any qualified column names. 98 expr, err := DequalifyColumnRefs(b.ctx, source, c.Expr) 99 if err != nil { 100 return nil, err 101 } 102 103 // Replace the column variables with dummyColumns so that they can be 104 // type-checked. 105 replacedExpr, colIDsUsed, err := b.desc.ReplaceColumnVarsInExprWithDummies(expr) 106 if err != nil { 107 return nil, err 108 } 109 110 // Verify that the expression results in a boolean and does not use 111 // invalid functions. 112 typedExpr, err := sqlbase.SanitizeVarFreeExpr( 113 b.ctx, 114 replacedExpr, 115 types.Bool, 116 "CHECK", 117 b.semaCtx, 118 true, /* allowImpure */ 119 ) 120 if err != nil { 121 return nil, err 122 } 123 124 // Collect and sort the column IDs referenced in the check expression. 125 colIDs := make(sqlbase.ColumnIDs, 0, colIDsUsed.Len()) 126 colIDsUsed.ForEach(func(id int) { 127 colIDs = append(colIDs, sqlbase.ColumnID(id)) 128 }) 129 sort.Sort(colIDs) 130 131 return &sqlbase.TableDescriptor_CheckConstraint{ 132 Expr: tree.Serialize(typedExpr), 133 Name: name, 134 ColumnIDs: colIDs, 135 Hidden: c.Hidden, 136 }, nil 137 } 138 139 // nameInUse returns true if the given name is already inuse. 140 func (b *CheckConstraintBuilder) nameInUse(name string) bool { 141 _, ok := b.inUseNames[name] 142 return ok 143 } 144 145 // generateUniqueName returns a name for the given check constraint expression 146 // that is unique among other names generated by the CheckConstraintBuilder. It 147 // is also unique from any constraint names marked as in-use via MarkNameInUse. 148 func (b *CheckConstraintBuilder) generateUniqueName(expr tree.Expr) (string, error) { 149 name, err := b.DefaultName(expr) 150 if err != nil { 151 return "", err 152 } 153 154 // If the generated name isn't unique, add a number to the end. Increment 155 // the number until a unique name is found. 156 if b.nameInUse(name) { 157 i := 1 158 for { 159 numberedName := fmt.Sprintf("%s%d", name, i) 160 if !b.nameInUse(numberedName) { 161 name = numberedName 162 break 163 } 164 i++ 165 } 166 } 167 168 b.MarkNameInUse(name) 169 170 return name, nil 171 } 172 173 // DefaultName creates a check constraint name based on the columns referenced 174 // in the check expression. The format is "check_col1_col2...". If columns are 175 // duplicated in the expression, they will be duplicated in the name. 176 // 177 // For example: 178 // 179 // CHECK (a < 0) => check_a 180 // CHECK (a < 0 AND b = 'foo') => check_a_b 181 // CHECK (a < 0 AND b = 'foo' AND a < 10) => check_a_b_a 182 // 183 // Note that the generated name is not guaranteed to be unique among the other 184 // constraints of the table. 185 func (b *CheckConstraintBuilder) DefaultName(expr tree.Expr) (string, error) { 186 var nameBuf bytes.Buffer 187 nameBuf.WriteString("check") 188 189 err := iterColDescriptors(b.desc, expr, func(c *sqlbase.ColumnDescriptor) error { 190 nameBuf.WriteByte('_') 191 nameBuf.WriteString(c.Name) 192 return nil 193 }) 194 if err != nil { 195 return "", err 196 } 197 198 return nameBuf.String(), nil 199 }