// Hash-join detection: the cost model that decides where the rewrite in// hashjoin.go applies. hashJoinOnce walks the stage list once, estimating// each stage's input/output cardinality and the cumulative estimate at// which every slot was first bound, then gates pivots on the thresholds// below -- a stage worth protecting from a multiplying re-expansion. All// decisions read estimated cardinality structure, never query identity.
package planimport// The decision thresholds are variables (not consts) so tests can force// the rewrite onto small fixture graphs and compare both execution paths.var (// HashJoinMinRows is the smallest estimated intermediate worth // protecting from a multiplying re-expansion.HashJoinMinRows = 1 << 16// HashJoinFanFactor is the minimum estimated output/input multiply of // the pivot stage before the rewrite is considered.HashJoinFanFactor = 64.0// HashJoinExtDivisor bounds the branch's external references: a slot // qualifies only when it was bound at a cumulative estimate at most // estIn(pivot)/HashJoinExtDivisor, which also bounds the number of // distinct memo tuples (re-builds) to the same fraction.HashJoinExtDivisor = 256.0// HashJoinMaxBuildRows caps the estimated materialized branch (a // memory bound; the win condition is the pivot gate's multiply).HashJoinMaxBuildRows = 1 << 22)// hashJoinStages applies the rewrite repeatedly until no pivot// qualifies -- a segment can hold several independent multiplying// branches (each pass consumes its pivot's ops, so the loop terminates).func hashJoinStages( []Stage, map[string]int, int, graph.Graph) []Stage {forrangelen() + 1 { := hashJoinOnce(, , , )if == nil {return } = }return}func hashJoinOnce( []Stage, map[string]int, int, graph.Graph) []Stage { := make(map[int]float64, +8) := make(map[int]int, +8)for := range { [] = 1 [] = -1 } := func(, int, float64) {if < 0 {return }if , := []; ! { [] = [] = } } := 1.0 := make([]float64, len()) := make([]float64, len())// Slots proven to hold exactly one concrete node (a resolved seek): // later hops from these price at the node's real degree, so the pivot // and build gates decide on fact where the type average would lie. := make(map[int]graph.NodeID)for , := range { [] = switch s := .(type) {case *MatchStage:for := range .Ops { := &.Ops[]if .Kind != OpScan || .Source.Kind == ScanArg {continue }if , := resolveScanNodes(&.Source, .Labels, .Props, ); && len() == 1 { [.Slot] = [0] } } , := matchEstAnchored(, , , )for := range .Ops { := &.Ops[]if .Kind == OpScan {if .Source.Kind != ScanArg { (.Slot, , float64([])) } } elseif !.Rebind { (.To, , float64([])) }if .Kind != OpScan { (.RelSlot, , float64([])) } }if .PathBind != nil { (.PathBind.PathSlot, , ) } = case *UnwindStage: *= unwindFanout (.OutSlot, , )case *SpStage: (.PathSlot, , )case *GateStage: (.Sp.PathSlot, , )for , := range .Derived { (.Slot, , ) }case *CallStage: (.NodeSlot, , ) (.ValueSlot, , ) (.DepthSlot, , )case *CallSubqueryStage:for , := range .OutSlots { (, , ) } } [] = }for := range { , := [].(*MatchStage)if ! || .Optional || .PathBind != nil {continue }if [] < float64(HashJoinMinRows) || [] < []*HashJoinFanFactor {continue }if := tryHashJoin(, , [], , , , , ); != nil {return } }returnnil}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.