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 }