vitess.io/vitess@v0.16.2/go/vt/vtctl/workflow/vexec/query_plan.go (about) 1 /* 2 Copyright 2021 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package vexec 18 19 import ( 20 "context" 21 "fmt" 22 "sync" 23 24 "vitess.io/vitess/go/vt/concurrency" 25 "vitess.io/vitess/go/vt/log" 26 "vitess.io/vitess/go/vt/sqlparser" 27 "vitess.io/vitess/go/vt/topo" 28 "vitess.io/vitess/go/vt/vterrors" 29 "vitess.io/vitess/go/vt/vttablet/tmclient" 30 31 querypb "vitess.io/vitess/go/vt/proto/query" 32 ) 33 34 // QueryPlan defines the interface to executing a preprared vexec query on one 35 // or more tablets. Implementations should ensure that it is safe to call the 36 // various Execute* methods repeatedly and in multiple goroutines. 37 type QueryPlan interface { 38 // Execute executes the planned query on a single target. 39 Execute(ctx context.Context, target *topo.TabletInfo) (*querypb.QueryResult, error) 40 // ExecuteScatter executes the planned query on the specified targets concurrently, 41 // returning a mapping of the target tablet to a querypb.QueryResult. 42 ExecuteScatter(ctx context.Context, targets ...*topo.TabletInfo) (map[*topo.TabletInfo]*querypb.QueryResult, error) 43 } 44 45 // FixedQueryPlan wraps a planned query produced by a QueryPlanner. It executes 46 // the same query with the same bind vals, regardless of the target. 47 type FixedQueryPlan struct { 48 ParsedQuery *sqlparser.ParsedQuery 49 50 workflow string 51 tmc tmclient.TabletManagerClient 52 } 53 54 // Execute is part of the QueryPlan interface. 55 func (qp *FixedQueryPlan) Execute(ctx context.Context, target *topo.TabletInfo) (qr *querypb.QueryResult, err error) { 56 if qp.ParsedQuery == nil { 57 return nil, fmt.Errorf("%w: call PlanQuery on a query planner first", ErrUnpreparedQuery) 58 } 59 60 targetAliasStr := target.AliasString() 61 62 defer func() { 63 if err != nil { 64 log.Warningf("Result on %v: %v", targetAliasStr, err) 65 return 66 } 67 }() 68 69 qr, err = qp.tmc.VReplicationExec(ctx, target.Tablet, qp.ParsedQuery.Query) 70 if err != nil { 71 return nil, err 72 } 73 return qr, nil 74 } 75 76 // ExecuteScatter is part of the QueryPlan interface. For a FixedQueryPlan, the 77 // exact same query is executed on each target, and errors from individual 78 // targets are aggregated into a singular error. 79 func (qp *FixedQueryPlan) ExecuteScatter(ctx context.Context, targets ...*topo.TabletInfo) (map[*topo.TabletInfo]*querypb.QueryResult, error) { 80 if qp.ParsedQuery == nil { 81 // This check is an "optimization" on error handling. We check here, 82 // even though we will check this during the individual Execute calls, 83 // so that we return one error, rather than the same error aggregated 84 // len(targets) times. 85 return nil, fmt.Errorf("%w: call PlanQuery on a query planner first", ErrUnpreparedQuery) 86 } 87 88 var ( 89 m sync.Mutex 90 wg sync.WaitGroup 91 rec concurrency.AllErrorRecorder 92 results = make(map[*topo.TabletInfo]*querypb.QueryResult, len(targets)) 93 ) 94 95 for _, target := range targets { 96 wg.Add(1) 97 98 go func(ctx context.Context, target *topo.TabletInfo) { 99 defer wg.Done() 100 101 qr, err := qp.Execute(ctx, target) 102 if err != nil { 103 rec.RecordError(err) 104 105 return 106 } 107 108 m.Lock() 109 defer m.Unlock() 110 111 results[target] = qr 112 }(ctx, target) 113 } 114 115 wg.Wait() 116 117 return results, rec.AggrError(vterrors.Aggregate) 118 } 119 120 // PerTargetQueryPlan implements the QueryPlan interface. Unlike FixedQueryPlan, 121 // this implementation implements different queries, keyed by tablet alias, on 122 // different targets. 123 // 124 // It is the callers responsibility to ensure that the shape of the QueryResult 125 // (i.e. fields returned) is consistent for each target's planned query, but 126 // this is not enforced. 127 type PerTargetQueryPlan struct { 128 ParsedQueries map[string]*sqlparser.ParsedQuery 129 130 tmc tmclient.TabletManagerClient 131 } 132 133 // Execute is part of the QueryPlan interface. 134 // 135 // It returns ErrUnpreparedQuery if there is no ParsedQuery for the target's 136 // tablet alias. 137 func (qp *PerTargetQueryPlan) Execute(ctx context.Context, target *topo.TabletInfo) (qr *querypb.QueryResult, err error) { 138 if qp.ParsedQueries == nil { 139 return nil, fmt.Errorf("%w: call PlanQuery on a query planner first", ErrUnpreparedQuery) 140 } 141 142 targetAliasStr := target.AliasString() 143 query, ok := qp.ParsedQueries[targetAliasStr] 144 if !ok { 145 return nil, fmt.Errorf("%w: no prepared query for target %s", ErrUnpreparedQuery, targetAliasStr) 146 } 147 148 defer func() { 149 if err != nil { 150 log.Warningf("Result on %v: %v", targetAliasStr, err) 151 return 152 } 153 }() 154 155 qr, err = qp.tmc.VReplicationExec(ctx, target.Tablet, query.Query) 156 if err != nil { 157 return nil, err 158 } 159 160 return qr, nil 161 } 162 163 // ExecuteScatter is part of the QueryPlan interface. 164 func (qp *PerTargetQueryPlan) ExecuteScatter(ctx context.Context, targets ...*topo.TabletInfo) (map[*topo.TabletInfo]*querypb.QueryResult, error) { 165 if qp.ParsedQueries == nil { 166 // This check is an "optimization" on error handling. We check here, 167 // even though we will check this during the individual Execute calls, 168 // so that we return one error, rather than the same error aggregated 169 // len(targets) times. 170 return nil, fmt.Errorf("%w: call PlanQuery on a query planner first", ErrUnpreparedQuery) 171 } 172 173 var ( 174 m sync.Mutex 175 wg sync.WaitGroup 176 rec concurrency.AllErrorRecorder 177 results = make(map[*topo.TabletInfo]*querypb.QueryResult, len(targets)) 178 ) 179 180 for _, target := range targets { 181 wg.Add(1) 182 183 go func(ctx context.Context, target *topo.TabletInfo) { 184 defer wg.Done() 185 186 qr, err := qp.Execute(ctx, target) 187 if err != nil { 188 rec.RecordError(err) 189 190 return 191 } 192 193 m.Lock() 194 defer m.Unlock() 195 196 results[target] = qr 197 }(ctx, target) 198 } 199 200 wg.Wait() 201 202 return results, rec.AggrError(vterrors.Aggregate) 203 }