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 = &params
   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  }