github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/virtual_table.go (about) 1 // Copyright 2018 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 sql 12 13 import ( 14 "context" 15 16 "github.com/cockroachdb/cockroach/pkg/sql/opt/constraint" 17 "github.com/cockroachdb/cockroach/pkg/sql/opt/exec" 18 "github.com/cockroachdb/cockroach/pkg/sql/rowcontainer" 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 // virtualTableGenerator is the function signature for the virtualTableNode 25 // `next` property. Each time the virtualTableGenerator function is called, it 26 // returns a tree.Datums corresponding to the next row of the virtual schema 27 // table. If there is no next row (end of table is reached), then return (nil, 28 // nil). If there is an error, then return (nil, error). 29 type virtualTableGenerator func() (tree.Datums, error) 30 31 // cleanupFunc is a function to cleanup resources created by the generator. 32 type cleanupFunc func() 33 34 // rowPusher is an interface for lazy generators to push rows into 35 // and then suspend until the next row has been requested. 36 type rowPusher interface { 37 // pushRow pushes the input row to the receiver of the generator. It doesn't 38 // mutate the input row. It will block until the the data has been received 39 // and more data has been requested. Once pushRow returns, the caller is free 40 // to mutate the slice passed as input. The caller is not allowed to perform 41 // operations on a transaction while blocked on a call to pushRow. 42 // If pushRow returns an error, the caller must immediately return the error. 43 pushRow(...tree.Datum) error 44 } 45 46 // funcRowPusher implements rowPusher on functions. 47 type funcRowPusher func(...tree.Datum) error 48 49 func (f funcRowPusher) pushRow(datums ...tree.Datum) error { 50 return f(datums...) 51 } 52 53 type virtualTableGeneratorResponse struct { 54 datums tree.Datums 55 err error 56 } 57 58 // setupGenerator takes in a worker that generates rows eagerly and transforms 59 // it into a lazy row generator. It returns two functions: 60 // * next: A handle that can be called to generate a row from the worker. Next 61 // cannot be called once cleanup has been called. 62 // * cleanup: Performs all cleanup. This function must be called exactly once 63 // to ensure that resources are cleaned up. 64 func setupGenerator( 65 ctx context.Context, worker func(pusher rowPusher) error, 66 ) (next virtualTableGenerator, cleanup cleanupFunc) { 67 var cancel func() 68 ctx, cancel = context.WithCancel(ctx) 69 cleanup = cancel 70 71 // comm is the channel to manage communication between the row receiver 72 // and the generator. The row receiver notifies the worker to begin 73 // computation through comm, and the generator places rows to consume 74 // back into comm. 75 comm := make(chan virtualTableGeneratorResponse) 76 77 addRow := func(datums ...tree.Datum) error { 78 select { 79 case <-ctx.Done(): 80 return sqlbase.QueryCanceledError 81 case comm <- virtualTableGeneratorResponse{datums: datums}: 82 } 83 84 // Block until the next call to cleanup() or next(). This allows us to 85 // avoid issues with concurrent transaction usage if the worker is using 86 // a transaction. Otherwise, worker could proceed running operations after 87 // a call to next() has returned. That could result in the main operator 88 // chain using the transaction while the worker is also running. This 89 // makes it so that the worker can only run while next() is being called, 90 // which effectively gives ownership of the transaction usage over to the 91 // worker, and then back to the next() caller after it is done. 92 select { 93 case <-ctx.Done(): 94 return sqlbase.QueryCanceledError 95 case <-comm: 96 } 97 return nil 98 } 99 100 go func() { 101 // We wait until a call to next before starting the worker. This prevents 102 // concurrent transaction usage during the startup phase. We also have to 103 // wait on done here if cleanup is called before any calls to next() to 104 // avoid leaking this goroutine. Lastly, we check if the context has 105 // been canceled before any rows are even requested. 106 select { 107 case <-ctx.Done(): 108 return 109 case <-comm: 110 } 111 err := worker(funcRowPusher(addRow)) 112 // If the query was canceled, next() will already return a 113 // QueryCanceledError, so just exit here. 114 if errors.Is(err, sqlbase.QueryCanceledError) { 115 return 116 } 117 118 // Notify that we are done sending rows. 119 select { 120 case <-ctx.Done(): 121 return 122 case comm <- virtualTableGeneratorResponse{err: err}: 123 } 124 }() 125 126 next = func() (tree.Datums, error) { 127 // Notify the worker to begin computing a row. 128 select { 129 case comm <- virtualTableGeneratorResponse{}: 130 case <-ctx.Done(): 131 return nil, sqlbase.QueryCanceledError 132 } 133 134 // Wait for the row to be sent. 135 select { 136 case <-ctx.Done(): 137 return nil, sqlbase.QueryCanceledError 138 case resp := <-comm: 139 return resp.datums, resp.err 140 } 141 } 142 return next, cleanup 143 } 144 145 // virtualTableNode is a planNode that constructs its rows by repeatedly 146 // invoking a virtualTableGenerator function. 147 type virtualTableNode struct { 148 columns sqlbase.ResultColumns 149 next virtualTableGenerator 150 cleanup func() 151 currentRow tree.Datums 152 } 153 154 func (p *planner) newVirtualTableNode( 155 columns sqlbase.ResultColumns, next virtualTableGenerator, cleanup func(), 156 ) *virtualTableNode { 157 return &virtualTableNode{ 158 columns: columns, 159 next: next, 160 cleanup: cleanup, 161 } 162 } 163 164 func (n *virtualTableNode) startExec(runParams) error { 165 return nil 166 } 167 168 func (n *virtualTableNode) Next(params runParams) (bool, error) { 169 row, err := n.next() 170 if err != nil { 171 return false, err 172 } 173 n.currentRow = row 174 return row != nil, nil 175 } 176 177 func (n *virtualTableNode) Values() tree.Datums { 178 return n.currentRow 179 } 180 181 func (n *virtualTableNode) Close(ctx context.Context) { 182 if n.cleanup != nil { 183 n.cleanup() 184 } 185 } 186 187 // vTableLookupJoinNode implements lookup join into a virtual table that has a 188 // virtual index on the equality columns. For each row of the input, a virtual 189 // table index lookup is performed, and the rows are joined together. 190 type vTableLookupJoinNode struct { 191 input planNode 192 193 dbName string 194 db *sqlbase.DatabaseDescriptor 195 table *sqlbase.TableDescriptor 196 index *sqlbase.IndexDescriptor 197 // eqCol is the single equality column ordinal into the lookup table. Virtual 198 // indexes only support a single indexed column currently. 199 eqCol int 200 virtualTableEntry virtualDefEntry 201 202 joinType sqlbase.JoinType 203 204 // columns is the join's output schema. 205 columns sqlbase.ResultColumns 206 // pred contains the join's on condition, if any. 207 pred *joinPredicate 208 // inputCols is the schema of the input to this lookup join. 209 inputCols sqlbase.ResultColumns 210 // vtableCols is the schema of the virtual table we're looking up rows from, 211 // before any projection. 212 vtableCols sqlbase.ResultColumns 213 // lookupCols is the projection on vtableCols to apply. 214 lookupCols exec.TableColumnOrdinalSet 215 216 // run contains the runtime state of this planNode. 217 run struct { 218 // row contains the next row to output. 219 row tree.Datums 220 // rows contains the next rows to output, except for row. 221 rows *rowcontainer.RowContainer 222 keyCtx constraint.KeyContext 223 224 // indexKeyDatums is scratch space used to construct the index key to 225 // look up in the vtable. 226 indexKeyDatums []tree.Datum 227 // params is set to the current value of runParams on each call to Next. 228 // We need to save this in this awkward way because of constraints on the 229 // interfaces used in virtual table row generation. 230 params *runParams 231 } 232 } 233 234 var _ planNode = &vTableLookupJoinNode{} 235 var _ rowPusher = &vTableLookupJoinNode{} 236 237 // startExec implements the planNode interface. 238 func (v *vTableLookupJoinNode) startExec(params runParams) error { 239 v.run.keyCtx = constraint.KeyContext{EvalCtx: params.EvalContext()} 240 v.run.rows = rowcontainer.NewRowContainer(params.EvalContext().Mon.MakeBoundAccount(), 241 sqlbase.ColTypeInfoFromResCols(v.columns), 0) 242 v.run.indexKeyDatums = make(tree.Datums, len(v.columns)) 243 var err error 244 v.db, err = params.p.LogicalSchemaAccessor().GetDatabaseDesc( 245 params.ctx, 246 params.p.txn, 247 params.p.ExecCfg().Codec, 248 v.dbName, 249 tree.DatabaseLookupFlags{Required: true, AvoidCached: params.p.avoidCachedDescriptors}, 250 ) 251 return err 252 } 253 254 // Next implements the planNode interface. 255 func (v *vTableLookupJoinNode) Next(params runParams) (bool, error) { 256 // Keep a pointer to runParams around so we can reference it later from 257 // pushRow, which can't take any extra arguments. 258 v.run.params = ¶ms 259 for { 260 // Check if there are any rows left to emit from the last input row. 261 if v.run.rows.Len() > 0 { 262 v.run.row = v.run.rows.At(0) 263 v.run.rows.PopFirst() 264 return true, nil 265 } 266 267 // Lookup more rows from the virtual table. 268 ok, err := v.input.Next(params) 269 if !ok || err != nil { 270 return ok, err 271 } 272 inputRow := v.input.Values() 273 var span constraint.Span 274 datum := inputRow[v.eqCol] 275 // Generate an index constraint from the equality column of the input. 276 key := constraint.MakeKey(datum) 277 span.Init(key, constraint.IncludeBoundary, key, constraint.IncludeBoundary) 278 var idxConstraint constraint.Constraint 279 idxConstraint.InitSingleSpan(&v.run.keyCtx, &span) 280 281 // Create the generation function for the index constraint. 282 genFunc := v.virtualTableEntry.makeConstrainedRowsGenerator(params.ctx, 283 params.p, v.db, v.index, 284 v.run.indexKeyDatums, 285 v.table.ColumnIdxMap(), 286 &idxConstraint, 287 v.vtableCols, 288 ) 289 // Add the input row to the left of the scratch row. 290 v.run.row = append(v.run.row[:0], inputRow...) 291 // Finally, we're ready to do the lookup. This invocation will push all of 292 // the looked-up rows into v.run.rows. 293 if err := genFunc(v); err != nil { 294 return false, err 295 } 296 if v.run.rows.Len() == 0 && v.joinType == sqlbase.LeftOuterJoin { 297 // No matches - construct an outer match. 298 v.run.row = v.run.row[:len(v.inputCols)] 299 for i := len(inputRow); i < len(v.columns); i++ { 300 v.run.row = append(v.run.row, tree.DNull) 301 } 302 return true, nil 303 } 304 } 305 } 306 307 // pushRow implements the rowPusher interface. 308 func (v *vTableLookupJoinNode) pushRow(lookedUpRow ...tree.Datum) error { 309 // Reset our output row to just the contents of the input row. 310 v.run.row = v.run.row[:len(v.inputCols)] 311 // Append the looked up row to the right of the input row. 312 for i, ok := v.lookupCols.Next(0); ok; i, ok = v.lookupCols.Next(i + 1) { 313 // Subtract 1 from the requested column position, to avoid the virtual 314 // table's fake primary key which won't be present in the row. 315 v.run.row = append(v.run.row, lookedUpRow[i-1]) 316 } 317 // Run the predicate and exit if we don't match, or if there was an error. 318 if ok, err := v.pred.eval(v.run.params.EvalContext(), 319 v.run.row[:len(v.inputCols)], 320 v.run.row[len(v.inputCols):]); !ok || err != nil { 321 return err 322 } 323 _, err := v.run.rows.AddRow(v.run.params.ctx, v.run.row) 324 return err 325 } 326 327 // Values implements the planNode interface. 328 func (v *vTableLookupJoinNode) Values() tree.Datums { 329 return v.run.row 330 } 331 332 // Close implements the planNode interface. 333 func (v *vTableLookupJoinNode) Close(ctx context.Context) { 334 v.input.Close(ctx) 335 v.run.rows.Close(ctx) 336 }