github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/row/fk_table_lookup.go (about) 1 // Copyright 2019 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 row 12 13 import ( 14 "context" 15 16 "github.com/cockroachdb/cockroach/pkg/sql/catalog" 17 "github.com/cockroachdb/cockroach/pkg/sql/privilege" 18 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 19 ) 20 21 // The facilities in this file serve as interface between the FK 22 // planning code and the SQL schema. They provide a cache of the 23 // mapping between table ID and table metadata. 24 // 25 // Only the table metadata used for FK work are considered here. 26 // Because CASCADE actions can affect arbitrary many tables, possibly 27 // in cycles, the analysis algorithm to load the metadata uses a queue 28 // (tableLookupQueue) instead of a naive recursion. 29 // 30 31 // 32 // ------- interface between prepare and execution of FK work -------- 33 // 34 35 // FkTableMetadata maps table IDs to looked up descriptors or, for tables that 36 // exist but are not yet public/leasable, entries with just the IsAdding flag. 37 // 38 // This is populated by the lookup queue (below) and used as input to 39 // the FK existence checkers and cascading actions. 40 // 41 // TODO(knz): the redundancy between this struct and the code in other 42 // packages (sql, sqlbase) is troubling! Some of this should be 43 // factored. 44 type FkTableMetadata map[TableID]catalog.TableEntry 45 46 // 47 // ------- table metadata lookup logic, used at start of query execution ------- 48 // 49 50 // TableID is an alias for sqlbase.TableID (table IDs). 51 type TableID = sqlbase.ID 52 53 // tableLookupQueue is the facility responsible for loading all 54 // the table metadata used by FK work into a FkTableMetadata. 55 // 56 // The main lookup loop in MakeFkMetadata repeats as follows: run 57 // dequeue() once, inspects the table, queue()s zero or more FK 58 // constraints for further lookups. The lookup stops 59 // when the queue becomes empty. 60 type tableLookupQueue struct { 61 // queue contains the remaining lookups to perform. 62 queue []tableLookupQueueElement 63 64 // alreadyChecked notes which tables / constraints have already been 65 // looked up, to avoid performing the same lookup work twice. 66 alreadyChecked map[TableID]map[FKCheckType]struct{} 67 68 // result contains the result of the overall lookup work. 69 result FkTableMetadata 70 71 // tblLookupFn is used to look up individual tables by ID. This 72 // is typically provided by the caller, e.g. from the functions 73 // in the `sql` package. 74 tblLookupFn TableLookupFunction 75 76 // privCheckFn is used to verify a table's privileges. This is 77 // typically provided by the caller, e.g. from the functions in the 78 // `sql` package. 79 privCheckFn CheckPrivilegeFunction 80 81 // analyzeExprFn is used to perform semantic analysis on scalar 82 // expressions. This is not used for FK work directly but needed 83 // during lookup to initialize the CHECK constraint helper in each 84 // TableEntry object. 85 analyzeExprFn sqlbase.AnalyzeExprFunction 86 } 87 88 // tableLookupQueueElement describes one unit of work in the lookup 89 // queue. 90 type tableLookupQueueElement struct { 91 // tableEntry is the metadata of the table to check for FK 92 // constraints. 93 tableEntry catalog.TableEntry 94 95 // usage is the type of mutation for which to look up additional 96 // metadata. At the top level this is the type of SQL statement 97 // performing a mutation. Then when there are CASCADE clauses 98 // this is used to indicate the type of CASCADE action. 99 usage FKCheckType 100 } 101 102 // FKCheckType indicates the type of mutation that triggers FK work 103 // (delete, insert, or both). 104 type FKCheckType int 105 106 const ( 107 // CheckDeletes checks if rows reference a changed value. 108 CheckDeletes FKCheckType = iota 109 // CheckInserts checks if a new value references an existing row. 110 CheckInserts 111 // CheckUpdates checks all references (CheckDeletes+CheckInserts). 112 CheckUpdates 113 ) 114 115 // TableLookupFunction is the function type used by MakeFkMetadata 116 // that will perform the actual lookup of table metadata. 117 type TableLookupFunction func(context.Context, TableID) (catalog.TableEntry, error) 118 119 // NoLookup is a stub that can be used to not actually fetch metadata. 120 // This can be used when the FK work is initialized from a pre-populated 121 // FkTableMetadata map. 122 func NoLookup(_ context.Context, _ TableID) (catalog.TableEntry, error) { 123 return catalog.TableEntry{}, nil 124 } 125 126 // CheckPrivilegeFunction is the function type used by MakeFkMetadata that will 127 // check the privileges of the current user to access specific tables. 128 type CheckPrivilegeFunction func(context.Context, sqlbase.DescriptorProto, privilege.Kind) error 129 130 // NoCheckPrivilege is a stub that can be used to not actually verify privileges. 131 // This can be used when the FK work is initialized from a pre-populated 132 // FkTableMetadata map. 133 func NoCheckPrivilege(_ context.Context, _ sqlbase.DescriptorProto, _ privilege.Kind) error { 134 return nil 135 } 136 137 // getTable retrieves one table's metadata during FK work preparation. 138 // A cached TableEntry, if one exists, is reused; otherwise it is 139 // created and initialized. 140 func (q *tableLookupQueue) getTable( 141 ctx context.Context, tableID TableID, 142 ) (catalog.TableEntry, error) { 143 // Do we already have an entry for this table? 144 if tableEntry, exists := q.result[tableID]; exists { 145 // Yes, simply reuse it. 146 return tableEntry, nil 147 } 148 149 // We don't have this table yet. 150 151 // Ask the caller to retrieve it for us. 152 tableEntry, err := q.tblLookupFn(ctx, tableID) 153 if err != nil { 154 return catalog.TableEntry{}, err 155 } 156 if !tableEntry.IsAdding && tableEntry.Desc != nil { 157 // If we have a real table, we need first to verify the user has permission. 158 if err := q.privCheckFn(ctx, tableEntry.Desc, privilege.SELECT); err != nil { 159 return catalog.TableEntry{}, err 160 } 161 162 // All is fine. Simply prepare the CHECK helper for when there are 163 // CASCADE actions. 164 // 165 // TODO(knz): the CHECK helper is always prepared here, even when 166 // there is no CASCADE work to perform. This should be moved to a 167 // different place. 168 checkHelper, err := sqlbase.NewEvalCheckHelper(ctx, q.analyzeExprFn, tableEntry.Desc) 169 if err != nil { 170 return catalog.TableEntry{}, err 171 } 172 tableEntry.CheckHelper = checkHelper 173 } 174 175 // Remember for next time. 176 q.result[tableID] = tableEntry 177 178 return tableEntry, nil 179 } 180 181 // enqueue prepares the lookup work for a given table. 182 func (q *tableLookupQueue) enqueue(ctx context.Context, tableID TableID, usage FKCheckType) error { 183 // Lookup the table. 184 tableEntry, err := q.getTable(ctx, tableID) 185 if err != nil { 186 return err 187 } 188 189 // Don't enqueue if lookup returns an empty tableEntry. This just means that 190 // there is no need to walk any further. 191 if tableEntry.Desc == nil { 192 return nil 193 } 194 195 // Only enqueue checks that haven't been performed yet. 196 if alreadyCheckByTableID, exists := q.alreadyChecked[tableID]; exists { 197 if _, existsInner := alreadyCheckByTableID[usage]; existsInner { 198 return nil 199 } 200 } else { 201 q.alreadyChecked[tableID] = make(map[FKCheckType]struct{}) 202 } 203 204 // Remember we've done this check already for later. 205 q.alreadyChecked[tableID][usage] = struct{}{} 206 207 // If the table is being added, there's no need to check it. 208 if tableEntry.IsAdding { 209 return nil 210 } 211 212 // Verify the user has privilege to perform the operations. 213 switch usage { 214 // We only need to check the privileges for CASCADE actions here: 215 // the privileges related to the main mutation statement are checked 216 // already in that mutation's planning code. 217 // Also, there is no CASCADE action that can insert new rows. 218 case CheckDeletes: 219 if err := q.privCheckFn(ctx, tableEntry.Desc, privilege.DELETE); err != nil { 220 return err 221 } 222 case CheckUpdates: 223 if err := q.privCheckFn(ctx, tableEntry.Desc, privilege.UPDATE); err != nil { 224 return err 225 } 226 } 227 228 // Queue more lookup processing. 229 (*q).queue = append((*q).queue, tableLookupQueueElement{tableEntry: tableEntry, usage: usage}) 230 231 return nil 232 } 233 234 // dequeue retrieves the next item in the queue (and pops it). 235 func (q *tableLookupQueue) dequeue() (catalog.TableEntry, FKCheckType, bool) { 236 if len((*q).queue) == 0 { 237 return catalog.TableEntry{}, 0, false 238 } 239 elem := (*q).queue[0] 240 (*q).queue = (*q).queue[1:] 241 return elem.tableEntry, elem.usage, true 242 }