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() {}