github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/causetstore/petri/acyclic/causet/embedded/optimizer.go (about) 1 // Copyright 2020 WHTCORPS INC, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package embedded 15 16 import ( 17 "context" 18 "math" 19 20 "github.com/whtcorpsinc/BerolinaSQL/ast" 21 "github.com/whtcorpsinc/BerolinaSQL/auth" 22 "github.com/whtcorpsinc/errors" 23 "github.com/whtcorpsinc/milevadb/causet/property" 24 "github.com/whtcorpsinc/milevadb/config" 25 "github.com/whtcorpsinc/milevadb/dagger" 26 "github.com/whtcorpsinc/milevadb/memex" 27 "github.com/whtcorpsinc/milevadb/privilege" 28 "github.com/whtcorpsinc/milevadb/schemareplicant" 29 utilhint "github.com/whtcorpsinc/milevadb/soliton/hint" 30 "github.com/whtcorpsinc/milevadb/soliton/set" 31 "github.com/whtcorpsinc/milevadb/stochastikctx" 32 "github.com/whtcorpsinc/milevadb/types" 33 "go.uber.org/atomic" 34 ) 35 36 // OptimizeAstNode optimizes the query to a physical plan directly. 37 var OptimizeAstNode func(ctx context.Context, sctx stochastikctx.Context, node ast.Node, is schemareplicant.SchemaReplicant) (Causet, types.NameSlice, error) 38 39 // AllowCartesianProduct means whether milevadb allows cartesian join without equal conditions. 40 var AllowCartesianProduct = atomic.NewBool(true) 41 42 const ( 43 flagGcSubstitute uint64 = 1 << iota 44 flagPrunDeferredCausets 45 flagBuildKeyInfo 46 flagDecorrelate 47 flagEliminateAgg 48 flagEliminateProjection 49 flagMaxMinEliminate 50 flagPredicatePushDown 51 flagEliminateOuterJoin 52 flagPartitionProcessor 53 flagPushDownAgg 54 flagPushDownTopN 55 flagJoinReOrder 56 flagPrunDeferredCausetsAgain 57 ) 58 59 var optMemruleList = []logicalOptMemrule{ 60 &gcSubstituter{}, 61 &columnPruner{}, 62 &buildKeySolver{}, 63 &decorrelateSolver{}, 64 &aggregationEliminator{}, 65 &projectionEliminator{}, 66 &maxMinEliminator{}, 67 &pFIDelSolver{}, 68 &outerJoinEliminator{}, 69 &partitionProcessor{}, 70 &aggregationPushDownSolver{}, 71 &pushDownTopNOptimizer{}, 72 &joinReOrderSolver{}, 73 &columnPruner{}, // column pruning again at last, note it will mess up the results of buildKeySolver 74 } 75 76 // logicalOptMemrule means a logical optimizing rule, which contains decorrelate, pFIDel, column pruning, etc. 77 type logicalOptMemrule interface { 78 optimize(context.Context, LogicalCauset) (LogicalCauset, error) 79 name() string 80 } 81 82 // BuildLogicalCauset used to build logical plan from ast.Node. 83 func BuildLogicalCauset(ctx context.Context, sctx stochastikctx.Context, node ast.Node, is schemareplicant.SchemaReplicant) (Causet, types.NameSlice, error) { 84 sctx.GetStochastikVars().CausetID = 0 85 sctx.GetStochastikVars().CausetDeferredCausetID = 0 86 builder := NewCausetBuilder(sctx, is, &utilhint.BlockHintProcessor{}) 87 p, err := builder.Build(ctx, node) 88 if err != nil { 89 return nil, nil, err 90 } 91 return p, p.OutputNames(), err 92 } 93 94 // CheckPrivilege checks the privilege for a user. 95 func CheckPrivilege(activeRoles []*auth.RoleIdentity, pm privilege.Manager, vs []visitInfo) error { 96 for _, v := range vs { 97 if !pm.RequestVerification(activeRoles, v.EDB, v.causet, v.column, v.privilege) { 98 if v.err == nil { 99 return ErrPrivilegeCheckFail 100 } 101 return v.err 102 } 103 } 104 return nil 105 } 106 107 // CheckBlockLock checks the causet dagger. 108 func CheckBlockLock(ctx stochastikctx.Context, is schemareplicant.SchemaReplicant, vs []visitInfo) error { 109 if !config.BlockLockEnabled() { 110 return nil 111 } 112 checker := dagger.NewChecker(ctx, is) 113 for i := range vs { 114 err := checker.CheckBlockLock(vs[i].EDB, vs[i].causet, vs[i].privilege) 115 if err != nil { 116 return err 117 } 118 } 119 return nil 120 } 121 122 // DoOptimize optimizes a logical plan to a physical plan. 123 func DoOptimize(ctx context.Context, sctx stochastikctx.Context, flag uint64, logic LogicalCauset) (PhysicalCauset, float64, error) { 124 // if there is something after flagPrunDeferredCausets, do flagPrunDeferredCausetsAgain 125 if flag&flagPrunDeferredCausets > 0 && flag-flagPrunDeferredCausets > flagPrunDeferredCausets { 126 flag |= flagPrunDeferredCausetsAgain 127 } 128 logic, err := logicalOptimize(ctx, flag, logic) 129 if err != nil { 130 return nil, 0, err 131 } 132 if !AllowCartesianProduct.Load() && existsCartesianProduct(logic) { 133 return nil, 0, errors.Trace(ErrCartesianProductUnsupported) 134 } 135 planCounter := CausetCounterTp(sctx.GetStochastikVars().StmtCtx.StmtHints.ForceNthCauset) 136 if planCounter == 0 { 137 planCounter = -1 138 } 139 physical, cost, err := physicalOptimize(logic, &planCounter) 140 if err != nil { 141 return nil, 0, err 142 } 143 finalCauset := postOptimize(sctx, physical) 144 return finalCauset, cost, nil 145 } 146 147 func postOptimize(sctx stochastikctx.Context, plan PhysicalCauset) PhysicalCauset { 148 plan = eliminatePhysicalProjection(plan) 149 plan = injectExtraProjection(plan) 150 plan = eliminateUnionScanAndLock(sctx, plan) 151 plan = enableParallelApply(sctx, plan) 152 return plan 153 } 154 155 func enableParallelApply(sctx stochastikctx.Context, plan PhysicalCauset) PhysicalCauset { 156 if !sctx.GetStochastikVars().EnableParallelApply { 157 return plan 158 } 159 // the parallel apply has three limitation: 160 // 1. the parallel implementation now cannot keep order; 161 // 2. the inner child has to support clone; 162 // 3. if one Apply is in the inner side of another Apply, it cannot be parallel, for example: 163 // The topology of 3 Apply operators are A1(A2, A3), which means A2 is the outer child of A1 164 // while A3 is the inner child. Then A1 and A2 can be parallel and A3 cannot. 165 if apply, ok := plan.(*PhysicalApply); ok { 166 outerIdx := 1 - apply.InnerChildIdx 167 noOrder := len(apply.GetChildReqProps(outerIdx).Items) == 0 // limitation 1 168 _, err := SafeClone(apply.Children()[apply.InnerChildIdx]) 169 supportClone := err == nil // limitation 2 170 if noOrder && supportClone { 171 apply.Concurrency = sctx.GetStochastikVars().InterlockingDirectorateConcurrency 172 } 173 174 // because of the limitation 3, we cannot parallelize Apply operators in this Apply's inner size, 175 // so we only invoke recursively for its outer child. 176 apply.SetChild(outerIdx, enableParallelApply(sctx, apply.Children()[outerIdx])) 177 return apply 178 } 179 for i, child := range plan.Children() { 180 plan.SetChild(i, enableParallelApply(sctx, child)) 181 } 182 return plan 183 } 184 185 func logicalOptimize(ctx context.Context, flag uint64, logic LogicalCauset) (LogicalCauset, error) { 186 var err error 187 for i, rule := range optMemruleList { 188 // The order of flags is same as the order of optMemrule in the list. 189 // We use a bitmask to record which opt rules should be used. If the i-th bit is 1, it means we should 190 // apply i-th optimizing rule. 191 if flag&(1<<uint(i)) == 0 || isLogicalMemruleDisabled(rule) { 192 continue 193 } 194 logic, err = rule.optimize(ctx, logic) 195 if err != nil { 196 return nil, err 197 } 198 } 199 return logic, err 200 } 201 202 func isLogicalMemruleDisabled(r logicalOptMemrule) bool { 203 disabled := DefaultDisabledLogicalMemrulesList.Load().(set.StringSet).Exist(r.name()) 204 return disabled 205 } 206 207 func physicalOptimize(logic LogicalCauset, planCounter *CausetCounterTp) (PhysicalCauset, float64, error) { 208 if _, err := logic.recursiveDeriveStats(nil); err != nil { 209 return nil, 0, err 210 } 211 212 preparePossibleProperties(logic) 213 214 prop := &property.PhysicalProperty{ 215 TaskTp: property.RootTaskType, 216 ExpectedCnt: math.MaxFloat64, 217 } 218 219 logic.SCtx().GetStochastikVars().StmtCtx.TaskMapBakTS = 0 220 t, _, err := logic.findBestTask(prop, planCounter) 221 if err != nil { 222 return nil, 0, err 223 } 224 if *planCounter > 0 { 225 logic.SCtx().GetStochastikVars().StmtCtx.AppendWarning(errors.Errorf("The parameter of nth_plan() is out of range.")) 226 } 227 if t.invalid() { 228 return nil, 0, ErrInternal.GenWithStackByArgs("Can't find a proper physical plan for this query") 229 } 230 231 err = t.plan().ResolveIndices() 232 return t.plan(), t.cost(), err 233 } 234 235 // eliminateUnionScanAndLock set dagger property for PointGet and BatchPointGet and eliminates UnionScan and Lock. 236 func eliminateUnionScanAndLock(sctx stochastikctx.Context, p PhysicalCauset) PhysicalCauset { 237 var pointGet *PointGetCauset 238 var batchPointGet *BatchPointGetCauset 239 var physLock *PhysicalLock 240 var unionScan *PhysicalUnionScan 241 iteratePhysicalCauset(p, func(p PhysicalCauset) bool { 242 if len(p.Children()) > 1 { 243 return false 244 } 245 switch x := p.(type) { 246 case *PointGetCauset: 247 pointGet = x 248 case *BatchPointGetCauset: 249 batchPointGet = x 250 case *PhysicalLock: 251 physLock = x 252 case *PhysicalUnionScan: 253 unionScan = x 254 } 255 return true 256 }) 257 if pointGet == nil && batchPointGet == nil { 258 return p 259 } 260 if physLock == nil && unionScan == nil { 261 return p 262 } 263 if physLock != nil { 264 dagger, waitTime := getLockWaitTime(sctx, physLock.Lock) 265 if !dagger { 266 return p 267 } 268 if pointGet != nil { 269 pointGet.Lock = dagger 270 pointGet.LockWaitTime = waitTime 271 } else { 272 batchPointGet.Lock = dagger 273 batchPointGet.LockWaitTime = waitTime 274 } 275 } 276 return transformPhysicalCauset(p, func(p PhysicalCauset) PhysicalCauset { 277 if p == physLock { 278 return p.Children()[0] 279 } 280 if p == unionScan { 281 return p.Children()[0] 282 } 283 return p 284 }) 285 } 286 287 func iteratePhysicalCauset(p PhysicalCauset, f func(p PhysicalCauset) bool) { 288 if !f(p) { 289 return 290 } 291 for _, child := range p.Children() { 292 iteratePhysicalCauset(child, f) 293 } 294 } 295 296 func transformPhysicalCauset(p PhysicalCauset, f func(p PhysicalCauset) PhysicalCauset) PhysicalCauset { 297 for i, child := range p.Children() { 298 p.Children()[i] = transformPhysicalCauset(child, f) 299 } 300 return f(p) 301 } 302 303 func existsCartesianProduct(p LogicalCauset) bool { 304 if join, ok := p.(*LogicalJoin); ok && len(join.EqualConditions) == 0 { 305 return join.JoinType == InnerJoin || join.JoinType == LeftOuterJoin || join.JoinType == RightOuterJoin 306 } 307 for _, child := range p.Children() { 308 if existsCartesianProduct(child) { 309 return true 310 } 311 } 312 return false 313 } 314 315 // DefaultDisabledLogicalMemrulesList indicates the logical rules which should be banned. 316 var DefaultDisabledLogicalMemrulesList *atomic.Value 317 318 func init() { 319 memex.EvalAstExpr = evalAstExpr 320 memex.RewriteAstExpr = rewriteAstExpr 321 DefaultDisabledLogicalMemrulesList = new(atomic.Value) 322 DefaultDisabledLogicalMemrulesList.CausetStore(set.NewStringSet()) 323 }