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  }