Skip to content

Commit faf13e9

Browse files
committed
[WIP - push over me]
1 parent 669d576 commit faf13e9

18 files changed

Lines changed: 248 additions & 31 deletions

File tree

internal/execution/queries/nodes/access.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type AccessStrategy interface {
1010
Serialize(w serialization.IndentWriter)
1111
Filter() impls.Expression
1212
Ordering() impls.OrderExpression
13+
EstimateCost() impls.NodeCost
1314
Scanner(ctx impls.ExecutionContext) (scan.RowScanner, error)
1415
}
1516

internal/execution/queries/nodes/access/strategy_index.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,22 @@ func (s *indexAccessStrategy[ScanOptions]) Ordering() impls.OrderExpression {
4444
return s.index.Ordering(s.opts)
4545
}
4646

47+
var indexAccessCostPerRow = impls.ResourceCost{CPU: 0.01, IO: 0.1}
48+
49+
func (s *indexAccessStrategy[ScanOptions]) EstimateCost() impls.NodeCost {
50+
stats, _ := s.table.Statistics()
51+
52+
// TODO - support parital indexes
53+
// TODO - determine selectivity based on index cond
54+
rowCount := stats.RowCount
55+
56+
return impls.NodeCost{
57+
EstimatedRows: rowCount,
58+
FixedCost: impls.ResourceCost{},
59+
VariableCost: indexAccessCostPerRow.ScaleUniform(float64(rowCount)),
60+
}
61+
}
62+
4763
func (s *indexAccessStrategy[ScanOptions]) Scanner(ctx impls.ExecutionContext) (scan.RowScanner, error) {
4864
ctx.Log("Building Index Access scanner Strategy")
4965

internal/execution/queries/nodes/access/strategy_table.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ func (s *tableAccessStrategy) Ordering() impls.OrderExpression {
3030
return nil
3131
}
3232

33+
var tableAccessCostPerRow = impls.ResourceCost{CPU: 0.01, IO: 0.1}
34+
35+
func (s *tableAccessStrategy) EstimateCost() impls.NodeCost {
36+
stats, _ := s.table.Statistics()
37+
38+
return impls.NodeCost{
39+
EstimatedRows: stats.RowCount,
40+
FixedCost: impls.ResourceCost{},
41+
VariableCost: tableAccessCostPerRow.ScaleUniform(float64(stats.RowCount)),
42+
}
43+
}
44+
3345
func (s *tableAccessStrategy) Scanner(ctx impls.ExecutionContext) (scan.RowScanner, error) {
3446
ctx.Log("Building Table Access Strategy scanner")
3547

internal/execution/queries/plan/access.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,22 @@ func (n *logicalAccessNode) Optimize(ctx impls.OptimizationContext) {
5959
n.order = nil
6060
}
6161

62-
func (n *logicalAccessNode) EstimateCost() Cost {
63-
return Cost{} // TODO
62+
var filterEvaluationCostPerRow = impls.ResourceCost{CPU: 0.01}
63+
64+
func (n *logicalAccessNode) EstimateCost() impls.NodeCost {
65+
strategyCost := n.strategy.EstimateCost()
66+
67+
if n.filter == nil {
68+
return strategyCost
69+
}
70+
71+
selectivity := 1.0 // TODO - estimate selectivity based on filter
72+
73+
return impls.NodeCost{
74+
EstimatedRows: int(float64(strategyCost.EstimatedRows) * selectivity),
75+
FixedCost: strategyCost.FixedCost,
76+
VariableCost: strategyCost.VariableCost.Add(filterEvaluationCostPerRow.ScaleUniform(float64(strategyCost.EstimatedRows))),
77+
}
6478
}
6579

6680
func (n *logicalAccessNode) Filter() impls.Expression {

internal/execution/queries/plan/analyze.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func (n *logicalAnalyze) Fields() []fields.Field
2121
func (n *logicalAnalyze) AddFilter(ctx impls.OptimizationContext, filter impls.Expression) {} // top-level
2222
func (n *logicalAnalyze) AddOrder(ctx impls.OptimizationContext, order impls.OrderExpression) {} // top-level
2323
func (n *logicalAnalyze) Optimize(ctx impls.OptimizationContext) {}
24-
func (n *logicalAnalyze) EstimateCost() Cost { return Cost{} }
24+
func (n *logicalAnalyze) EstimateCost() impls.NodeCost { return impls.NodeCost{} }
2525
func (n *logicalAnalyze) Filter() impls.Expression { return nil }
2626
func (n *logicalAnalyze) Ordering() impls.OrderExpression { return nil }
2727
func (n *logicalAnalyze) SupportsMarkRestore() bool { return false }

internal/execution/queries/plan/combination/except.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ func (n *logicalExceptNode) Optimize(ctx impls.OptimizationContext) {
6262
n.right.Optimize(ctx)
6363
}
6464

65-
func (n *logicalExceptNode) EstimateCost() plan.Cost {
66-
return plan.Cost{} // TODO
65+
func (n *logicalExceptNode) EstimateCost() impls.NodeCost {
66+
return impls.NodeCost{} // TODO
6767
}
6868

6969
func (n *logicalExceptNode) Filter() impls.Expression {

internal/execution/queries/plan/combination/intersect.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,12 @@ func (n *logicalIntersectNode) AddOrder(ctx impls.OptimizationContext, orderExpr
6060
func (n *logicalIntersectNode) Optimize(ctx impls.OptimizationContext) {
6161
n.left.Optimize(ctx)
6262
n.right.Optimize(ctx)
63+
64+
// TODO - flip sides if one side is much smaller than the other
6365
}
6466

65-
func (n *logicalIntersectNode) EstimateCost() plan.Cost {
66-
return plan.Cost{} // TODO
67+
func (n *logicalIntersectNode) EstimateCost() impls.NodeCost {
68+
return impls.NodeCost{} // TODO
6769
}
6870

6971
func (n *logicalIntersectNode) Filter() impls.Expression {

internal/execution/queries/plan/combination/union.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,19 @@ func (n *logicalUnionNode) Optimize(ctx impls.OptimizationContext) {
6363
n.right.Optimize(ctx)
6464
}
6565

66-
func (n *logicalUnionNode) EstimateCost() plan.Cost {
67-
return plan.Cost{} // TODO
66+
func (n *logicalUnionNode) EstimateCost() impls.NodeCost {
67+
leftCost := n.left.EstimateCost()
68+
rightCost := n.right.EstimateCost()
69+
70+
if !n.distinct {
71+
return impls.NodeCost{
72+
EstimatedRows: leftCost.EstimatedRows + rightCost.EstimatedRows,
73+
FixedCost: leftCost.FixedCost.Add(rightCost.FixedCost),
74+
VariableCost: leftCost.VariableCost.Add(rightCost.VariableCost),
75+
}
76+
}
77+
78+
return impls.NodeCost{} // TODO
6879
}
6980

7081
func (n *logicalUnionNode) Filter() impls.Expression {

internal/execution/queries/plan/join/helpers_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package join
22

33
import (
44
"github.com/efritz/gostgres/internal/execution/queries/nodes"
5-
"github.com/efritz/gostgres/internal/execution/queries/plan"
65
"github.com/efritz/gostgres/internal/shared/fields"
76
"github.com/efritz/gostgres/internal/shared/impls"
87
"github.com/efritz/gostgres/internal/shared/types"
@@ -32,7 +31,7 @@ func (m *mockLogicalNode) Fields() []fields.Field {
3231
func (*mockLogicalNode) AddFilter(impls.OptimizationContext, impls.Expression) {}
3332
func (*mockLogicalNode) AddOrder(impls.OptimizationContext, impls.OrderExpression) {}
3433
func (*mockLogicalNode) Optimize(impls.OptimizationContext) {}
35-
func (*mockLogicalNode) EstimateCost() plan.Cost { return plan.Cost{} }
34+
func (*mockLogicalNode) EstimateCost() impls.NodeCost { return impls.NodeCost{} }
3635
func (*mockLogicalNode) Filter() impls.Expression { return nil }
3736
func (*mockLogicalNode) Ordering() impls.OrderExpression { return nil }
3837
func (*mockLogicalNode) SupportsMarkRestore() bool { return false }

internal/execution/queries/plan/join/node.go

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func (n *joinNodeLeaf) Optimize(ctx impls.OptimizationContext) {
118118
n.relation.Optimize(ctx)
119119
}
120120

121-
func (n *joinNodeLeaf) EstimateCost() plan.Cost {
121+
func (n *joinNodeLeaf) EstimateCost() impls.NodeCost {
122122
return n.relation.EstimateCost()
123123
}
124124

@@ -187,8 +187,34 @@ func (n *joinNodeInternal) Optimize(ctx impls.OptimizationContext) {
187187
n.strategy = &logicalNestedLoopJoinStrategy{n: n}
188188
}
189189

190-
func (n *joinNodeInternal) EstimateCost() plan.Cost {
191-
return plan.Cost{} // TODO
190+
var joinMergeCostPerRow = impls.ResourceCost{CPU: 0.2}
191+
var joinFilterCostPerRow = impls.ResourceCost{CPU: 0.1}
192+
193+
func (n *joinNodeInternal) EstimateCost() impls.NodeCost {
194+
// TODO - the following estimates are based on nested loop joins
195+
196+
leftCost := n.left.EstimateCost()
197+
rightCost := n.right.EstimateCost()
198+
199+
selectivity := 1.0 // TODO - estimate selectivity based on filter
200+
numLeftRows := leftCost.EstimatedRows
201+
numCandidateRows := leftCost.EstimatedRows * rightCost.EstimatedRows
202+
estimatedRows := int(float64(numCandidateRows) * selectivity)
203+
204+
// For each left row we scan, we reset the right relation scanner
205+
costPerLeftRow := impls.SumCosts(leftCost.VariableCost, rightCost.FixedCost)
206+
207+
// For each candidate row we scan, we evaluate the join condition
208+
costPerCandidateRow := impls.SumCosts(rightCost.VariableCost, joinMergeCostPerRow, joinFilterCostPerRow)
209+
210+
return impls.NodeCost{
211+
EstimatedRows: estimatedRows,
212+
FixedCost: leftCost.FixedCost,
213+
VariableCost: impls.SumCosts(
214+
costPerLeftRow.ScaleUniform(float64(numLeftRows)),
215+
costPerCandidateRow.ScaleUniform(float64(numCandidateRows)),
216+
),
217+
}
192218
}
193219

194220
func (n *joinNodeInternal) Filter() impls.Expression {

0 commit comments

Comments
 (0)