1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct PlanCost {
17 pub estimated_rows: u64,
18 pub cost: u64,
19}
20
21impl PlanCost {
22 pub fn with_cardinality_floor(self) -> Self {
23 Self {
24 estimated_rows: self.estimated_rows.max(1),
25 ..self
26 }
27 }
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub struct PlanCostBreakdown {
36 pub estimated_rows: u64,
37 pub cost: u64,
38 pub cpu: u64,
39 pub random_io: u64,
40 pub sequential_io: u64,
41 pub output_rows: u64,
42}
43
44const CPU_WEIGHT: u64 = 1;
48const RANDOM_ACCESS_WEIGHT: u64 = 2;
49const SEQUENTIAL_ACCESS_WEIGHT: u64 = 1;
50const OUTPUT_WEIGHT: u64 = 1;
51
52impl PlanCostBreakdown {
53 pub fn with_cardinality_floor(self) -> Self {
54 Self {
55 estimated_rows: self.estimated_rows.max(1),
56 ..self
57 }
58 }
59
60 pub fn new(
61 estimated_rows: u64,
62 cpu: u64,
63 random_io: u64,
64 sequential_io: u64,
65 output_rows: u64,
66 ) -> Self {
67 Self {
68 estimated_rows,
69 cost: cpu
70 .saturating_mul(CPU_WEIGHT)
71 .saturating_add(random_io.saturating_mul(RANDOM_ACCESS_WEIGHT))
72 .saturating_add(sequential_io.saturating_mul(SEQUENTIAL_ACCESS_WEIGHT))
73 .saturating_add(output_rows.saturating_mul(OUTPUT_WEIGHT)),
74 cpu,
75 random_io,
76 sequential_io,
77 output_rows,
78 }
79 .with_cardinality_floor()
80 }
81
82 pub fn from_scalar(cost: PlanCost) -> Self {
83 Self::new(cost.estimated_rows, cost.cost, 0, 0, 0)
84 }
85
86 pub fn with_cpu(self, estimated_rows: u64, cpu: u64, output_rows: u64) -> Self {
87 Self::new(
88 estimated_rows,
89 self.cpu.saturating_add(cpu),
90 self.random_io,
91 self.sequential_io,
92 self.output_rows.saturating_add(output_rows),
93 )
94 }
95
96 pub fn with_random_io(self, estimated_rows: u64, random_io: u64, output_rows: u64) -> Self {
97 Self::new(
98 estimated_rows,
99 self.cpu,
100 self.random_io.saturating_add(random_io),
101 self.sequential_io,
102 self.output_rows.saturating_add(output_rows),
103 )
104 }
105
106 pub fn combine_with_cpu(
107 left: Self,
108 right: Self,
109 estimated_rows: u64,
110 cpu: u64,
111 output_rows: u64,
112 ) -> Self {
113 Self::new(
114 estimated_rows,
115 left.cpu.saturating_add(right.cpu).saturating_add(cpu),
116 left.random_io.saturating_add(right.random_io),
117 left.sequential_io.saturating_add(right.sequential_io),
118 left.output_rows
119 .saturating_add(right.output_rows)
120 .saturating_add(output_rows),
121 )
122 }
123
124 pub fn as_plan_cost(self) -> PlanCost {
125 PlanCost {
126 estimated_rows: self.estimated_rows,
127 cost: self.cost,
128 }
129 .with_cardinality_floor()
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::{PlanCost, PlanCostBreakdown};
136
137 #[test]
138 fn cost_breakdown_sums_stable_components() {
139 let cost = PlanCostBreakdown::new(7, 10, 20, 30, 40);
140
141 assert_eq!(cost.estimated_rows, 7);
142 assert_eq!(cost.cost, 120);
143 assert_eq!(
144 cost.as_plan_cost(),
145 PlanCost {
146 estimated_rows: 7,
147 cost: 120,
148 }
149 );
150 }
151
152 #[test]
153 fn cost_breakdown_combines_child_costs_with_parent_cpu() {
154 let left = PlanCostBreakdown::new(2, 3, 5, 7, 11);
155 let right = PlanCostBreakdown::new(13, 17, 19, 23, 29);
156
157 let combined = PlanCostBreakdown::combine_with_cpu(left, right, 26, 31, 37);
158
159 assert_eq!(combined.estimated_rows, 26);
160 assert_eq!(combined.cpu, 51);
161 assert_eq!(combined.random_io, 24);
162 assert_eq!(combined.sequential_io, 30);
163 assert_eq!(combined.output_rows, 77);
164 assert_eq!(combined.cost, 206);
165 }
166
167 #[test]
168 fn cost_breakdown_preserves_a_non_zero_cardinality_floor() {
169 let cost = PlanCostBreakdown::new(0, 0, 0, 0, 0);
170
171 assert_eq!(cost.estimated_rows, 1);
172 assert_eq!(cost.as_plan_cost().estimated_rows, 1);
173 }
174
175 #[test]
176 fn scalar_cost_preserves_a_non_zero_cardinality_floor() {
177 let cost = PlanCost {
178 estimated_rows: 0,
179 cost: 7,
180 }
181 .with_cardinality_floor();
182
183 assert_eq!(cost.estimated_rows, 1);
184 assert_eq!(cost.cost, 7);
185 }
186}