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  }