souffle  2.0.2-371-g6315b36
MakeIndex.cpp
Go to the documentation of this file.
1 /*
2  * Souffle - A Datalog Compiler
3  * Copyright (c) 2018, The Souffle Developers. All rights reserved
4  * Licensed under the Universal Permissive License v 1.0 as shown at:
5  * - https://opensource.org/licenses/UPL
6  * - <souffle root>/licenses/SOUFFLE-UPL.txt
7  */
8 
9 /************************************************************************
10  *
11  * @file MakeIndex.cpp
12  *
13  ***********************************************************************/
14 
16 #include "FunctorOps.h"
17 #include "RelationTag.h"
18 #include "ram/Condition.h"
19 #include "ram/Constraint.h"
20 #include "ram/Expression.h"
21 #include "ram/Node.h"
22 #include "ram/Operation.h"
23 #include "ram/Program.h"
24 #include "ram/Relation.h"
25 #include "ram/Statement.h"
26 #include "ram/utility/Utils.h"
27 #include "ram/utility/Visitor.h"
29 #include "souffle/RamTypes.h"
32 #include <algorithm>
33 #include <cstddef>
34 #include <functional>
35 #include <memory>
36 #include <tuple>
37 #include <utility>
38 #include <vector>
39 
40 namespace souffle::ram::transform {
41 
42 using ExpressionPair = std::pair<Own<Expression>, Own<Expression>>;
43 
45  const Constraint* binRelOp, size_t& element, int identifier) {
46  if (isLessEqual(binRelOp->getOperator())) {
47  // Tuple[level, element] <= <expr>
48  if (const auto* lhs = dynamic_cast<const TupleElement*>(&binRelOp->getLHS())) {
49  const Expression* rhs = &binRelOp->getRHS();
50  if (lhs->getTupleId() == identifier && rla->getLevel(rhs) < identifier) {
51  element = lhs->getElement();
52  return {mk<UndefValue>(), clone(rhs)};
53  }
54  }
55  // <expr> <= Tuple[level, element]
56  if (const auto* rhs = dynamic_cast<const TupleElement*>(&binRelOp->getRHS())) {
57  const Expression* lhs = &binRelOp->getLHS();
58  if (rhs->getTupleId() == identifier && rla->getLevel(lhs) < identifier) {
59  element = rhs->getElement();
60  return {clone(lhs), mk<UndefValue>()};
61  }
62  }
63  }
64 
65  if (isGreaterEqual(binRelOp->getOperator())) {
66  // Tuple[level, element] >= <expr>
67  if (const auto* lhs = dynamic_cast<const TupleElement*>(&binRelOp->getLHS())) {
68  const Expression* rhs = &binRelOp->getRHS();
69  if (lhs->getTupleId() == identifier && rla->getLevel(rhs) < identifier) {
70  element = lhs->getElement();
71  return {clone(rhs), mk<UndefValue>()};
72  }
73  }
74  // <expr> >= Tuple[level, element]
75  if (const auto* rhs = dynamic_cast<const TupleElement*>(&binRelOp->getRHS())) {
76  const Expression* lhs = &binRelOp->getLHS();
77  if (rhs->getTupleId() == identifier && rla->getLevel(lhs) < identifier) {
78  element = rhs->getElement();
79  return {mk<UndefValue>(), clone(lhs)};
80  }
81  }
82  }
83  return {mk<UndefValue>(), mk<UndefValue>()};
84 }
85 
86 // Retrieves the <expr1> <= Tuple[level, element] <= <expr2> part of the constraint as a pair { <expr1>,
87 // <expr2> }
89  if (auto* binRelOp = dynamic_cast<Constraint*>(c)) {
90  bool interpreter = !Global::config().has("compile") && !Global::config().has("dl-program") &&
91  !Global::config().has("generate") && !Global::config().has("swig");
92  // don't index FEQ in interpreter mode
93  if (binRelOp->getOperator() == BinaryConstraintOp::FEQ && interpreter) {
94  return {mk<UndefValue>(), mk<UndefValue>()};
95  } else if (isEqConstraint(binRelOp->getOperator())) {
96  if (const auto* lhs = dynamic_cast<const TupleElement*>(&binRelOp->getLHS())) {
97  const Expression* rhs = &binRelOp->getRHS();
98  if (lhs->getTupleId() == identifier && rla->getLevel(rhs) < identifier) {
99  element = lhs->getElement();
100  return {clone(rhs), clone(rhs)};
101  }
102  }
103  if (const auto* rhs = dynamic_cast<const TupleElement*>(&binRelOp->getRHS())) {
104  const Expression* lhs = &binRelOp->getLHS();
105  if (rhs->getTupleId() == identifier && rla->getLevel(lhs) < identifier) {
106  element = rhs->getElement();
107  return {clone(lhs), clone(lhs)};
108  }
109  }
110  } else if (isWeakIneqConstraint(binRelOp->getOperator())) {
111  return getExpressionPair(binRelOp, element, identifier);
112  }
113  }
114  return {mk<UndefValue>(), mk<UndefValue>()};
115 }
116 
117 Own<Condition> MakeIndexTransformer::constructPattern(const std::vector<std::string>& attributeTypes,
118  RamPattern& queryPattern, bool& indexable, VecOwn<Condition> conditionList, int identifier) {
119  // Remaining conditions which cannot be handled by an index
120  Own<Condition> condition;
121  auto addCondition = [&](Own<Condition> c) {
122  if (condition != nullptr) {
123  condition = mk<Conjunction>(std::move(condition), std::move(c));
124  } else {
125  condition = std::move(c);
126  }
127  };
128 
129  // transform condition list so that every strict inequality becomes a weak inequality + filter
130  // e.g. Tuple[level, element] < <expr> --> Tuple[level, element] <= <expr> and Tuple[level, element] !=
131  // <expr>
132  std::vector<std::unique_ptr<Condition>> toAppend;
133  auto it = conditionList.begin();
134  while (it != conditionList.end()) {
135  auto* binRelOp = dynamic_cast<Constraint*>(it->get());
136  if (binRelOp == nullptr) {
137  ++it;
138  continue;
139  }
140 
141  bool transformable = false;
142 
143  if (isStrictIneqConstraint(binRelOp->getOperator())) {
144  if (const auto* lhs = dynamic_cast<const TupleElement*>(&binRelOp->getLHS())) {
145  const Expression* rhs = &binRelOp->getRHS();
146  if (lhs->getTupleId() == identifier && rla->getLevel(rhs) < identifier) {
147  transformable = true;
148  }
149  }
150  if (const auto* rhs = dynamic_cast<const TupleElement*>(&binRelOp->getRHS())) {
151  const Expression* lhs = &binRelOp->getLHS();
152  if (rhs->getTupleId() == identifier && rla->getLevel(lhs) < identifier) {
153  transformable = true;
154  }
155  }
156  }
157 
158  if (transformable) {
159  // append the weak version of inequality
160  toAppend.emplace_back(
161  std::make_unique<Constraint>(convertStrictToWeakIneqConstraint(binRelOp->getOperator()),
162  clone(&binRelOp->getLHS()), clone(&binRelOp->getRHS())));
163  // append the != constraint
164  toAppend.emplace_back(
165  std::make_unique<Constraint>(convertStrictToNotEqualConstraint(binRelOp->getOperator()),
166  clone(&binRelOp->getLHS()), clone(&binRelOp->getRHS())));
167 
168  // remove the strict version of inequality
169  it = conditionList.erase(it);
170  } else {
171  ++it;
172  }
173  }
174 
175  std::transform(toAppend.begin(), toAppend.end(), std::back_inserter(conditionList),
176  [](const std::unique_ptr<Condition>& cond) { return clone(cond); });
177 
178  // Build query pattern and remaining condition
179  for (auto& cond : conditionList) {
180  size_t element = 0;
181  Own<Expression> lowerExpression;
182  Own<Expression> upperExpression;
183  std::tie(lowerExpression, upperExpression) = getLowerUpperExpression(cond.get(), element, identifier);
184 
185  // we have new bounds if at least one is defined
186  if (!isUndefValue(lowerExpression.get()) || !isUndefValue(upperExpression.get())) {
187  // if no previous bounds are set then just assign them, consider both bounds to be set (but not
188  // necessarily defined) in all remaining cases
189  auto type = attributeTypes[element];
190  indexable = true;
191  if (isUndefValue(queryPattern.first[element].get()) &&
192  isUndefValue(queryPattern.second[element].get())) {
193  queryPattern.first[element] = std::move(lowerExpression);
194  queryPattern.second[element] = std::move(upperExpression);
195  // if lower bound is undefined and we have a new lower bound then assign it
196  } else if (isUndefValue(queryPattern.first[element].get()) &&
197  !isUndefValue(lowerExpression.get()) && isUndefValue(upperExpression.get())) {
198  queryPattern.first[element] = std::move(lowerExpression);
199  // if upper bound is undefined and we have a new upper bound then assign it
200  } else if (isUndefValue(queryPattern.second[element].get()) &&
201  isUndefValue(lowerExpression.get()) && !isUndefValue(upperExpression.get())) {
202  queryPattern.second[element] = std::move(upperExpression);
203  // if both bounds are defined ...
204  // and equal then we have a previous equality constraint i.e. Tuple[level, element] = <expr1>
205  } else if (!isUndefValue(queryPattern.first[element].get()) &&
206  !isUndefValue(queryPattern.second[element].get()) &&
207  (*(queryPattern.first[element]) == *(queryPattern.second[element]))) {
208  // new equality constraint i.e. Tuple[level, element] = <expr2>
209  // simply hoist <expr1> = <expr2> to the outer loop
210  if (!isUndefValue(lowerExpression.get()) && !isUndefValue(upperExpression.get())) {
211  // FIXME: `FEQ` handling; need to know if the expr is a float exp or not
212  addCondition(mk<Constraint>(getEqConstraint(type),
213  souffle::clone(queryPattern.first[element]), std::move(lowerExpression)));
214  }
215  // new lower bound i.e. Tuple[level, element] >= <expr2>
216  // we need to hoist <expr1> >= <expr2> to the outer loop
217  else if (!isUndefValue(lowerExpression.get()) && isUndefValue(upperExpression.get())) {
218  addCondition(mk<Constraint>(getGreaterEqualConstraint(type),
219  souffle::clone(queryPattern.first[element]), std::move(lowerExpression)));
220  }
221  // new upper bound i.e. Tuple[level, element] <= <expr2>
222  // we need to hoist <expr1> <= <expr2> to the outer loop
223  else if (isUndefValue(lowerExpression.get()) && !isUndefValue(upperExpression.get())) {
224  addCondition(mk<Constraint>(getLessEqualConstraint(type),
225  souffle::clone(queryPattern.first[element]), std::move(upperExpression)));
226  }
227  // if either bound is defined but they aren't equal we must consider the cases for updating
228  // them note that at this point we know that if we have a lower/upper bound it can't be the
229  // first one
230  } else if (!isUndefValue(queryPattern.first[element].get()) ||
231  !isUndefValue(queryPattern.second[element].get())) {
232  // if we have a new equality constraint and previous inequality constraints
233  if (!isUndefValue(lowerExpression.get()) && !isUndefValue(upperExpression.get()) &&
234  *lowerExpression == *upperExpression) {
235  // if Tuple[level, element] >= <expr1> and we see Tuple[level, element] = <expr2>
236  // need to hoist <expr2> >= <expr1> to the outer loop
237  if (!isUndefValue(queryPattern.first[element].get())) {
238  addCondition(mk<Constraint>(getGreaterEqualConstraint(type),
239  souffle::clone(lowerExpression), std::move(queryPattern.first[element])));
240  }
241  // if Tuple[level, element] <= <expr1> and we see Tuple[level, element] = <expr2>
242  // need to hoist <expr2> <= <expr1> to the outer loop
243  if (!isUndefValue(queryPattern.second[element].get())) {
244  addCondition(mk<Constraint>(getLessEqualConstraint(type),
245  souffle::clone(upperExpression), std::move(queryPattern.second[element])));
246  }
247  // finally replace bounds with equality constraint
248  queryPattern.first[element] = std::move(lowerExpression);
249  queryPattern.second[element] = std::move(upperExpression);
250  // if we have a new lower bound
251  } else if (!isUndefValue(lowerExpression.get())) {
252  // we want the tightest lower bound so we take the max
253  VecOwn<Expression> maxArguments;
254  maxArguments.push_back(std::move(queryPattern.first[element]));
255  maxArguments.push_back(std::move(lowerExpression));
256 
257  queryPattern.first[element] =
258  mk<IntrinsicOperator>(getMaxOp(type), std::move(maxArguments));
259  // if we have a new upper bound
260  } else if (!isUndefValue(upperExpression.get())) {
261  // we want the tightest upper bound so we take the min
262  VecOwn<Expression> minArguments;
263  minArguments.push_back(std::move(queryPattern.second[element]));
264  minArguments.push_back(std::move(upperExpression));
265 
266  queryPattern.second[element] =
267  mk<IntrinsicOperator>(getMinOp(type), std::move(minArguments));
268  }
269  }
270  } else {
271  addCondition(std::move(cond));
272  }
273  }
274 
275  // Avoid null-pointers for condition and query pattern
276  if (condition == nullptr) {
277  condition = mk<True>();
278  }
279  return condition;
280 }
281 
282 Own<Operation> MakeIndexTransformer::rewriteAggregate(const Aggregate* agg) {
283  if (dynamic_cast<const True*>(&agg->getCondition()) == nullptr) {
284  const Relation& rel = relAnalysis->lookup(agg->getRelation());
285  int identifier = agg->getTupleId();
286  RamPattern queryPattern;
287  for (unsigned int i = 0; i < rel.getArity(); ++i) {
288  queryPattern.first.push_back(mk<UndefValue>());
289  queryPattern.second.push_back(mk<UndefValue>());
290  }
291 
292  bool indexable = false;
293  Own<Condition> condition = constructPattern(rel.getAttributeTypes(), queryPattern, indexable,
294  toConjunctionList(&agg->getCondition()), identifier);
295  if (indexable) {
296  return mk<IndexAggregate>(souffle::clone(&agg->getOperation()), agg->getFunction(),
297  agg->getRelation(), souffle::clone(&agg->getExpression()), std::move(condition),
298  std::move(queryPattern), agg->getTupleId());
299  }
300  }
301  return nullptr;
302 }
303 
304 Own<Operation> MakeIndexTransformer::rewriteScan(const Scan* scan) {
305  if (const auto* filter = dynamic_cast<const Filter*>(&scan->getOperation())) {
306  const Relation& rel = relAnalysis->lookup(scan->getRelation());
307  const int identifier = scan->getTupleId();
308  RamPattern queryPattern;
309  for (unsigned int i = 0; i < rel.getArity(); ++i) {
310  queryPattern.first.push_back(mk<UndefValue>());
311  queryPattern.second.push_back(mk<UndefValue>());
312  }
313 
314  bool indexable = false;
315  Own<Condition> condition = constructPattern(rel.getAttributeTypes(), queryPattern, indexable,
316  toConjunctionList(&filter->getCondition()), identifier);
317  if (indexable) {
318  Own<Operation> op = souffle::clone(&filter->getOperation());
319  if (!isTrue(condition.get())) {
320  op = mk<Filter>(std::move(condition), std::move(op));
321  }
322  return mk<IndexScan>(scan->getRelation(), identifier, std::move(queryPattern), std::move(op),
323  scan->getProfileText());
324  }
325  }
326  return nullptr;
327 }
328 
329 Own<Operation> MakeIndexTransformer::rewriteIndexScan(const IndexScan* iscan) {
330  if (const auto* filter = dynamic_cast<const Filter*>(&iscan->getOperation())) {
331  const Relation& rel = relAnalysis->lookup(iscan->getRelation());
332  const int identifier = iscan->getTupleId();
333 
334  RamPattern strengthenedPattern;
335  strengthenedPattern.first = clone(iscan->getRangePattern().first);
336  strengthenedPattern.second = clone(iscan->getRangePattern().second);
337 
338  bool indexable = false;
339  // strengthen the pattern with construct pattern
340  Own<Condition> condition = constructPattern(rel.getAttributeTypes(), strengthenedPattern, indexable,
341  toConjunctionList(&filter->getCondition()), identifier);
342 
343  if (indexable) {
344  // Merge Index Pattern here
345 
346  Own<Operation> op = souffle::clone(&filter->getOperation());
347  if (!isTrue(condition.get())) {
348  op = mk<Filter>(std::move(condition), std::move(op));
349  }
350  return mk<IndexScan>(iscan->getRelation(), identifier, std::move(strengthenedPattern),
351  std::move(op), iscan->getProfileText());
352  }
353  }
354  return nullptr;
355 }
356 
357 bool MakeIndexTransformer::makeIndex(Program& program) {
358  bool changed = false;
359  visitDepthFirst(program, [&](const Query& query) {
360  std::function<Own<Node>(Own<Node>)> scanRewriter = [&](Own<Node> node) -> Own<Node> {
361  if (const Scan* scan = dynamic_cast<Scan*>(node.get())) {
362  const Relation& rel = relAnalysis->lookup(scan->getRelation());
363  if (rel.getRepresentation() != RelationRepresentation::INFO) {
364  if (Own<Operation> op = rewriteScan(scan)) {
365  changed = true;
366  node = std::move(op);
367  }
368  }
369  } else if (const IndexScan* iscan = dynamic_cast<IndexScan*>(node.get())) {
370  if (Own<Operation> op = rewriteIndexScan(iscan)) {
371  changed = true;
372  node = std::move(op);
373  }
374  } else if (const Aggregate* agg = dynamic_cast<Aggregate*>(node.get())) {
375  if (Own<Operation> op = rewriteAggregate(agg)) {
376  changed = true;
377  node = std::move(op);
378  }
379  }
380  node->apply(makeLambdaRamMapper(scanRewriter));
381  return node;
382  };
383  const_cast<Query*>(&query)->apply(makeLambdaRamMapper(scanRewriter));
384  });
385  return changed;
386 }
387 
388 } // namespace souffle::ram::transform
souffle::ram::transform
Definition: ChoiceConversion.cpp:30
souffle::ram::analysis::LevelAnalysis::getLevel
int getLevel(const Node *value) const
Get level of a RAM expression/condition.
Definition: Level.cpp:64
BinaryConstraintOps.h
souffle::isGreaterEqual
bool isGreaterEqual(const BinaryConstraintOp constraintOp)
Definition: BinaryConstraintOps.h:217
souffle::ram::transform::MakeIndexTransformer::getLowerUpperExpression
ExpressionPair getLowerUpperExpression(Condition *c, size_t &element, int level)
Definition: MakeIndex.cpp:92
souffle::ram::transform::MakeIndexTransformer::rewriteIndexScan
Own< Operation > rewriteIndexScan(const IndexScan *iscan)
Rewrite an index scan operation to an amended index scan operation.
Definition: MakeIndex.cpp:333
Constraint.h
souffle::getMaxOp
FunctorOp getMaxOp(const std::string &type)
Definition: FunctorOps.cpp:276
souffle::ram::isUndefValue
bool isUndefValue(const Expression *expr)
Determines if an expression represents an undefined value.
Definition: Utils.h:40
souffle::ram::toConjunctionList
VecOwn< Condition > toConjunctionList(const Condition *condition)
Convert terms of a conjunction to a list.
Definition: Utils.h:57
souffle::ram::transform::Transformer::apply
bool apply(TranslationUnit &translationUnit)
@Brief apply the transformer to a translation unit @Param translationUnit that will be transformed.
Definition: Transformer.cpp:39
souffle::getGreaterEqualConstraint
BinaryConstraintOp getGreaterEqualConstraint(const std::string &type)
Definition: BinaryConstraintOps.h:94
souffle::Own
std::unique_ptr< A > Own
Definition: ContainerUtil.h:42
souffle::ram::transform::MakeIndexTransformer::rewriteAggregate
Own< Operation > rewriteAggregate(const Aggregate *agg)
Rewrite an aggregate operation to an indexed aggregate operation.
Definition: MakeIndex.cpp:286
MiscUtil.h
souffle::isWeakIneqConstraint
bool isWeakIneqConstraint(const BinaryConstraintOp constraintOp)
Definition: BinaryConstraintOps.h:146
souffle::getLessEqualConstraint
BinaryConstraintOp getLessEqualConstraint(const std::string &type)
Definition: BinaryConstraintOps.h:84
souffle::identifier
std::string identifier(std::string id)
Valid C++ identifier, note that this does not ensure the uniqueness of identifiers returned.
Definition: StringUtil.h:387
souffle::ram::TupleElement
Access element from the current tuple in a tuple environment.
Definition: TupleElement.h:42
rhs
Own< Argument > rhs
Definition: ResolveAliases.cpp:185
souffle::ram::transform::MakeIndexTransformer::rewriteScan
Own< Operation > rewriteScan(const Scan *scan)
Rewrite a scan operation to an indexed scan operation.
Definition: MakeIndex.cpp:308
souffle::BinaryConstraintOp::FEQ
@ FEQ
Program.h
lhs
Own< Argument > lhs
Definition: ResolveAliases.cpp:184
souffle::getMinOp
FunctorOp getMinOp(const std::string &type)
Given a type of an an attribute it returns the appropriate min/max functor operation.
Definition: FunctorOps.cpp:267
Visitor.h
souffle::clone
auto clone(const std::vector< A * > &xs)
Definition: ContainerUtil.h:172
i
size_t i
Definition: json11.h:663
souffle::filter
std::vector< A > filter(std::vector< A > xs, F &&f)
Filter a vector to include certain elements.
Definition: FunctionalUtil.h:155
ContainerUtil.h
souffle::ram::transform::ExpressionPair
std::pair< Own< Expression >, Own< Expression > > ExpressionPair
Definition: MakeIndex.cpp:46
RelationTag.h
souffle::ram::Relation
An abstract class for performing indexed operations.
Definition: Relation.h:40
souffle::convertStrictToNotEqualConstraint
BinaryConstraintOp convertStrictToNotEqualConstraint(const BinaryConstraintOp constraintOp)
Definition: BinaryConstraintOps.h:173
Relation.h
souffle::ram::transform::MakeIndexTransformer::constructPattern
Own< Condition > constructPattern(const std::vector< std::string > &attributeTypes, RamPattern &queryPattern, bool &indexable, VecOwn< Condition > conditionList, int identifier)
Definition: MakeIndex.cpp:121
Condition.h
souffle::isLessEqual
bool isLessEqual(const BinaryConstraintOp constraintOp)
Definition: BinaryConstraintOps.h:207
Utils.h
souffle::ram::isTrue
bool isTrue(const Condition *cond)
Determines if a condition represents true.
Definition: Utils.h:45
souffle::isEqConstraint
bool isEqConstraint(const BinaryConstraintOp constraintOp)
Definition: BinaryConstraintOps.h:124
souffle::ram::transform::MakeIndexTransformer::relAnalysis
analysis::RelationAnalysis * relAnalysis
Definition: MakeIndex.h:140
souffle::ram::Scan
Iterate all tuples of a relation.
Definition: Scan.h:47
souffle::ram::IndexScan
Search for tuples of a relation matching a criteria.
Definition: IndexScan.h:52
souffle::isStrictIneqConstraint
bool isStrictIneqConstraint(const BinaryConstraintOp constraintOp)
Definition: BinaryConstraintOps.h:133
souffle::Global::config
static MainConfig & config()
Definition: Global.h:141
souffle::ram::Constraint
Evaluates a binary constraint with respect to two Expressions.
Definition: Constraint.h:55
Node.h
Operation.h
souffle::ram::RamPattern
std::pair< RamBound, RamBound > RamPattern
Definition: IndexOperation.h:42
RamTypes.h
MakeIndex.h
Statement.h
souffle::RelationRepresentation::INFO
@ INFO
souffle::getEqConstraint
BinaryConstraintOp getEqConstraint(const std::string &type)
Definition: BinaryConstraintOps.h:74
FunctorOps.h
Expression.h
souffle::ram::transform::MakeIndexTransformer::makeIndex
bool makeIndex(Program &program)
Make indexable RAM operation indexed.
Definition: MakeIndex.cpp:361
souffle::ram::transform::MakeIndexTransformer::getExpressionPair
ExpressionPair getExpressionPair(const Constraint *binRelOp, size_t &element, int identifier)
Definition: MakeIndex.cpp:48
souffle::ram::analysis::RelationAnalysis::lookup
const ram::Relation & lookup(const std::string &name) const
Definition: Relation.cpp:33
souffle::ram::visitDepthFirst
void visitDepthFirst(const Node &root, Visitor< R, Ps... > &visitor, Args &... args)
A utility function visiting all nodes within the RAM fragments rooted by the given node recursively i...
Definition: Visitor.h:357
rel
void rel(size_t limit, bool showLimit=true)
Definition: Tui.h:1086
souffle::ram::transform::MakeIndexTransformer::rla
analysis::LevelAnalysis * rla
Definition: MakeIndex.h:132
souffle::ram::Expression
Abstract class for describing scalar values in RAM.
Definition: Expression.h:33
std::type
ElementType type
Definition: span.h:640
souffle::convertStrictToWeakIneqConstraint
BinaryConstraintOp convertStrictToWeakIneqConstraint(const BinaryConstraintOp constraintOp)
Definition: BinaryConstraintOps.h:159
souffle::ram::makeLambdaRamMapper
LambdaNodeMapper< Lambda > makeLambdaRamMapper(const Lambda &lambda)
Creates a node mapper based on a corresponding lambda expression.
Definition: LambdaNodeMapper.h:67