github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/planhook.go (about)

     1  // Copyright 2016 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/catalog/lease"
    17  	"github.com/cockroachdb/cockroach/pkg/sql/catalog/resolver"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/sessiondata"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    21  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    22  )
    23  
    24  // planHookFn is a function that can intercept a statement being planned and
    25  // provide an alternate implementation. It's primarily intended to allow
    26  // implementation of certain sql statements to live outside of the sql package.
    27  //
    28  // To intercept a statement the function should return a non-nil function for
    29  // `fn` as well as the appropriate sqlbase.ResultColumns describing the results
    30  // it will return (if any). If the hook plan requires sub-plans to be planned
    31  // and started by the usual machinery (e.g. to run a subquery), it must return
    32  // then as well. `fn` will be called in a goroutine during the `Start` phase of
    33  // plan execution.
    34  type planHookFn func(
    35  	context.Context, tree.Statement, PlanHookState,
    36  ) (fn PlanHookRowFn, header sqlbase.ResultColumns, subplans []planNode, avoidBuffering bool, err error)
    37  
    38  // PlanHookRowFn describes the row-production for hook-created plans. The
    39  // channel argument is used to return results to the plan's runner. It's
    40  // a blocking channel, so implementors should be careful to only use blocking
    41  // sends on it when necessary. Any subplans returned by the hook when initially
    42  // called are passed back, planned and started, for the the RowFn's use.
    43  //
    44  //TODO(dt): should this take runParams like a normal planNode.Next?
    45  type PlanHookRowFn func(context.Context, []planNode, chan<- tree.Datums) error
    46  
    47  var planHooks []planHookFn
    48  
    49  // wrappedPlanHookFn is similar to planHookFn but returns an existing plan type.
    50  // Additionally, it takes a context.
    51  type wrappedPlanHookFn func(
    52  	context.Context, tree.Statement, PlanHookState,
    53  ) (planNode, error)
    54  
    55  var wrappedPlanHooks []wrappedPlanHookFn
    56  
    57  func (p *planner) RunParams(ctx context.Context) runParams {
    58  	return runParams{ctx, p.ExtendedEvalContext(), p}
    59  }
    60  
    61  // PlanHookState exposes the subset of planner needed by plan hooks.
    62  // We pass this as one interface, rather than individually passing each field or
    63  // interface as we find we need them, to avoid churn in the planHookFn sig and
    64  // the hooks that implement it.
    65  //
    66  // The PlanHookState is used by modules that are under the CCL. Since the OSS
    67  // modules cannot depend on the CCL modules, the CCL modules need to inform the
    68  // planner when they should be invoked (via plan hooks). The only way for the
    69  // CCL statements to get access to a "planner" is through this PlanHookState
    70  // that gets passed back due to this inversion of roles.
    71  type PlanHookState interface {
    72  	resolver.SchemaResolver
    73  	RunParams(ctx context.Context) runParams
    74  	ExtendedEvalContext() *extendedEvalContext
    75  	SessionData() *sessiondata.SessionData
    76  	ExecCfg() *ExecutorConfig
    77  	DistSQLPlanner() *DistSQLPlanner
    78  	LeaseMgr() *lease.Manager
    79  	TypeAsString(ctx context.Context, e tree.Expr, op string) (func() (string, error), error)
    80  	TypeAsStringArray(ctx context.Context, e tree.Exprs, op string) (func() ([]string, error), error)
    81  	TypeAsStringOpts(
    82  		ctx context.Context, opts tree.KVOptions, optsValidate map[string]KVStringOptValidate,
    83  	) (func() (map[string]string, error), error)
    84  	User() string
    85  	AuthorizationAccessor
    86  	// The role create/drop call into OSS code to reuse plan nodes.
    87  	// TODO(mberhault): it would be easier to just pass a planner to plan hooks.
    88  	GetAllRoles(ctx context.Context) (map[string]bool, error)
    89  	BumpRoleMembershipTableVersion(ctx context.Context) error
    90  	EvalAsOfTimestamp(ctx context.Context, asOf tree.AsOfClause) (hlc.Timestamp, error)
    91  	ResolveUncachedDatabaseByName(
    92  		ctx context.Context, dbName string, required bool) (*UncachedDatabaseDescriptor, error)
    93  	ResolveMutableTableDescriptor(
    94  		ctx context.Context, tn *TableName, required bool, requiredType resolver.ResolveRequiredType,
    95  	) (table *MutableTableDescriptor, err error)
    96  	ShowCreate(
    97  		ctx context.Context, dbPrefix string, allDescs []sqlbase.Descriptor, desc *sqlbase.TableDescriptor, displayOptions ShowCreateDisplayOptions,
    98  	) (string, error)
    99  }
   100  
   101  // AddPlanHook adds a hook used to short-circuit creating a planNode from a
   102  // tree.Statement. If the func returned by the hook is non-nil, it is used to
   103  // construct a planNode that runs that func in a goroutine during Start.
   104  //
   105  // See PlanHookState comments for information about why plan hooks are needed.
   106  func AddPlanHook(f planHookFn) {
   107  	planHooks = append(planHooks, f)
   108  }
   109  
   110  // ClearPlanHooks is used by tests to clear out any mocked out plan hooks that
   111  // were registered.
   112  func ClearPlanHooks() {
   113  	planHooks = nil
   114  }
   115  
   116  // hookFnNode is a planNode implemented in terms of a function. It begins the
   117  // provided function during Start and serves the results it returns over the
   118  // channel.
   119  type hookFnNode struct {
   120  	optColumnsSlot
   121  
   122  	f        PlanHookRowFn
   123  	header   sqlbase.ResultColumns
   124  	subplans []planNode
   125  
   126  	run hookFnRun
   127  }
   128  
   129  // hookFnRun contains the run-time state of hookFnNode during local execution.
   130  type hookFnRun struct {
   131  	resultsCh chan tree.Datums
   132  	errCh     chan error
   133  
   134  	row tree.Datums
   135  }
   136  
   137  func (f *hookFnNode) startExec(params runParams) error {
   138  	// TODO(dan): Make sure the resultCollector is set to flush after every row.
   139  	f.run.resultsCh = make(chan tree.Datums)
   140  	f.run.errCh = make(chan error)
   141  	go func() {
   142  		err := f.f(params.ctx, f.subplans, f.run.resultsCh)
   143  		select {
   144  		case <-params.ctx.Done():
   145  		case f.run.errCh <- err:
   146  		}
   147  		close(f.run.errCh)
   148  		close(f.run.resultsCh)
   149  	}()
   150  	return nil
   151  }
   152  
   153  func (f *hookFnNode) Next(params runParams) (bool, error) {
   154  	select {
   155  	case <-params.ctx.Done():
   156  		return false, params.ctx.Err()
   157  	case err := <-f.run.errCh:
   158  		return false, err
   159  	case f.run.row = <-f.run.resultsCh:
   160  		return true, nil
   161  	}
   162  }
   163  
   164  func (f *hookFnNode) Values() tree.Datums { return f.run.row }
   165  
   166  func (f *hookFnNode) Close(ctx context.Context) {
   167  	for _, sub := range f.subplans {
   168  		sub.Close(ctx)
   169  	}
   170  }