mutable
A Database System for Research and Fast Prototyping
Loading...
Searching...
No Matches
PlanTable.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cmath>
4#include <cstdint>
5#include <iomanip>
6#include <iostream>
7#include <memory>
8#include <mutable/mutable-config.hpp>
13#include <mutable/util/ADT.hpp>
14#include <mutable/util/crtp.hpp>
15#include <mutable/util/fn.hpp>
18#include <sstream>
19#include <string>
20#include <unordered_map>
21#include <vector>
22
23
24namespace m {
25
26// forward declarations
27template<typename> struct PlanTableBase;
28template<typename Actual> requires requires { typename PlanTableBase<Actual>; } struct PlanTableDecorator;
29
30using Subproblem = SmallBitset;
31
33struct M_EXPORT PlanTableEntryData
34{
35 virtual ~PlanTableEntryData() { }
36};
37
38struct M_EXPORT PlanTableEntry
39{
42 std::unique_ptr<DataModel> model;
43 double cost = std::numeric_limits<double>::infinity();
45 std::unique_ptr<PlanTableEntryData> data;
46
47 /* Returns all subproblems. */
48 std::vector<Subproblem> get_subproblems() const {
49 std::vector<Subproblem> s;
50 if (left) s.push_back(left);
51 if (right) s.push_back(right);
52 return s;
53 }
54
56 bool operator==(const PlanTableEntry &other) const {
57 return (this->cost == other.cost) and (this->left == other.left) and (this->right == other.right);
58 }
59
61 bool operator!=(const PlanTableEntry &other) const { return not(*this == other); }
62};
63
65{
66 std::size_t operator()(Subproblem S) const { return murmur3_64(uint64_t(S)); }
67};
68
69template<typename Actual>
70struct M_EXPORT PlanTableBase : crtp<Actual, PlanTableBase>
71{
72 using crtp<Actual, PlanTableBase>::actual;
73 using size_type = std::size_t;
75 using cost_type = decltype(PlanTableEntry::cost);
76
77 friend void swap(PlanTableBase &first, PlanTableBase &second);
78
79 protected:
80 PlanTableBase() = default;
81
82 public:
83 PlanTableBase(const PlanTableBase&) = delete;
84 PlanTableBase(PlanTableBase &&other) : PlanTableBase() { swap(*this, other); }
85
86 PlanTableBase & operator=(PlanTableBase other) { swap(*this, other); return *this; }
87
89 bool operator==(const PlanTableBase &other) const { return this->actual() == other.actual(); }
91 bool operator!=(const PlanTableBase &other) const { return not operator==(other); }
92
94 size_type num_sources() const { return actual().num_sources(); }
95
97 PlanTableEntry & at(Subproblem s) { return actual().at(s); }
99 const PlanTableEntry & at(Subproblem s) const { return actual().at(s); }
100
102 PlanTableEntry & operator[](Subproblem s) { return actual().operator[](s); }
104 const PlanTableEntry & operator[](Subproblem s) const { return actual().operator[](s); }
105
107 PlanTableEntry & get_final() { return operator[](Subproblem::All(num_sources())); }
109 const PlanTableEntry & get_final() const { return operator[](Subproblem::All(num_sources())); }
110
112 cost_type c(Subproblem s) const { return operator[](s).cost; }
113
115 bool has_plan(Subproblem s) const { return actual().has_plan(s); }
116
119 void update(const QueryGraph &G, const CardinalityEstimator &CE, const CostFunction &CF,
120 Subproblem left, Subproblem right, const cnf::CNF &condition)
121 {
122 using std::swap;
123 M_insist(not left.empty(), "left side must not be empty");
124 M_insist(not right.empty(), "right side must not be empty");
125 auto &entry = operator[](left | right);
126
127 /*----- Compute data model of join result. -------------------------------------------------------------------*/
128 if (not entry.model) {
129 /* If we consider this subproblem for the first time, compute its `DataModel`. If this subproblem describes
130 * a nested query, the `DataModel` must have been set by the `Optimizer`. */
131 auto &entry_left = operator[](left);
132 auto &entry_right = operator[](right);
133 M_insist(bool(entry_left.model), "must have a model for the left side");
134 M_insist(bool(entry_right.model), "must have a model for the right side");
135 // TODO use join condition for cardinality estimation
136 entry.model = CE.estimate_join(G, *entry_left.model, *entry_right.model, condition);
137 }
138
139 /*----- Calculate join cost. ---------------------------------------------------------------------------------*/
140 double cost = CF.calculate_join_cost(G, actual(), CE, left, right, condition);
141 // TODO only if cost model is not commutative
142 double rl_cost = CF.calculate_join_cost(G, actual(), CE, right, left, condition);
143 if (rl_cost < cost) {
144 swap(cost, rl_cost);
145 swap(left, right);
146 }
147
148 /*----- Update plan table entry. -----------------------------------------------------------------------------*/
149 if (not has_plan(left | right) or cost < entry.cost) {
150 /* If there is no plan yet for this subproblem or the current plan is better than the best plan yet, update
151 * the plan and costs for this subproblem. */
152 entry.cost = cost;
153 entry.left = left;
154 entry.right = right;
155 }
156 }
157
159 void reset_costs() { actual().reset_costs(); }
160
162 public:
163 friend std::ostream & M_EXPORT operator<<(std::ostream &out, const PlanTableBase &PT);
164
165 friend std::string to_string(const PlanTableBase &PT) {
166 std::ostringstream oss;
167 oss << PT;
168 return oss.str();
169 }
170
171 void dump(std::ostream &out) const { actual().dump(out); }
172 void dump() const { actual().dump(); }
174};
175
179struct M_EXPORT PlanTableSmallOrDense : PlanTableBase<PlanTableSmallOrDense>
180{
182
184
185 private:
193 std::unique_ptr<PlanTableEntry[]> table_ = nullptr;
194
195 public:
196 friend void swap(PlanTableSmallOrDense &first, PlanTableSmallOrDense &second) {
197 using std::swap;
198 swap(first.allocator_, second.allocator_);
199 swap(first.num_sources_, second.num_sources_);
201 swap(first.table_, second.table_);
202 }
203
205 explicit PlanTableSmallOrDense(size_type num_sources, size_type num_additional_entries = 0,
207 : allocator_(std::move(allocator))
208 , num_sources_(num_sources)
209 , num_additional_entries_(num_additional_entries)
210 , table_(allocator_.template make_unique<PlanTableEntry[]>((1UL << num_sources) + num_additional_entries))
211 {
212 /*----- Initialize table. ------------------------------------------------------------------------------------*/
213 for (auto ptr = &table_[0], end = &table_[size()]; ptr != end; ++ptr)
214 new (ptr) PlanTableEntry();
215 }
216
218 : PlanTableSmallOrDense(G.num_sources(), 0, std::move(allocator))
219 { }
220
223
225 if (bool(table_)) {
226 for (auto ptr = &table_[0], end = &table_[size()]; ptr != end; ++ptr)
227 ptr->~PlanTableEntry();
228 }
229 allocator_.dispose(std::move(table_), size());
230 }
231
232 PlanTableSmallOrDense & operator=(PlanTableSmallOrDense &&other) { swap(*this, other); return *this; }
233
234 bool operator==(const PlanTableSmallOrDense &other) const {
235 if (this->num_sources() != other.num_sources()) return false;
236 if (this->size() != other.size()) return false;
237 for (size_type i = 0; i < size(); ++i) {
238 Subproblem S(i);
239 if ((*this)[S] != other[S]) return false;
240 }
241 return true;
242 }
243 bool operator!=(const PlanTableSmallOrDense &other) const { return not operator==(other); }
244
245 size_type num_sources() const { return num_sources_; }
246 size_type size() const { return (1UL << num_sources_) + num_additional_entries_; }
247
248 PlanTableEntry & at(Subproblem s) { M_insist(uint64_t(s) < size()); return table_[uint64_t(s)]; }
249 const PlanTableEntry & at(Subproblem s) const { return const_cast<PlanTableSmallOrDense*>(this)->at(s); }
250
251 PlanTableEntry & operator[](Subproblem s) { return at(s); }
252 const PlanTableEntry & operator[](Subproblem s) const { return at(s); }
253
254 bool has_plan(Subproblem s) const {
255 if (s.size() == 1) return true;
256 auto &e = operator[](s);
257 M_insist(e.left.empty() == e.right.empty(), "either both sides are not set or both sides are set");
258 return not e.left.empty();
259 }
260
261 void reset_costs() {
262 for (size_type i = 0; i < size(); ++i) {
263 Subproblem S(i);
264 if (not S.is_singleton())
265 operator[](S).cost = std::numeric_limits<decltype(PlanTableEntry::cost)>::infinity();
266 }
267 }
268
269 private:
270 auto begin() { return &table_[0]; }
271 auto end() { return &table_[size()]; }
272
273 public:
274 friend std::ostream & M_EXPORT operator<<(std::ostream &out, const PlanTableSmallOrDense &PT);
275
276 void dump(std::ostream &out) const;
277 void dump() const;
278};
279
283struct M_EXPORT PlanTableLargeAndSparse : PlanTableBase<PlanTableLargeAndSparse>
284{
286
287 private:
291 std::unordered_map<Subproblem, PlanTableEntry, SubproblemHash> table_;
292
293 public:
295 using std::swap;
296 swap(first.num_sources_, second.num_sources_);
297 swap(first.table_, second.table_);
298 }
299
301 explicit PlanTableLargeAndSparse(size_type num_sources, size_type num_additional_entries = 0)
302 : num_sources_(num_sources)
303 , table_(OnoLohmannCycle(num_sources_) + num_additional_entries)
304 { }
306 : PlanTableLargeAndSparse(G.num_sources())
307 { }
308
311
312 PlanTableLargeAndSparse & operator=(PlanTableLargeAndSparse &&other) { swap(*this, other); return *this; }
313
314 bool operator==(const PlanTableLargeAndSparse &other) const {
315 if (this->num_sources() != other.num_sources()) return false;
316 if (this->table_.size() != other.table_.size()) return false;
317 for (auto &this_e : this->table_) {
318 auto other_it = other.table_.find(this_e.first);
319 if (other_it == other.table_.end())
320 return false;
321 if (this_e.second != other_it->second)
322 return false;
323 }
324 return true;
325 }
326 bool operator!=(const PlanTableLargeAndSparse &other) const { return not operator==(other); }
327
328 size_type num_sources() const { return num_sources_; }
329 size_type size() const { return table_.size(); }
330
331 PlanTableEntry & at(Subproblem s) { return table_.at(s); }
332 const PlanTableEntry & at(Subproblem s) const { return const_cast<PlanTableLargeAndSparse*>(this)->at(s); }
333
334 PlanTableEntry & operator[](Subproblem s) { return table_[s]; }
336 return const_cast<PlanTableLargeAndSparse*>(this)->operator[](s);
337 }
338
339 bool has_plan(Subproblem s) const {
340 if (s.size() == 1) return true;
341 if (auto it = table_.find(s); it != table_.end()) {
342 auto &e = it->second;
343 M_insist(e.left.empty() == e.right.empty(), "either both sides are not set or both sides are set");
344 return not e.left.empty();
345 } else {
346 return false;
347 }
348 }
349
350 void reset_costs() {
351 for (auto &entry : table_) {
352 if (not entry.first.is_singleton())
353 entry.second.cost = std::numeric_limits<decltype(PlanTableEntry::cost)>::infinity();
354 }
355 }
356
357 private:
358 auto begin() { return projecting_iterator(table_.begin(), [](auto it) -> auto& { return it->second; }); }
359 auto end() { return projecting_iterator(table_.end(), [](auto it) -> auto& { return it->second; }); }
360
361 public:
362 friend std::ostream & M_EXPORT operator<<(std::ostream &out, const PlanTableLargeAndSparse &PT);
363
364 void dump(std::ostream &out) const;
365 void dump() const;
366
367 private:
369 static size_type OnoLohmannCycle(size_type N) { return N*N - N + 1; }
370};
371
372template<typename Actual>
373requires requires { typename PlanTableBase<Actual>; }
374struct M_EXPORT PlanTableDecorator
375{
376 protected:
377 Actual table_;
378
379 public:
381 explicit PlanTableDecorator(Actual &&table) : table_(std::move(table)) { }
382
383 protected:
384 auto begin() { return table_.begin(); }
385 auto end() { return table_.end(); }
386};
387
388
389/*----------------------------------------------------------------------------------------------------------------------
390 * PlanTableBase friends
391 *--------------------------------------------------------------------------------------------------------------------*/
392
393template<typename Actual>
395{
396 swap(static_cast<Actual&>(first), static_cast<Actual&>(second));
397}
398
400template<typename Actual>
401std::ostream & operator<<(std::ostream &out, const PlanTableBase<Actual> &PT)
402{
403 return out << static_cast<const Actual&>(PT);
404}
406
407}
and(sizeof(T)==4) U64x1 reinterpret_to_U64(m
Definition: WasmAlgo.cpp:266
#define M_insist(...)
Definition: macro.hpp:129
‍mutable namespace
Definition: Backend.hpp:10
uint64_t murmur3_64(uint64_t v)
This function implements the 64-bit finalizer of Murmur3_x64 by Austin Appleby, available at https://...
Definition: fn.hpp:466
void swap(PlanTableBase< Actual > &first, PlanTableBase< Actual > &second)
Definition: PlanTable.hpp:394
M_LCOV_EXCL_START std::ostream & operator<<(std::ostream &out, const PlanTableBase< Actual > &PT)
Definition: PlanTable.hpp:401
STL namespace.
virtual std::unique_ptr< DataModel > estimate_join(const QueryGraph &G, const DataModel &left, const DataModel &right, const cnf::CNF &condition) const =0
Form a new DataModel by joining two DataModels.
double calculate_join_cost(const QueryGraph &G, const PlanTable &PT, const CardinalityEstimator &CE, Subproblem left, Subproblem right, const cnf::CNF &condition) const
Returns the total cost of performing a Join operation.
friend std::ostream &M_EXPORT operator<<(std::ostream &out, const PlanTableBase &PT)
Definition: PlanTable.hpp:401
void dump() const
Definition: PlanTable.hpp:172
const PlanTableEntry & operator[](Subproblem s) const
Returns a reference to the entry of s.
Definition: PlanTable.hpp:104
bool has_plan(Subproblem s) const
Returns true iff the plan table has a plan for s.
Definition: PlanTable.hpp:115
friend void swap(PlanTableBase &first, PlanTableBase &second)
Definition: PlanTable.hpp:394
bool operator!=(const PlanTableBase &other) const
Returns true if two tables differ in at least one entry.
Definition: PlanTable.hpp:91
std::size_t size_type
Definition: PlanTable.hpp:73
decltype(PlanTableEntry::cost) cost_type
Definition: PlanTable.hpp:75
void update(const QueryGraph &G, const CardinalityEstimator &CE, const CostFunction &CF, Subproblem left, Subproblem right, const cnf::CNF &condition)
Update the entry for left joined with right (left|right) by considering plan left join right.
Definition: PlanTable.hpp:119
bool operator==(const PlanTableBase &other) const
Returns true if two tables contain the exact same entries.
Definition: PlanTable.hpp:89
void reset_costs()
Resets the costs for all entries in the table.
Definition: PlanTable.hpp:159
const PlanTableEntry & at(Subproblem s) const
Returns a reference to the entry of s.
Definition: PlanTable.hpp:99
PlanTableEntry & at(Subproblem s)
Returns a reference to the entry of s.
Definition: PlanTable.hpp:97
cost_type c(Subproblem s) const
Returns the cost of the best plan to compute s.
Definition: PlanTable.hpp:112
friend std::string to_string(const PlanTableBase &PT)
Definition: PlanTable.hpp:165
PlanTableBase(const PlanTableBase &)=delete
PlanTableEntry & get_final()
Returns the entry for the final plan, i.e.
Definition: PlanTable.hpp:107
PlanTableBase(PlanTableBase &&other)
Definition: PlanTable.hpp:84
void dump(std::ostream &out) const
Definition: PlanTable.hpp:171
size_type num_sources() const
Returns the number of data sources.
Definition: PlanTable.hpp:94
PlanTableEntry & operator[](Subproblem s)
Returns a reference to the entry of s.
Definition: PlanTable.hpp:102
PlanTableBase & operator=(PlanTableBase other)
Definition: PlanTable.hpp:86
PlanTableBase()=default
const PlanTableEntry & get_final() const
Returns the entry for the final plan, i.e.
Definition: PlanTable.hpp:109
PlanTableDecorator()=default
PlanTableDecorator(Actual &&table)
Definition: PlanTable.hpp:381
This interface allows for attaching arbitrary data to PlanTableEntry instances.
Definition: PlanTable.hpp:34
virtual ~PlanTableEntryData()
Definition: PlanTable.hpp:35
std::unique_ptr< PlanTableEntryData > data
‍additional data associated to this PlanTableEntry; used for holistic optimization
Definition: PlanTable.hpp:45
double cost
the cost of the subproblem
Definition: PlanTable.hpp:43
bool operator==(const PlanTableEntry &other) const
Returns true iff two entries are equal.
Definition: PlanTable.hpp:56
Subproblem left
the left subproblem
Definition: PlanTable.hpp:40
std::unique_ptr< DataModel > model
the model of this subplan's result
Definition: PlanTable.hpp:42
std::vector< Subproblem > get_subproblems() const
Definition: PlanTable.hpp:48
Subproblem right
the right subproblem
Definition: PlanTable.hpp:41
bool operator!=(const PlanTableEntry &other) const
Returns true iff two entries are not equal.
Definition: PlanTable.hpp:61
This table represents all explored plans with their sub-plans, estimated size, cost,...
Definition: PlanTable.hpp:284
size_type num_sources_
‍the number of DataSources in the query
Definition: PlanTable.hpp:289
size_type size() const
Definition: PlanTable.hpp:329
friend std::ostream &M_EXPORT operator<<(std::ostream &out, const PlanTableLargeAndSparse &PT)
PlanTableLargeAndSparse(const QueryGraph &G)
Definition: PlanTable.hpp:305
PlanTableLargeAndSparse(PlanTableLargeAndSparse &&other)
Definition: PlanTable.hpp:310
const PlanTableEntry & at(Subproblem s) const
Definition: PlanTable.hpp:332
size_type num_sources() const
Definition: PlanTable.hpp:328
PlanTableLargeAndSparse & operator=(PlanTableLargeAndSparse &&other)
Definition: PlanTable.hpp:312
static size_type OnoLohmannCycle(size_type N)
Computes the number of connected subgraphs (CSGs) of a query graph with N relations and cycle topolog...
Definition: PlanTable.hpp:369
const PlanTableEntry & operator[](Subproblem s) const
Definition: PlanTable.hpp:335
bool operator==(const PlanTableLargeAndSparse &other) const
Definition: PlanTable.hpp:314
bool operator!=(const PlanTableLargeAndSparse &other) const
Definition: PlanTable.hpp:326
std::unordered_map< Subproblem, PlanTableEntry, SubproblemHash > table_
‍the PlanTableEntrys
Definition: PlanTable.hpp:291
bool has_plan(Subproblem s) const
Definition: PlanTable.hpp:339
friend void swap(PlanTableLargeAndSparse &first, PlanTableLargeAndSparse &second)
Definition: PlanTable.hpp:294
PlanTableEntry & operator[](Subproblem s)
Definition: PlanTable.hpp:334
PlanTableLargeAndSparse(size_type num_sources, size_type num_additional_entries=0)
Definition: PlanTable.hpp:301
PlanTableEntry & at(Subproblem s)
Definition: PlanTable.hpp:331
PlanTableLargeAndSparse(const PlanTableLargeAndSparse &)=delete
This table represents all explored plans with their sub-plans, estimated size, cost,...
Definition: PlanTable.hpp:180
friend std::ostream &M_EXPORT operator<<(std::ostream &out, const PlanTableSmallOrDense &PT)
PlanTableEntry & operator[](Subproblem s)
Definition: PlanTable.hpp:251
PlanTableSmallOrDense(size_type num_sources, size_type num_additional_entries=0, allocator_type allocator=allocator_type())
Definition: PlanTable.hpp:205
size_type num_sources() const
Definition: PlanTable.hpp:245
PlanTableSmallOrDense(const PlanTableSmallOrDense &)=delete
PlanTableSmallOrDense(const QueryGraph &G, allocator_type allocator=allocator_type())
Definition: PlanTable.hpp:217
const PlanTableEntry & at(Subproblem s) const
Definition: PlanTable.hpp:249
bool operator==(const PlanTableSmallOrDense &other) const
Definition: PlanTable.hpp:234
bool operator!=(const PlanTableSmallOrDense &other) const
Definition: PlanTable.hpp:243
size_type size() const
Definition: PlanTable.hpp:246
allocator_type allocator_
‍the allocator
Definition: PlanTable.hpp:187
size_type num_sources_
‍the number of DataSources in the query
Definition: PlanTable.hpp:189
PlanTableEntry & at(Subproblem s)
Definition: PlanTable.hpp:248
size_type num_additional_entries_
‍the number of additional entries this should contain
Definition: PlanTable.hpp:191
std::unique_ptr< PlanTableEntry[]> table_
‍the table of problem plans, sizes, and costs
Definition: PlanTable.hpp:193
bool has_plan(Subproblem s) const
Definition: PlanTable.hpp:254
const PlanTableEntry & operator[](Subproblem s) const
Definition: PlanTable.hpp:252
PlanTableSmallOrDense & operator=(PlanTableSmallOrDense &&other)
Definition: PlanTable.hpp:232
friend void swap(PlanTableSmallOrDense &first, PlanTableSmallOrDense &second)
Definition: PlanTable.hpp:196
PlanTableSmallOrDense(PlanTableSmallOrDense &&other)
Definition: PlanTable.hpp:222
The query graph represents all data sources and joins in a graph structure.
Definition: QueryGraph.hpp:172
Implements a small and efficient set over integers in the range of 0 to 63 (including).
Definition: ADT.hpp:26
bool is_singleton() const
Definition: ADT.hpp:165
static SmallBitset All(std::size_t n)
Factory method for creating a SmallBitset with first n bits set.
Definition: ADT.hpp:117
std::size_t size() const
Returns the number of elements in this SmallBitset.
Definition: ADT.hpp:161
bool empty() const
Returns true if there are no elements in this SmallBitset.
Definition: ADT.hpp:163
std::size_t operator()(Subproblem S) const
Definition: PlanTable.hpp:66
void dispose(std::unique_ptr< T > ptr)
A CNF represents a conjunction of cnf::Clauses.
Definition: CNF.hpp:134
A helper class to define CRTP class hierarchies.
Definition: crtp.hpp:50
actual_type & actual()
Definition: crtp.hpp:52
This allocator serves allocations using malloc/free.