github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/schemaexpr/computed_column.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 16 "github.com/cockroachdb/cockroach/pkg/sql/parser" 17 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 18 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 19 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 20 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 21 "github.com/cockroachdb/errors" 22 ) 23 24 // ComputedColumnValidator validates that an expression is a valid computed 25 // column. See Validate for more details. 26 type ComputedColumnValidator struct { 27 ctx context.Context 28 desc *sqlbase.MutableTableDescriptor 29 semaCtx *tree.SemaContext 30 } 31 32 // NewComputedColumnValidator returns an ComputedColumnValidator struct that can 33 // be used to validate computed columns. See Validate for more details. 34 func NewComputedColumnValidator( 35 ctx context.Context, desc *sqlbase.MutableTableDescriptor, semaCtx *tree.SemaContext, 36 ) ComputedColumnValidator { 37 return ComputedColumnValidator{ 38 ctx: ctx, 39 desc: desc, 40 semaCtx: semaCtx, 41 } 42 } 43 44 // Validate verifies that an expression is a valid computed column expression. 45 // If it is not valid, an error is returned. 46 // 47 // A computed column expression is valid if all of the following are true: 48 // 49 // - It does not have a default value. 50 // - It does not reference other computed columns. 51 // 52 // It additionally updates the target computed column with the serialized 53 // typed expression. 54 // TODO(mgartner): Add unit tests for Validate. 55 func (v *ComputedColumnValidator) Validate(d *tree.ColumnTableDef) error { 56 if d.HasDefaultExpr() { 57 return pgerror.New( 58 pgcode.InvalidTableDefinition, 59 "computed columns cannot have default values", 60 ) 61 } 62 63 // TODO(mgartner): Use util.FastIntSet here instead. 64 dependencies := make(map[sqlbase.ColumnID]struct{}) 65 // First, check that no column in the expression is a computed column. 66 err := iterColDescriptors(v.desc, d.Computed.Expr, func(c *sqlbase.ColumnDescriptor) error { 67 if c.IsComputed() { 68 return pgerror.New(pgcode.InvalidTableDefinition, 69 "computed columns cannot reference other computed columns") 70 } 71 dependencies[c.ID] = struct{}{} 72 73 return nil 74 }) 75 if err != nil { 76 return err 77 } 78 79 // TODO(justin,bram): allow depending on columns like this. We disallow it 80 // for now because cascading changes must hook into the computed column 81 // update path. 82 for i := range v.desc.OutboundFKs { 83 fk := &v.desc.OutboundFKs[i] 84 for _, id := range fk.OriginColumnIDs { 85 if _, ok := dependencies[id]; !ok { 86 // We don't depend on this column. 87 continue 88 } 89 for _, action := range []sqlbase.ForeignKeyReference_Action{ 90 fk.OnDelete, 91 fk.OnUpdate, 92 } { 93 switch action { 94 case sqlbase.ForeignKeyReference_CASCADE, 95 sqlbase.ForeignKeyReference_SET_NULL, 96 sqlbase.ForeignKeyReference_SET_DEFAULT: 97 return pgerror.New(pgcode.InvalidTableDefinition, 98 "computed columns cannot reference non-restricted FK columns") 99 } 100 } 101 } 102 } 103 104 // Replace the column variables with dummyColumns so that they can be 105 // type-checked. 106 replacedExpr, _, err := v.desc.ReplaceColumnVarsInExprWithDummies(d.Computed.Expr) 107 if err != nil { 108 return err 109 } 110 111 // Resolve the type of the computed column expression. 112 defType, err := tree.ResolveType(v.ctx, d.Type, v.semaCtx.GetTypeResolver()) 113 if err != nil { 114 return err 115 } 116 117 // Check that the type of the expression is of type defType and that there 118 // are no variable expressions (besides dummyColumnItems) and no impure 119 // functions. 120 typedExpr, err := sqlbase.SanitizeVarFreeExpr( 121 v.ctx, 122 replacedExpr, 123 defType, 124 "computed column", 125 v.semaCtx, 126 false /* allowImpure */) 127 if err != nil { 128 return err 129 } 130 131 // Get the column that this definition points to. 132 targetCol, _, err := v.desc.FindColumnByName(d.Name) 133 if err != nil { 134 return err 135 } 136 // In order to safely serialize user defined types and their members, we 137 // need to serialize the typed expression here. 138 s := tree.Serialize(typedExpr) 139 targetCol.ComputeExpr = &s 140 141 return nil 142 } 143 144 // ValidateNoDependents verifies that the input column is not dependent on a 145 // computed column. The function errs if any existing computed columns or 146 // computed columns being added reference the given column. 147 // TODO(mgartner): Add unit tests for ValidateNoDependents. 148 func (v *ComputedColumnValidator) ValidateNoDependents(col *sqlbase.ColumnDescriptor) error { 149 checkComputed := func(c *sqlbase.ColumnDescriptor) error { 150 if !c.IsComputed() { 151 return nil 152 } 153 154 expr, err := parser.ParseExpr(*c.ComputeExpr) 155 if err != nil { 156 // At this point, we should be able to parse the computed expression. 157 return errors.WithAssertionFailure(err) 158 } 159 160 return iterColDescriptors(v.desc, expr, func(colVar *sqlbase.ColumnDescriptor) error { 161 if colVar.ID == col.ID { 162 return pgerror.Newf( 163 pgcode.InvalidColumnReference, 164 "column %q is referenced by computed column %q", 165 col.Name, 166 c.Name, 167 ) 168 } 169 return nil 170 }) 171 } 172 173 for i := range v.desc.Columns { 174 if err := checkComputed(&v.desc.Columns[i]); err != nil { 175 return err 176 } 177 } 178 179 for i := range v.desc.Mutations { 180 mut := &v.desc.Mutations[i] 181 mutCol := mut.GetColumn() 182 if mut.Direction == sqlbase.DescriptorMutation_ADD && mutCol != nil { 183 if err := checkComputed(mutCol); err != nil { 184 return err 185 } 186 } 187 } 188 189 return nil 190 }