github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/optbuilder/locking.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 optbuilder
    12  
    13  import "github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    14  
    15  // lockingSpec maintains a collection of FOR [KEY] UPDATE/SHARE items that apply
    16  // to a given scope. Locking clauses can be applied to the lockingSpec as they
    17  // come into scope in the AST. The lockingSpec can then be consolidated down to
    18  // a single row-level locking specification for different tables to determine
    19  // how scans over those tables should perform row-level locking, if at all.
    20  //
    21  // A SELECT statement may contain zero, one, or more than one row-level locking
    22  // clause. Each of these clauses consist of two different properties.
    23  //
    24  // The first property is locking strength (see tree.LockingStrength). Locking
    25  // strength represents the degree of protection that a row-level lock provides.
    26  // The stronger the lock, the more protection it provides for the lock holder
    27  // but the more restrictive it is to concurrent transactions attempting to
    28  // access the same row. In order from weakest to strongest, the lock strength
    29  // variants are:
    30  //
    31  //   FOR KEY SHARE
    32  //   FOR SHARE
    33  //   FOR NO KEY UPDATE
    34  //   FOR UPDATE
    35  //
    36  // The second property is the locking wait policy (see tree.LockingWaitPolicy).
    37  // A locking wait policy represents the policy a table scan uses to interact
    38  // with row-level locks held by other transactions. Unlike locking strength,
    39  // locking wait policy is optional to specify in a locking clause. If not
    40  // specified, the policy defaults to blocking and waiting for locks to become
    41  // available. The non-standard policies instruct scans to take other approaches
    42  // to handling locks held by other transactions. These non-standard policies
    43  // are:
    44  //
    45  //   SKIP LOCKED
    46  //   NOWAIT
    47  //
    48  // In addition to these two properties, locking clauses can contain an optional
    49  // list of target relations. When provided, the locking clause applies only to
    50  // those relations in the target list. When not provided, the locking clause
    51  // applies to all relations in the current scope.
    52  //
    53  // Put together, a complex locking spec might look like:
    54  //
    55  //   SELECT ... FROM ... FOR SHARE NOWAIT FOR UPDATE OF t1, t2
    56  //
    57  // which would be represented as:
    58  //
    59  //   [ {ForShare, LockWaitError, []}, {ForUpdate, LockWaitBlock, [t1, t2]} ]
    60  //
    61  type lockingSpec []*tree.LockingItem
    62  
    63  // noRowLocking indicates that no row-level locking has been specified.
    64  var noRowLocking lockingSpec
    65  
    66  // isSet returns whether the spec contains any row-level locking modes.
    67  func (lm lockingSpec) isSet() bool {
    68  	return len(lm) != 0
    69  }
    70  
    71  // get returns the first row-level locking mode in the spec. If the spec was the
    72  // outcome of filter operation, this will be the only locking mode in the spec.
    73  func (lm lockingSpec) get() *tree.LockingItem {
    74  	if lm.isSet() {
    75  		return lm[0]
    76  	}
    77  	return nil
    78  }
    79  
    80  // apply merges the locking clause into the current locking spec. The effect of
    81  // applying new locking clauses to an existing spec is always to strengthen the
    82  // locking approaches it represents, either through increasing locking strength
    83  // or using more aggressive wait policies.
    84  func (lm *lockingSpec) apply(locking tree.LockingClause) {
    85  	// TODO(nvanbenschoten): If we wanted to eagerly prune superfluous locking
    86  	// items so that they don't need to get merged away in each call to filter,
    87  	// this would be the place to do it. We don't expect to see multiple FOR
    88  	// UPDATE clauses very often, so it's probably not worth it.
    89  	if len(*lm) == 0 {
    90  		// NB: avoid allocation, but also prevent future mutation of AST.
    91  		l := len(locking)
    92  		*lm = lockingSpec(locking[:l:l])
    93  		return
    94  	}
    95  	*lm = append(*lm, locking...)
    96  }
    97  
    98  // filter returns the desired row-level locking mode for the specified table as
    99  // a new consolidated lockingSpec. If no matching locking mode is found then the
   100  // resulting spec will remain un-set. If a matching locking mode for the table
   101  // is found then the resulting spec will contain exclusively that locking mode
   102  // and will no longer be restricted to specific target relations.
   103  func (lm lockingSpec) filter(alias tree.Name) lockingSpec {
   104  	var ret lockingSpec
   105  	var copied bool
   106  	updateRet := func(li *tree.LockingItem, len1 []*tree.LockingItem) {
   107  		if ret == nil && len(li.Targets) == 0 {
   108  			// Fast-path. We don't want the resulting spec to include targets,
   109  			// so we only allow this if the item we want to copy has none.
   110  			ret = len1
   111  			return
   112  		}
   113  		if !copied {
   114  			retCpy := make(lockingSpec, 1)
   115  			retCpy[0] = new(tree.LockingItem)
   116  			if len(ret) == 1 {
   117  				*retCpy[0] = *ret[0]
   118  			}
   119  			ret = retCpy
   120  			copied = true
   121  		}
   122  		// From https://www.postgresql.org/docs/12/sql-select.html#SQL-FOR-UPDATE-SHARE
   123  		// > If the same table is mentioned (or implicitly affected) by more
   124  		// > than one locking clause, then it is processed as if it was only
   125  		// > specified by the strongest one.
   126  		ret[0].Strength = ret[0].Strength.Max(li.Strength)
   127  		// > Similarly, a table is processed as NOWAIT if that is specified in
   128  		// > any of the clauses affecting it. Otherwise, it is processed as SKIP
   129  		// > LOCKED if that is specified in any of the clauses affecting it.
   130  		ret[0].WaitPolicy = ret[0].WaitPolicy.Max(li.WaitPolicy)
   131  	}
   132  
   133  	for i, li := range lm {
   134  		len1 := lm[i : i+1 : i+1]
   135  		if len(li.Targets) == 0 {
   136  			// If no targets are specified, the clause affects all tables.
   137  			updateRet(li, len1)
   138  		} else {
   139  			// If targets are specified, the clause affects only those tables.
   140  			for _, target := range li.Targets {
   141  				if target.ObjectName == alias {
   142  					updateRet(li, len1)
   143  					break
   144  				}
   145  			}
   146  		}
   147  	}
   148  	return ret
   149  }
   150  
   151  // withoutTargets returns a new lockingSpec with all locking clauses that apply
   152  // only to a subset of tables removed.
   153  func (lm lockingSpec) withoutTargets() lockingSpec {
   154  	return lm.filter("")
   155  }
   156  
   157  // ignoreLockingForCTE is a placeholder for the following comment:
   158  //
   159  // We intentionally do not propagate any row-level locking information from the
   160  // current scope to the CTE. This mirrors Postgres' behavior. It also avoids a
   161  // number of awkward questions like how row-level locking would interact with
   162  // mutating common table expressions.
   163  //
   164  // From https://www.postgresql.org/docs/12/sql-select.html#SQL-FOR-UPDATE-SHARE
   165  // > these clauses do not apply to WITH queries referenced by the primary query.
   166  // > If you want row locking to occur within a WITH query, specify a locking
   167  // > clause within the WITH query.
   168  func (lm lockingSpec) ignoreLockingForCTE() {}