19#include <nlohmann/json.hpp>
29std::filesystem::path injected_cardinalities_file;
45 throw data_model_exception(
"predicting the number of distinct values is not supported by this data model.");
65 auto model = std::make_unique<CartesianProductDataModel>();
72 M_insist(P.
size() == 1,
"Subproblem must identify exactly one DataSource");
73 auto idx = *P.
begin();
74 auto &BT = as<const BaseTable>(*G.
sources()[idx]);
75 auto model = std::make_unique<CartesianProductDataModel>();
76 model->size = BT.table().store().num_rows();
80std::unique_ptr<DataModel>
84 auto &data = as<const CartesianProductDataModel>(_data);
85 return std::make_unique<CartesianProductDataModel>(data);
88std::unique_ptr<DataModel>
90 std::size_t offset)
const
92 auto data = as<const CartesianProductDataModel>(_data);
93 const std::size_t remaining = offset > data.size ? 0UL : data.size - offset;
94 auto model = std::make_unique<CartesianProductDataModel>();
95 model->size = std::min(remaining, limit);
99std::unique_ptr<DataModel>
101 const std::vector<group_type>&)
const
103 auto &data = as<const CartesianProductDataModel>(_data);
104 auto model = std::make_unique<CartesianProductDataModel>();
105 model->size = data.size;
109std::unique_ptr<DataModel>
113 auto left = as<const CartesianProductDataModel>(_left);
114 auto right = as<const CartesianProductDataModel>(_right);
115 auto model = std::make_unique<CartesianProductDataModel>();
116 model->size = left.size * right.size;
120template<
typename PlanTable>
121std::unique_ptr<DataModel>
126 auto model = std::make_unique<CartesianProductDataModel>();
128 for (
auto it = to_join.
begin(); it != to_join.
end(); ++it)
129 model->size *= as<const CartesianProductDataModel>(*PT[it.as_set()].model).size;
134std::unique_ptr<DataModel>
138std::unique_ptr<DataModel>
144 return as<const CartesianProductDataModel>(data).size;
150 out <<
"CartesianProductEstimator - returns size of the Cartesian product of the given subproblems";
162 : fallback_(name_of_database)
165 Position pos(
"InjectionCardinalityEstimator");
167 if (options::injected_cardinalities_file.empty()) {
168 std::cout <<
"No injection file was passed.\n";
170 std::ifstream in(options::injected_cardinalities_file);
174 diag.
w(pos) <<
"Could not open file " << options::injected_cardinalities_file <<
".\n"
175 <<
"A dummy estimator will be used to do estimations.\n";
182 : fallback_(name_of_database)
191 Position pos(
"InjectionCardinalityEstimator");
192 std::string prev_relation;
194 using json = nlohmann::json;
198 }
catch (json::parse_error parse_error) {
199 diag.
w(pos) <<
"The file could not be parsed as json. Parser error output:\n"
200 << parse_error.what() <<
"\n"
201 <<
"A dummy estimator will be used to do estimations.\n";
204 json *database_entry;
206 database_entry = &cardinalities.at(*name_of_database);
208 diag.
w(pos) <<
"No entry for the db " << name_of_database <<
" in the file.\n"
209 <<
"A dummy estimator will be used to do estimations.\n";
213 std::vector<std::string> names;
214 for (
auto &subproblem_entry : *database_entry) {
215 json* relations_array;
218 relations_array = &subproblem_entry.at(
"relations");
219 size = &subproblem_entry.at(
"size");
221 diag.
w(pos) <<
"The entry " << subproblem_entry <<
" for the db \"" << name_of_database <<
"\""
222 <<
" does not have the required form of {\"relations\": ..., \"size\": ... } "
223 <<
"and will thus be ignored.\n";
228 for (
auto it = relations_array->begin(); it != relations_array->end(); ++it)
229 names.emplace_back(it->get<std::string>());
230 std::sort(names.begin(), names.end());
233 for (
auto it = names.begin(); it != names.end(); ++it) {
234 if (it != names.begin())
235 buf_.emplace_back(
'$');
238 buf_.emplace_back(0);
241 M_insist(res.second,
"insertion must not fail as we do not allow for duplicates in the input file");
249 return std::make_unique<InjectionCardinalityDataModel>(
Subproblem(), 0);
255 const auto idx = *P.
begin();
259 return std::make_unique<InjectionCardinalityDataModel>(P, it->second);
267std::unique_ptr<DataModel>
271 auto &data = as<const InjectionCardinalityDataModel>(_data);
272 return std::make_unique<InjectionCardinalityDataModel>(data);
275std::unique_ptr<DataModel>
277 std::size_t offset)
const
279 auto &data = as<const InjectionCardinalityDataModel>(_data);
280 const std::size_t remaining = offset > data.size_ ? 0UL : data.size_ - offset;
281 return std::make_unique<InjectionCardinalityDataModel>(data.subproblem_, std::min(remaining, limit));
284std::unique_ptr<DataModel>
286 const std::vector<group_type> &exprs)
const
288 auto &data = as<const InjectionCardinalityDataModel>(_data);
291 return std::make_unique<InjectionCardinalityDataModel>(data.subproblem_, 1);
296 for (
auto [grp, alias] : exprs) {
298 if (alias.has_value())
308 return std::make_unique<InjectionCardinalityDataModel>(data.subproblem_, std::min(it->second, data.size_));
311 return std::make_unique<InjectionCardinalityDataModel>(data);
315std::unique_ptr<DataModel>
319 auto &left = as<const InjectionCardinalityDataModel>(_left);
320 auto &right = as<const InjectionCardinalityDataModel>(_right);
322 const Subproblem subproblem = left.subproblem_ | right.subproblem_;
330 return std::make_unique<InjectionCardinalityDataModel>(subproblem, std::min(it->second,
max_cardinality));
334 std::cerr <<
"warning: failed to estimate the join of " << left.subproblem_ <<
" and " << right.subproblem_
336 auto left_fallback = std::make_unique<CartesianProductEstimator::CartesianProductDataModel>();
337 left_fallback->size = left.size_;
338 auto right_fallback = std::make_unique<CartesianProductEstimator::CartesianProductDataModel>();
339 right_fallback->size = right.size_;
341 return std::make_unique<InjectionCardinalityDataModel>(subproblem,
346template<
typename PlanTable>
347std::unique_ptr<DataModel>
356 for (
auto it = to_join.
begin(); it != to_join.
end(); ++it)
357 max_cardinality *= as<const InjectionCardinalityDataModel>(*PT[it.as_set()].model).size_;
358 return std::make_unique<InjectionCardinalityDataModel>(to_join, std::min(it->second,
max_cardinality));
362 std::cerr <<
"warning: failed to estimate the join of all data sources in " << to_join <<
'\n';
363 auto ds_it = to_join.
begin();
364 std::size_t size = as<const InjectionCardinalityDataModel>(*PT[ds_it.as_set()].model).size_;
365 for (; ds_it != to_join.end(); ++ds_it)
366 size *= as<const InjectionCardinalityDataModel>(*PT[ds_it.as_set()].model).size_;
367 return std::make_unique<InjectionCardinalityDataModel>(to_join, size);
372std::unique_ptr<DataModel>
377std::unique_ptr<DataModel>
383 return as<const InjectionCardinalityDataModel>(data).size_;
389 constexpr uint32_t max_rows_printed = 100;
390 std::size_t sub_len = 13;
392 sub_len = std::max(sub_len, strlen(*entry.first));
394 out << std::left <<
"InjectionCardinalityEstimator\n"
395 << std::setw(sub_len) <<
"Subproblem" <<
"Size" <<
"\n" << std::right;
398 uint32_t counter = 0;
400 if (counter >= max_rows_printed)
break;
401 out << std::left << std::setw(sub_len) << entry.first << entry.second <<
"\n";
410 static thread_local std::vector<ThreadSafePooledString> names;
413 names.emplace_back(G.
sources()[
id]->name());
414 std::sort(names.begin(), names.end(), [](
auto lhs,
auto rhs){ return strcmp(*lhs, *rhs) < 0; });
417 for (
auto it = names.begin(); it != names.end(); ++it) {
418 if (it != names.begin())
419 buf_.emplace_back(
'$');
423 buf_.emplace_back(0);
435struct FilterTranslator : ast::ConstASTExprVisitor {
441 FilterTranslator() : attribute(C.pool(
"")), value(0), op(
Spn::EQUAL) { }
443 using ConstASTExprVisitor::operator();
448 auto val = Interpreter::eval(constant);
451 [&val,
this](
const Numeric &numeric) {
452 switch (numeric.kind) {
454 value = float(val.as_i());
456 case Numeric::N_Float:
459 case Numeric::N_Decimal:
460 value = float(val.as_d());
466 }, *constant.
type());
470 switch (binary_expr.
op().
type) {
483 case TK_GREATER_EQUAL:
490 (*this)(*binary_expr.
lhs);
491 (*this)(*binary_expr.
rhs);
501struct JoinTranslator : ast::ConstASTExprVisitor {
503 std::vector<std::pair<ThreadSafePooledString, ThreadSafePooledString>> join_designator;
505 using ConstASTExprVisitor::operator();
515 (*this)(*binary_expr.
lhs);
516 (*this)(*binary_expr.
rhs);
548 auto &attr_to_id = data.
spns_.begin()->second.get().get_attribute_to_id();
551 bool is_primary_key =
false;
554 auto find_iter = table_name == join.first.first ?
555 attr_to_id.find(join.first.second) : attr_to_id.find(join.second.second);
557 if (find_iter != attr_to_id.end()) { spn_id = find_iter->second; }
558 else { is_primary_key =
true; }
560 return {spn_id, is_primary_key};
565 auto [spn_id, is_primary_key] =
find_spn_id(data, join);
578 auto find_iter = attr_to_id.find(attribute);
593 const auto idx = *P.
begin();
594 auto &BT = as<const BaseTable>(*G.
sources()[idx]);
599 spns.emplace(BT.name(), spn);
600 return std::make_unique<SpnDataModel>(std::move(spns), spn.
num_rows());
602 throw data_model_exception(
"Table does not exist.");
606std::unique_ptr<DataModel>
609 auto &data = as<const SpnDataModel>(_data);
611 auto new_data = std::make_unique<SpnDataModel>(data);
612 auto &spn = new_data->spns_.begin()->second.get();
613 auto &attribute_to_id = spn.get_attribute_to_id();
619 for (
auto &clause : filter) {
624 if (
auto it = attribute_to_id.find(ft.attribute); it != attribute_to_id.end()) {
627 throw data_model_exception(
"Attribute does not exist.");
630 translated_filter.emplace(spn_id, std::make_pair(ft.op, ft.value));
634 new_data->num_rows_ = float(new_data->num_rows_) * spn.likelihood(translated_filter);
638std::unique_ptr<DataModel>
641 auto model = std::make_unique<SpnDataModel>(as<const SpnDataModel>(data));
642 model->num_rows_ = std::min(model->num_rows_, limit);
647 const std::vector<group_type> &groups)
const
649 auto model = std::make_unique<SpnDataModel>(as<const SpnDataModel>(data));
650 std::size_t num_rows = 1;
651 for (
auto [grp, alias] : groups) {
652 auto designator = cast<const ast::Designator>(&grp.get());
654 throw data_model_exception(
"SpnEstimator only supports Designators and no composed expressions");
655 auto spn_it = model->spns_.find(designator->table_name.text.assert_not_none());
656 if (spn_it == model->spns_.end())
657 throw data_model_exception(
"Could not find table for grouping.");
659 auto &spn = spn_it->second.get();
660 auto &attr_to_id = spn.get_attribute_to_id();
661 if (
auto attr_it = attr_to_id.find(designator->attr_name.text.assert_not_none()); attr_it != attr_to_id.end()) {
662 num_rows *= spn.estimate_number_distinct_values(attr_it->second);
664 num_rows *= spn.num_rows();
667 model->num_rows_ = num_rows;
671std::unique_ptr<DataModel>
675 auto &left = as<const SpnDataModel>(_left);
676 auto &right = as<const SpnDataModel>(_right);
678 auto new_left = std::make_unique<SpnDataModel>(left);
679 auto new_right = std::make_unique<SpnDataModel>(right);
683 if (not condition.empty()) {
685 jt(*condition[0][0]);
686 auto first_identifier = std::make_pair(jt.join_designator[0].first, jt.join_designator[0].second);
687 auto second_identifier = std::make_pair(jt.join_designator[1].first, jt.join_designator[1].second);
688 SpnJoin join = std::make_pair(first_identifier, second_identifier);
692 if (left.spns_.size() == 1) { new_left->max_frequencies_.emplace_back(
max_frequency(left, join)); }
693 if (right.spns_.size() == 1) { new_right->max_frequencies_.emplace_back(
max_frequency(right, join)); }
697 std::size_t mf_left = std::accumulate(
698 new_left->max_frequencies_.begin(), new_left->max_frequencies_.end(), 1, std::multiplies<>());
700 std::size_t mf_right = std::accumulate(
701 new_right->max_frequencies_.begin(), new_right->max_frequencies_.end(), 1, std::multiplies<>());
703 std::size_t left_clause = new_left->num_rows_ / mf_left;
704 std::size_t right_clause = new_right->num_rows_ / mf_right;
706 std::size_t num_rows_join = std::min<std::size_t>(left_clause, right_clause) * mf_left * mf_right;
708 new_left->num_rows_ = num_rows_join;
711 if (left.spns_.size() == 1) { new_left->max_frequencies_.emplace_back(left.num_rows_); }
712 if (right.spns_.size() == 1) { new_right->max_frequencies_.emplace_back(right.num_rows_); }
714 new_left->num_rows_ = left.num_rows_ * right.num_rows_;
718 new_left->spns_.insert(new_right->spns_.begin(), new_right->spns_.end());
719 new_left->max_frequencies_.insert(
720 new_left->max_frequencies_.end(), new_right->max_frequencies_.begin(), new_right->max_frequencies_.end());
725template<
typename PlanTable>
726std::unique_ptr<DataModel>
732 if (condition.empty()) {
733 auto model = std::make_unique<SpnDataModel>();
734 model->num_rows_ = 1UL;
735 for (
auto it = to_join.
begin(); it != to_join.
end(); ++it)
736 model->num_rows_ *= as<const SpnDataModel>(*PT[it.as_set()].model).num_rows_;
742 std::unordered_map<ThreadSafePooledString, ThreadSafePooledString> table_to_attribute;
743 for (
auto clause : condition) {
745 table_to_attribute.emplace(jt.join_designator[0].first, jt.join_designator[0].second);
746 table_to_attribute.emplace(jt.join_designator[1].first, jt.join_designator[1].second);
750 SpnDataModel final_model = as<const SpnDataModel>(*PT[to_join.
begin().as_set()].model);
754 if (
auto find_iter = table_to_attribute.find(first_table_name); find_iter != table_to_attribute.end()) {
763 for (
auto it = ++to_join.
begin(); it != to_join.
end(); it++) {
764 SpnDataModel model = as<const SpnDataModel>(*PT[it.as_set()].model);
767 if (
auto find_iter = table_to_attribute.find(table_name); find_iter != table_to_attribute.end()) {
773 std::size_t mf_left = std::accumulate(
777 std::size_t left_clause = final_model.
num_rows_ / mf_left;
778 std::size_t right_clause = model.
num_rows_ / mf_right;
780 std::size_t num_rows_join = std::min<std::size_t>(left_clause, right_clause) * mf_left * mf_right;
785 final_model.
spns_.emplace(*model.
spns_.begin());
789 return std::make_unique<SpnDataModel>(final_model);
794 auto &data = as<const SpnDataModel>(_data);
795 return data.num_rows_;
802 X(CartesianProductEstimator, "CartesianProduct", "estimates cardinalities as Cartesian product") \
803 X(InjectionCardinalityEstimator, "Injected", "estimates cardinalities based on a JSON file") \
804 X(SpnEstimator, "Spn", "estimates cardinalities based on Sum-Product Networks")
806#define INSTANTIATE(TYPE, _1, _2) \
807 template std::unique_ptr<DataModel> TYPE::operator()(estimate_join_all_tag, PlanTableSmallOrDense &&PT, \
808 const QueryGraph &G, Subproblem to_join, \
809 const cnf::CNF &condition) const; \
810 template std::unique_ptr<DataModel> TYPE::operator()(estimate_join_all_tag, PlanTableLargeAndSparse &&PT, \
811 const QueryGraph &G, Subproblem to_join, \
812 const cnf::CNF &condition) const;
817static
void register_cardinality_estimators()
821#define REGISTER(TYPE, NAME, DESCRIPTION) \
822 C.register_cardinality_estimator<TYPE>(C.pool(NAME), DESCRIPTION);
827 "Cardinality estimation",
829 "--show-cardinality-file-example",
830 "show an example of an input JSON file for cardinality injection",
833Example for injected cardinalities file:\n\
837 \"relations\": [\"A\", \"B\", ...],\n\
841 \"relations\": [\"C\", \"A\", ...],\n\
847 \"relations\": [\"customers\"],\n\
851 \"relations\": [\"customers\", \"orders\", ...],\n\
860 "Cardinality estimation",
862 "--use-cardinality-file",
863 "inject cardinalities from the given JSON file",
864 [] (
const char *path) {
865 options::injected_cardinalities_file = path;
__attribute__((constructor(202))) static void register_interpreter()
#define INSTANTIATE(CLASS)
std::size_t max_cardinality
maximum cardinality of relations and intermediate results
void add(const char *group_name, const char *short_name, const char *long_name, const char *description, Callback &&callback)
Adds a new group option to the ArgParser.
#define M_unreachable(MSG)
ThreadSafeStringPool::proxy_type ThreadSafePooledString
auto visit(Callable &&callable, Base &obj, m::tag< Callable > &&=m::tag< Callable >())
Generic implementation to visit a class hierarchy, with similar syntax as std::visit.
command-line options for the HeuristicSearchPlanEnumerator
data_model_exception is thrown if a DataModel implementation does not contain the requested informati...
virtual double predict_number_distinct_values(const DataModel &data) const
virtual ~CardinalityEstimator()=0
virtual void print(std::ostream &out) const =0
std::unique_ptr< DataModel > estimate_join(const QueryGraph &G, const DataModel &left, const DataModel &right, const cnf::CNF &condition) const override
std::unique_ptr< DataModel > estimate_grouping(const QueryGraph &G, const DataModel &data, const std::vector< group_type > &groups) const override
void print(std::ostream &out) const override
std::unique_ptr< DataModel > estimate_limit(const QueryGraph &G, const DataModel &data, std::size_t limit, std::size_t offset) const override
std::unique_ptr< DataModel > empty_model() const override
std::unique_ptr< DataModel > estimate_filter(const QueryGraph &G, const DataModel &data, const cnf::CNF &filter) const override
std::unique_ptr< DataModel > operator()(estimate_join_all_tag, PlanTable &&PT, const QueryGraph &G, Subproblem to_join, const cnf::CNF &condition) const
std::unique_ptr< DataModel > estimate_scan(const QueryGraph &G, Subproblem P) const override
std::size_t predict_cardinality(const DataModel &data) const override
The catalog contains all Databases and keeps track of all meta information of the database system.
ThreadSafePooledString pool(const char *str) const
Creates an internalized copy of the string str by adding it to the internal StringPool.
static Catalog & Get()
Return a reference to the single Catalog instance.
m::ArgParser & arg_parser()
A DataModel describes a data set.
std::ostream & w(const Position pos)
const char * buf_view() const
std::unordered_map< ThreadSafePooledString, std::size_t > cardinality_table_
void print(std::ostream &out) const override
std::vector< char > buf_
buffer used to construct identifiers
std::unique_ptr< DataModel > estimate_grouping(const QueryGraph &G, const DataModel &data, const std::vector< group_type > &groups) const override
std::size_t predict_cardinality(const DataModel &data) const override
std::unique_ptr< DataModel > estimate_scan(const QueryGraph &G, Subproblem P) const override
std::unique_ptr< DataModel > estimate_join(const QueryGraph &G, const DataModel &left, const DataModel &right, const cnf::CNF &condition) const override
std::unique_ptr< DataModel > operator()(estimate_join_all_tag, PlanTable &&PT, const QueryGraph &G, Subproblem to_join, const cnf::CNF &condition) const
ThreadSafePooledString make_identifier(const QueryGraph &G, const Subproblem S) const
std::unique_ptr< DataModel > empty_model() const override
InjectionCardinalityEstimator(ThreadSafePooledString name_of_database)
Create an InjectionCardinalityEstimator for the database name_of_database from file that was passed b...
CartesianProductEstimator fallback_
std::unique_ptr< DataModel > estimate_limit(const QueryGraph &G, const DataModel &data, std::size_t limit, std::size_t offset) const override
std::ostringstream oss_
buffer used to construct identifiers
void buf_append(const char *s) const
std::unique_ptr< DataModel > estimate_filter(const QueryGraph &G, const DataModel &data, const cnf::CNF &filter) const override
void read_json(Diagnostic &diag, std::istream &in, const ThreadSafePooledString &name_of_database)
A Type that represents the absence of any other type.
The numeric type represents integer and floating-point types of different precision and scale.
static Options & Get()
Return a reference to the single Options instance.
This table represents all explored plans with their sub-plans, estimated size, cost,...
This table represents all explored plans with their sub-plans, estimated size, cost,...
A data type representing a pooled (or internalized) object.
Pooled< T, Pool, false > assert_not_none() const
The query graph represents all data sources and joins in a graph structure.
const auto & sources() const
Implements a small and efficient set over integers in the range of 0 to 63 (including).
std::size_t size() const
Returns the number of elements in this SmallBitset.
bool empty() const
Returns true if there are no elements in this SmallBitset.
table_spn_map spns_
a map from table to Spn
std::vector< std::size_t > max_frequencies_
the maximum frequencies of values of attributes to join
std::unique_ptr< DataModel > operator()(estimate_join_all_tag, PlanTable &&PT, const QueryGraph &G, Subproblem to_join, const cnf::CNF &condition) const
std::unique_ptr< DataModel > estimate_join(const QueryGraph &G, const DataModel &left, const DataModel &right, const cnf::CNF &condition) const override
static std::size_t max_frequency(const SpnDataModel &data, SpnJoin &join)
Compute the maximum frequency of values of the attribute in the join.
void learn_spns()
Learn an Spn on every table in the database.
std::unordered_map< ThreadSafePooledString, SpnWrapper * > table_to_spn_
the map from every table to its respective Spn, initially empty
static std::pair< unsigned, bool > find_spn_id(const SpnDataModel &data, SpnJoin &join)
Function to compute which of the two join identifiers belongs to the given data model and which attri...
std::unique_ptr< DataModel > estimate_limit(const QueryGraph &G, const DataModel &data, std::size_t limit, std::size_t offset) const override
ThreadSafePooledString name_of_database_
the name of the database, the estimator is built on
std::unique_ptr< DataModel > estimate_scan(const QueryGraph &G, Subproblem P) const override
std::unique_ptr< DataModel > empty_model() const override
std::pair< SpnIdentifier, SpnIdentifier > SpnJoin
void print(std::ostream &out) const override
std::unordered_map< ThreadSafePooledString, std::reference_wrapper< const SpnWrapper > > table_spn_map
void learn_new_spn(const ThreadSafePooledString &name_of_table)
Add a new Spn for a table in the database.
std::unique_ptr< DataModel > estimate_grouping(const QueryGraph &G, const DataModel &data, const std::vector< group_type > &groups) const override
std::unique_ptr< DataModel > estimate_filter(const QueryGraph &G, const DataModel &data, const cnf::CNF &filter) const override
std::size_t predict_cardinality(const DataModel &data) const override
A wrapper class for an Spn to be used in the context of databases.
std::size_t estimate_number_distinct_values(const ThreadSafePooledString &attribute) const
Estimate the number of distinct values of the given attribute.
std::size_t num_rows() const
returns the number of rows in the SPN.
static std::unordered_map< ThreadSafePooledString, SpnWrapper * > learn_spn_database(const ThreadSafePooledString &name_of_database, std::unordered_map< ThreadSafePooledString, std::vector< Spn::LeafType > > leaf_types=decltype(leaf_types)())
Learn SPNs over the tables in the given database.
static SpnWrapper learn_spn_table(const ThreadSafePooledString &name_of_database, const ThreadSafePooledString &name_of_table, std::vector< Spn::LeafType > leaf_types=decltype(leaf_types)())
Learn an SPN over the given table.
const std::unordered_map< ThreadSafePooledString, unsigned > & get_attribute_to_id() const
Get the reference to the attribute to spn internal id mapping.
Tree structure for Sum Product Networks.
std::unordered_map< unsigned, std::pair< SpnOperator, float > > Filter
std::unique_ptr< Expr > lhs
std::unique_ptr< Expr > rhs
A constant: a string literal or a numeric constant.
const Type * type() const
Returns the Type of this Expr.
A query expression for nested queries.
ThreadSafePooledOptionalString text
declared as optional for dummy tokens
A unary expression: "+e", "-e", "~e", "NOT e".
A CNF represents a conjunction of cnf::Clauses.
Signals that an index-based or key-based access was out of range.