github.com/dolthub/go-mysql-server@v0.18.0/sql/memo/memo.go (about) 1 // Copyright 2022 Dolthub, 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 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package memo 16 17 import ( 18 "fmt" 19 "strings" 20 21 "github.com/dolthub/go-mysql-server/sql" 22 "github.com/dolthub/go-mysql-server/sql/expression" 23 "github.com/dolthub/go-mysql-server/sql/plan" 24 "github.com/dolthub/go-mysql-server/sql/transform" 25 ) 26 27 type GroupId uint16 28 type TableId uint16 29 30 type TableAndColumn struct { 31 tableName string 32 columnName string 33 } 34 35 // Memo collects a forest of query plans structured by logical and 36 // physical equivalency. Logically equivalent plans, represented by 37 // an exprGroup, produce the same rows (possibly unordered) and schema. 38 // Physical plans are stored in a linked list within an expression group. 39 type Memo struct { 40 cnt uint16 41 root *ExprGroup 42 43 hints *joinHints 44 45 c Coster 46 statsProv sql.StatsProvider 47 Ctx *sql.Context 48 scope *plan.Scope 49 scopeLen int 50 51 TableProps *tableProps 52 } 53 54 func NewMemo(ctx *sql.Context, stats sql.StatsProvider, s *plan.Scope, scopeLen int, cost Coster) *Memo { 55 return &Memo{ 56 Ctx: ctx, 57 c: cost, 58 statsProv: stats, 59 scope: s, 60 scopeLen: scopeLen, 61 TableProps: newTableProps(), 62 hints: &joinHints{}, 63 } 64 } 65 66 type MemoErr struct { 67 Err error 68 } 69 70 func (m *Memo) HandleErr(err error) { 71 panic(MemoErr{Err: err}) 72 } 73 74 func (m *Memo) Root() *ExprGroup { 75 return m.root 76 } 77 78 func (m *Memo) StatsProvider() sql.StatsProvider { 79 return m.statsProv 80 } 81 82 // newExprGroup creates a new logical expression group to encapsulate the 83 // action of a SQL clause. 84 // TODO: this is supposed to deduplicate logically equivalent table scans 85 // and scalar expressions, replacing references with a pointer. Currently 86 // a hacky format to quickly support memoizing join trees. 87 func (m *Memo) NewExprGroup(rel exprType) *ExprGroup { 88 m.cnt++ 89 id := GroupId(m.cnt) 90 grp := newExprGroup(m, id, rel) 91 92 if s, ok := rel.(SourceRel); ok { 93 m.TableProps.addTable(s.Name(), id) 94 } 95 return grp 96 } 97 98 func (m *Memo) memoizeSourceRel(rel SourceRel) *ExprGroup { 99 grp := m.NewExprGroup(rel) 100 return grp 101 } 102 103 func (m *Memo) getTableId(table string) (GroupId, bool) { 104 return m.TableProps.GetId(table) 105 } 106 107 func (m *Memo) MemoizeLeftJoin(grp, left, right *ExprGroup, op plan.JoinType, filter []sql.Expression) *ExprGroup { 108 newJoin := &LeftJoin{ 109 JoinBase: &JoinBase{ 110 relBase: &relBase{}, 111 Left: left, 112 Right: right, 113 Op: op, 114 Filter: filter, 115 }, 116 } 117 // todo intern relExprs? add to appropriate group? 118 if grp == nil { 119 return m.NewExprGroup(newJoin) 120 } 121 newJoin.g = grp 122 grp.Prepend(newJoin) 123 return grp 124 } 125 126 func (m *Memo) MemoizeInnerJoin(grp, left, right *ExprGroup, op plan.JoinType, filter []sql.Expression) *ExprGroup { 127 newJoin := &InnerJoin{ 128 JoinBase: &JoinBase{ 129 relBase: &relBase{}, 130 Left: left, 131 Right: right, 132 Op: op, 133 Filter: filter, 134 }, 135 } 136 // todo intern relExprs? add to appropriate group? 137 if grp == nil { 138 return m.NewExprGroup(newJoin) 139 } 140 newJoin.g = grp 141 grp.Prepend(newJoin) 142 return grp 143 } 144 145 func (m *Memo) MemoizeLookupJoin(grp, left, right *ExprGroup, op plan.JoinType, filter []sql.Expression, lookup *IndexScan) *ExprGroup { 146 if right.RelProps.reqIdxCols.Difference(lookup.Index.set).Len() > 0 { 147 // the index lookup does not cover the requested RHS indexScan columns, 148 // so this physical plan is invalid. 149 return grp 150 } 151 newJoin := &LookupJoin{ 152 JoinBase: &JoinBase{ 153 relBase: &relBase{}, 154 Left: left, 155 Right: right, 156 Op: op.AsLookup(), 157 Filter: filter, 158 }, 159 Lookup: lookup, 160 } 161 162 if grp == nil { 163 return m.NewExprGroup(newJoin) 164 } 165 newJoin.g = grp 166 grp.Prepend(newJoin) 167 168 if isInjectiveLookup(lookup.Index, newJoin.JoinBase, lookup.Table.Expressions(), lookup.Table.NullMask()) { 169 newJoin.Injective = true 170 } 171 172 return grp 173 } 174 175 func (m *Memo) MemoizeHashJoin(grp *ExprGroup, join *JoinBase, toExpr, fromExpr []sql.Expression) *ExprGroup { 176 if join.Right.RelProps.reqIdxCols.Len() > 0 { 177 // HASH_JOIN's RHS will be a table scan, so this physical 178 // plan will not provide the requested indexScan 179 return grp 180 } 181 newJoin := &HashJoin{ 182 JoinBase: join.Copy(), 183 LeftAttrs: toExpr, 184 RightAttrs: fromExpr, 185 } 186 newJoin.Op = newJoin.Op.AsHash() 187 188 if grp == nil { 189 return m.NewExprGroup(newJoin) 190 } 191 newJoin.g = grp 192 grp.Prepend(newJoin) 193 194 return grp 195 } 196 197 // MemoizeConcatLookupJoin creates a lookup join over a set of disjunctions. 198 // If a LOOKUP_JOIN simulates x = v1, a concat lookup performs x in (v1, v2, v3, ...) 199 func (m *Memo) MemoizeConcatLookupJoin(grp, left, right *ExprGroup, op plan.JoinType, filter []sql.Expression, lookups []*IndexScan) *ExprGroup { 200 newJoin := &ConcatJoin{ 201 JoinBase: &JoinBase{ 202 relBase: &relBase{}, 203 Left: left, 204 Right: right, 205 Op: op.AsLookup(), 206 Filter: filter, 207 }, 208 Concat: lookups, 209 } 210 211 if grp == nil { 212 return m.NewExprGroup(newJoin) 213 } 214 newJoin.g = grp 215 grp.Prepend(newJoin) 216 return grp 217 } 218 219 func (m *Memo) MemoizeRangeHeapJoin(grp, left, right *ExprGroup, op plan.JoinType, filter []sql.Expression, rangeHeap *RangeHeap) *ExprGroup { 220 newJoin := &RangeHeapJoin{ 221 JoinBase: &JoinBase{ 222 relBase: &relBase{}, 223 Left: left, 224 Right: right, 225 Op: op, 226 Filter: filter, 227 }, 228 RangeHeap: rangeHeap, 229 } 230 newJoin.RangeHeap.Parent = newJoin.JoinBase 231 232 if grp == nil { 233 return m.NewExprGroup(newJoin) 234 } 235 newJoin.g = grp 236 grp.Prepend(newJoin) 237 return grp 238 } 239 240 func (m *Memo) MemoizeMergeJoin(grp, left, right *ExprGroup, lIdx, rIdx *IndexScan, op plan.JoinType, filter []sql.Expression, swapCmp bool) *ExprGroup { 241 rel := &MergeJoin{ 242 JoinBase: &JoinBase{ 243 relBase: &relBase{}, 244 Op: op, 245 Filter: filter, 246 Left: left, 247 Right: right, 248 }, 249 InnerScan: lIdx, 250 OuterScan: rIdx, 251 SwapCmp: swapCmp, 252 } 253 254 comparer, ok := filter[0].(*expression.Equals) 255 if !ok { 256 err := sql.ErrMergeJoinExpectsComparerFilters.New(filter[0]) 257 m.HandleErr(err) 258 } 259 260 var leftCompareExprs []sql.Expression 261 var rightCompareExprs []sql.Expression 262 263 leftTuple, isTuple := comparer.Left().(expression.Tuple) 264 if isTuple { 265 rightTuple, _ := comparer.Right().(expression.Tuple) 266 leftCompareExprs = leftTuple.Children() 267 rightCompareExprs = rightTuple.Children() 268 } else { 269 leftCompareExprs = []sql.Expression{comparer.Left()} 270 rightCompareExprs = []sql.Expression{comparer.Right()} 271 } 272 273 if grp == nil { 274 grp = m.NewExprGroup(rel) 275 rel.Injective = isInjectiveMerge(rel, leftCompareExprs, rightCompareExprs) 276 return grp 277 } 278 rel.g = grp 279 rel.Injective = isInjectiveMerge(rel, leftCompareExprs, rightCompareExprs) 280 rel.CmpCnt = len(leftCompareExprs) 281 grp.Prepend(rel) 282 return grp 283 } 284 285 func (m *Memo) MemoizeProject(grp, child *ExprGroup, projections []sql.Expression) *ExprGroup { 286 rel := &Project{ 287 relBase: &relBase{}, 288 Child: child, 289 Projections: projections, 290 } 291 if grp == nil { 292 return m.NewExprGroup(rel) 293 } 294 rel.g = grp 295 grp.Prepend(rel) 296 return grp 297 } 298 299 // MemoizeIndexScan creates a source node that uses a specific index to 300 // access data. IndexScans are either static and read a specific set of 301 // ranges, or dynamic and use a lookup template that is iteratively 302 // bound and executed during LOOKUP_JOINs. 303 func (m *Memo) MemoizeIndexScan(grp *ExprGroup, ita *plan.IndexedTableAccess, alias string, index *Index, stat sql.Statistic) *ExprGroup { 304 rel := &IndexScan{ 305 sourceBase: &sourceBase{relBase: &relBase{}}, 306 Table: ita, 307 Alias: alias, 308 Index: index, 309 Stats: stat, 310 } 311 if grp == nil { 312 return m.NewExprGroup(rel) 313 } 314 rel.g = grp 315 grp.Prepend(rel) 316 return grp 317 } 318 319 func (m *Memo) MemoizeFilter(grp, child *ExprGroup, filters []sql.Expression) *ExprGroup { 320 rel := &Filter{ 321 relBase: &relBase{}, 322 Child: child, 323 Filters: filters, 324 } 325 if grp == nil { 326 return m.NewExprGroup(rel) 327 } 328 rel.g = grp 329 grp.Prepend(rel) 330 return grp 331 } 332 333 func (m *Memo) MemoizeMax1Row(grp, child *ExprGroup) *ExprGroup { 334 rel := &Max1Row{ 335 relBase: &relBase{}, 336 Child: child, 337 } 338 if grp == nil { 339 return m.NewExprGroup(rel) 340 } 341 rel.g = grp 342 grp.Prepend(rel) 343 return grp 344 } 345 346 // OptimizeRoot finds the implementation for the root expression 347 // that has the lowest cost. 348 func (m *Memo) OptimizeRoot() error { 349 err := m.optimizeMemoGroup(m.root) 350 if err != nil { 351 return err 352 } 353 354 // Certain "best" groups are incompatible. 355 m.root.fixConflicts() 356 return nil 357 } 358 359 // optimizeMemoGroup recursively builds the lowest cost plan for memo 360 // group expressions. We optimize expressions groups independently, walking 361 // the linked list of execution plans for a particular group only after 362 // optimizing all subgroups. All plans within a group by definition share 363 // the same subgroup dependencies. After finding the best implementation 364 // for a particular group, we fix the best plan for that group and recurse 365 // into its parents. 366 // TODO: we should not have to cost every plan, sometimes there is a provably 367 // best case implementation 368 func (m *Memo) optimizeMemoGroup(grp *ExprGroup) error { 369 if grp.Done { 370 return nil 371 } 372 373 var err error 374 n := grp.First 375 if _, ok := n.(SourceRel); ok { 376 // We should order the search bottom-up so that physical operators 377 // always have their trees materialized. Until then, we always assume 378 // the indexScan child is faster than a filter option, and correct 379 // when a chosen join operator is incompatible with the indexScan 380 // option. 381 grp.Done = true 382 grp.HintOk = true 383 grp.Best = grp.First 384 grp.Best.SetDistinct(NoDistinctOp) 385 return nil 386 } 387 388 for n != nil { 389 var cost float64 390 for _, g := range n.Children() { 391 err = m.optimizeMemoGroup(g) 392 if err != nil { 393 return err 394 } 395 cost += g.Cost 396 } 397 relCost, err := m.c.EstimateCost(m.Ctx, n, m.statsProv) 398 if err != nil { 399 return err 400 } 401 402 if grp.RelProps.Distinct.IsHash() { 403 var dCost float64 404 if sortedInputs(n) { 405 n.SetDistinct(SortedDistinctOp) 406 } else { 407 n.SetDistinct(HashDistinctOp) 408 d := &Distinct{Child: grp} 409 dCost = float64(statsForRel(d).RowCount()) 410 } 411 relCost += dCost 412 } else { 413 n.SetDistinct(NoDistinctOp) 414 } 415 416 n.SetCost(relCost) 417 cost += relCost 418 m.updateBest(grp, n, cost) 419 n = n.Next() 420 } 421 422 grp.Done = true 423 if err != nil { 424 return err 425 } 426 return nil 427 } 428 429 // updateBest chooses the best hinted plan or the best overall plan if the 430 // hint corresponds to no valid plan. Ordering is applied as a global 431 // rather than a local property. 432 func (m *Memo) updateBest(grp *ExprGroup, n RelExpr, cost float64) { 433 if !m.hints.isEmpty() { 434 if m.hints.satisfiedBy(n) { 435 if !grp.HintOk { 436 grp.Best = n 437 grp.Cost = cost 438 grp.HintOk = true 439 return 440 } 441 grp.updateBest(n, cost) 442 } else if grp.Best == nil || !grp.HintOk { 443 grp.updateBest(n, cost) 444 } 445 return 446 } 447 grp.updateBest(n, cost) 448 } 449 450 func (m *Memo) BestRootPlan(ctx *sql.Context) (sql.Node, error) { 451 b := NewExecBuilder() 452 return buildBestJoinPlan(b, m.root, nil) 453 } 454 455 // buildBestJoinPlan converts group's lowest cost implementation into a 456 // tree node with a recursive DFS. 457 func buildBestJoinPlan(b *ExecBuilder, grp *ExprGroup, input sql.Schema) (sql.Node, error) { 458 if !grp.Done { 459 return nil, fmt.Errorf("expected expression group plans to be fixed") 460 } 461 n := grp.Best 462 var err error 463 children := make([]sql.Node, len(n.Children())) 464 for i, g := range n.Children() { 465 children[i], err = buildBestJoinPlan(b, g, input) 466 if err != nil { 467 return nil, err 468 } 469 } 470 return b.buildRel(n, children...) 471 } 472 473 func getProjectColset(p *Project) sql.ColSet { 474 var colset sql.ColSet 475 for _, e := range p.Projections { 476 transform.InspectExpr(e, func(e sql.Expression) bool { 477 if gf, ok := e.(*expression.GetField); ok && gf.Id() > 0 { 478 colset.Add(gf.Id()) 479 } 480 return false 481 }) 482 } 483 return colset 484 } 485 486 func (m *Memo) ApplyHint(hint Hint) { 487 switch hint.Typ { 488 case HintTypeJoinOrder: 489 m.WithJoinOrder(hint.Args) 490 case HintTypeJoinFixedOrder: 491 case HintTypeInnerJoin, HintTypeMergeJoin, HintTypeLookupJoin, HintTypeHashJoin, HintTypeSemiJoin, HintTypeAntiJoin, HintTypeLeftOuterLookupJoin: 492 m.WithJoinOp(hint.Typ, hint.Args[0], hint.Args[1]) 493 case HintTypeLeftDeep: 494 m.hints.leftDeep = true 495 default: 496 } 497 } 498 499 func (m *Memo) WithJoinOrder(tables []string) { 500 // order maps groupId -> table dependencies 501 order := make(map[sql.TableId]uint64) 502 for i, t := range tables { 503 for _, n := range m.root.RelProps.TableIdNodes() { 504 if strings.EqualFold(t, n.Name()) { 505 order[n.Id()] = uint64(i) 506 break 507 } 508 } 509 } 510 hint := newJoinOrderHint(order) 511 hint.build(m.root) 512 if hint.isValid() { 513 m.hints.order = hint 514 } 515 } 516 517 func (m *Memo) WithJoinOp(op HintType, left, right string) { 518 var lTab, rTab sql.TableId 519 for _, n := range m.root.RelProps.TableIdNodes() { 520 if strings.EqualFold(left, n.Name()) { 521 lTab = n.Id() 522 } 523 if strings.EqualFold(right, n.Name()) { 524 rTab = n.Id() 525 } 526 } 527 if lTab == 0 || rTab == 0 { 528 return 529 } 530 hint := newjoinOpHint(op, lTab, rTab) 531 if !hint.isValid() { 532 return 533 } 534 m.hints.ops = append(m.hints.ops, hint) 535 } 536 537 func (m *Memo) String() string { 538 exprs := make([]string, m.cnt) 539 groups := make([]*ExprGroup, 0) 540 if m.root != nil { 541 r := m.root.First 542 for r != nil { 543 groups = append(groups, r.Group()) 544 groups = append(groups, r.Children()...) 545 r = r.Next() 546 } 547 } 548 for len(groups) > 0 { 549 newGroups := make([]*ExprGroup, 0) 550 for _, g := range groups { 551 if exprs[int(TableIdForSource(g.Id))] != "" { 552 continue 553 } 554 exprs[int(TableIdForSource(g.Id))] = g.String() 555 newGroups = append(newGroups, g.children()...) 556 } 557 groups = newGroups 558 } 559 b := strings.Builder{} 560 b.WriteString("memo:\n") 561 beg := "├──" 562 for i, g := range exprs { 563 if i == len(exprs)-1 { 564 beg = "└──" 565 } 566 b.WriteString(fmt.Sprintf("%s G%d: %s\n", beg, i+1, g)) 567 } 568 return b.String() 569 } 570 571 type tableProps struct { 572 grpToName map[GroupId]string 573 nameToGrp map[string]GroupId 574 } 575 576 func newTableProps() *tableProps { 577 return &tableProps{ 578 grpToName: make(map[GroupId]string), 579 nameToGrp: make(map[string]GroupId), 580 } 581 } 582 583 func (p *tableProps) addTable(n string, id GroupId) { 584 p.grpToName[id] = n 585 p.nameToGrp[n] = id 586 } 587 588 func (p *tableProps) GetTable(id GroupId) (string, bool) { 589 n, ok := p.grpToName[id] 590 return n, ok 591 } 592 593 func (p *tableProps) GetId(n string) (GroupId, bool) { 594 id, ok := p.nameToGrp[strings.ToLower(n)] 595 return id, ok 596 } 597 598 // Coster types can estimate the CPU and memory cost of physical execution 599 // operators. 600 type Coster interface { 601 // EstimateCost cost returns the incremental CPU and memory cost for an 602 // operator, or an error. Cost is dependent on physical operator type, 603 // and the cardinality of inputs. 604 EstimateCost(*sql.Context, RelExpr, sql.StatsProvider) (float64, error) 605 } 606 607 // RelExpr wraps a sql.Node for use as a ExprGroup linked list node. 608 // TODO: we need relExprs for every sql.Node and sql.Expression 609 type RelExpr interface { 610 fmt.Stringer 611 exprType 612 Next() RelExpr 613 SetNext(RelExpr) 614 SetCost(c float64) 615 Cost() float64 616 Distinct() distinctOp 617 SetDistinct(distinctOp) 618 } 619 620 type relBase struct { 621 // g is this relation's expression group 622 g *ExprGroup 623 // n is the next RelExpr in the ExprGroup linked list 624 n RelExpr 625 // c is this relation's cost while costing and plan reify are separate 626 c float64 627 // d indicates a RelExpr should be checked for distinctness 628 d distinctOp 629 } 630 631 // relKey is a quick identifier for avoiding duplicate work on the same 632 // RelExpr. 633 // TODO: the key should be a formalized hash of 1) the operator type, and 2) 634 // hashes of the RelExpr and ScalarExpr children. 635 func relKey(r RelExpr) uint64 { 636 key := int(r.Group().Id) 637 i := 1<<16 - 1 638 for _, c := range r.Children() { 639 key += i * int(c.Id) 640 i *= 1<<16 - 1 641 } 642 return uint64(key) 643 } 644 645 type distinctOp uint8 646 647 const ( 648 unknownDistinctOp distinctOp = iota 649 NoDistinctOp 650 SortedDistinctOp 651 HashDistinctOp 652 ) 653 654 func (d distinctOp) IsHash() bool { 655 return d == HashDistinctOp 656 } 657 658 func (r *relBase) Distinct() distinctOp { 659 return r.d 660 } 661 662 func (r *relBase) SetDistinct(d distinctOp) { 663 r.d = d 664 } 665 666 func (r *relBase) Group() *ExprGroup { 667 return r.g 668 } 669 670 func (r *relBase) SetGroup(g *ExprGroup) { 671 r.g = g 672 } 673 674 func (r *relBase) Next() RelExpr { 675 return r.n 676 } 677 678 func (r *relBase) SetNext(rel RelExpr) { 679 r.n = rel 680 } 681 682 func (r *relBase) SetCost(c float64) { 683 r.c = c 684 } 685 686 func (r *relBase) Cost() float64 { 687 return r.c 688 } 689 690 func DescribeStats(r RelExpr) *sql.DescribeStats { 691 return &sql.DescribeStats{ 692 EstimatedRowCount: r.Group().RelProps.GetStats().RowCount(), 693 Cost: r.Cost(), 694 } 695 } 696 697 func TableIdForSource(id GroupId) sql.TableId { 698 return sql.TableId(id - 1) 699 } 700 701 type exprType interface { 702 Group() *ExprGroup 703 Children() []*ExprGroup 704 SetGroup(g *ExprGroup) 705 } 706 707 // SourceRel represents a data source, like a tableScan, subqueryAlias, 708 // or list of values. 709 type SourceRel interface { 710 RelExpr 711 // outputCols retuns the output schema of this data source. 712 // TODO: this is more useful as a relExpr property, but we need 713 // this to fix up expression indexes currently 714 OutputCols() sql.Schema 715 Name() string 716 TableId() sql.TableId 717 Indexes() []*Index 718 SetIndexes(indexes []*Index) 719 TableIdNode() plan.TableIdNode 720 } 721 722 type Index struct { 723 // ordered list of index columns 724 order []sql.ColumnId 725 // unordered column set 726 set sql.ColSet 727 idx sql.Index 728 } 729 730 func (i *Index) Cols() []sql.ColumnId { 731 return i.order 732 } 733 734 func (i *Index) ColSet() sql.ColSet { 735 return i.set 736 } 737 738 func (i *Index) SqlIdx() sql.Index { 739 return i.idx 740 } 741 742 type sourceBase struct { 743 *relBase 744 indexes []*Index 745 } 746 747 func (s *sourceBase) Indexes() []*Index { 748 return s.indexes 749 } 750 751 func (s *sourceBase) SetIndexes(indexes []*Index) { 752 s.indexes = indexes 753 } 754 755 // JoinRel represents a plan.JoinNode or plan.CrossJoin. See plan.JoinType 756 // for the full list. 757 type JoinRel interface { 758 RelExpr 759 JoinPrivate() *JoinBase 760 Group() *ExprGroup 761 } 762 763 var _ JoinRel = (*AntiJoin)(nil) 764 var _ JoinRel = (*ConcatJoin)(nil) 765 var _ JoinRel = (*CrossJoin)(nil) 766 var _ JoinRel = (*LeftJoin)(nil) 767 var _ JoinRel = (*FullOuterJoin)(nil) 768 var _ JoinRel = (*HashJoin)(nil) 769 var _ JoinRel = (*InnerJoin)(nil) 770 var _ JoinRel = (*LookupJoin)(nil) 771 var _ JoinRel = (*SemiJoin)(nil) 772 773 type JoinBase struct { 774 *relBase 775 776 Op plan.JoinType 777 Filter []sql.Expression 778 Left *ExprGroup 779 Right *ExprGroup 780 } 781 782 func (r *JoinBase) Children() []*ExprGroup { 783 return []*ExprGroup{r.Left, r.Right} 784 } 785 786 func (r *JoinBase) JoinPrivate() *JoinBase { 787 return r 788 } 789 790 // Copy creates a JoinBase with the same underlying join expression. 791 // note: it is important to Copy the base node to avoid cyclical 792 // relExpr references in the ExprGroup linked list. 793 func (r *JoinBase) Copy() *JoinBase { 794 return &JoinBase{ 795 relBase: &relBase{ 796 g: r.g, 797 n: r.n, 798 c: r.c, 799 }, 800 Op: r.Op, 801 Filter: r.Filter, 802 Left: r.Left, 803 Right: r.Right, 804 } 805 } 806 807 func (r *LookupJoin) Children() []*ExprGroup { 808 return []*ExprGroup{r.Left, r.Right} 809 } 810 811 // RangeHeap contains all the information necessary to construct a RangeHeap join. 812 // Because both sides of the join can be implemented either by an index or a sorted node, 813 // we require that exactly one of ValueIndex and ValueExpr is non-nil, and exactly one 814 // of MinIndex and MinExpr is non-nil. If the index is non-nil, we will use it to construct 815 // a plan.IndexedTableAccess. Otherwise we use the expression to construct a plan.Sort. 816 type RangeHeap struct { 817 ValueIndex *IndexScan 818 ValueExpr sql.Expression 819 820 MinIndex *IndexScan 821 MinExpr sql.Expression 822 823 ValueCol *expression.GetField 824 MinColRef *expression.GetField 825 MaxColRef *expression.GetField 826 RangeClosedOnLowerBound bool 827 RangeClosedOnUpperBound bool 828 Parent *JoinBase 829 }