souffle  2.0.2-371-g6315b36
ExplainProvenanceImpl.h
Go to the documentation of this file.
1 /*
2  * Souffle - A Datalog Compiler
3  * Copyright (c) 2017, 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 ExplainProvenanceImpl.h
12  *
13  * Implementation of abstract class in ExplainProvenance.h for guided Impl provenance
14  *
15  ***********************************************************************/
16 
17 #pragma once
18 
20 #include "souffle/RamTypes.h"
22 #include "souffle/SymbolTable.h"
29 #include <algorithm>
30 #include <cassert>
31 #include <chrono>
32 #include <cstdio>
33 #include <iostream>
34 #include <map>
35 #include <memory>
36 #include <regex>
37 #include <sstream>
38 #include <string>
39 #include <tuple>
40 #include <type_traits>
41 #include <utility>
42 #include <vector>
43 
44 namespace souffle {
45 
46 using namespace stream_write_qualified_char_as_number;
47 
48 class ExplainProvenanceImpl : public ExplainProvenance {
49  using arity_type = Relation::arity_type;
50 
51 public:
52  ExplainProvenanceImpl(SouffleProgram& prog) : ExplainProvenance(prog) {
53  setup();
54  }
55 
56  void setup() override {
57  // for each clause, store a mapping from the head relation name to body relation names
58  for (auto& rel : prog.getAllRelations()) {
59  std::string name = rel->getName();
60 
61  // only process info relations
62  if (name.find("@info") == std::string::npos) {
63  continue;
64  }
65 
66  // find all the info tuples
67  for (auto& tuple : *rel) {
68  std::vector<std::string> bodyLiterals;
69 
70  RamDomain ruleNum;
71  tuple >> ruleNum;
72 
73  for (size_t i = 1; i + 1 < rel->getArity(); i++) {
74  std::string bodyLit;
75  tuple >> bodyLit;
76  bodyLiterals.push_back(bodyLit);
77  }
78 
79  std::string rule;
80  tuple >> rule;
81 
82  info.insert({std::make_pair(name.substr(0, name.find(".@info")), ruleNum), bodyLiterals});
83  rules.insert({std::make_pair(name.substr(0, name.find(".@info")), ruleNum), rule});
84  }
85  }
86  }
87 
88  Own<TreeNode> explain(
89  std::string relName, std::vector<RamDomain> tuple, int ruleNum, int levelNum, size_t depthLimit) {
90  std::stringstream joinedArgs;
91  joinedArgs << join(decodeArguments(relName, tuple), ", ");
92  auto joinedArgsStr = joinedArgs.str();
93 
94  // if fact
95  if (levelNum == 0) {
96  return mk<LeafNode>(relName + "(" + joinedArgsStr + ")");
97  }
98 
99  assert(info.find(std::make_pair(relName, ruleNum)) != info.end() && "invalid rule for tuple");
100 
101  // if depth limit exceeded
102  if (depthLimit <= 1) {
103  tuple.push_back(ruleNum);
104  tuple.push_back(levelNum);
105 
106  // find if subproof exists already
107  size_t idx = 0;
108  auto it = std::find(subproofs.begin(), subproofs.end(), tuple);
109  if (it != subproofs.end()) {
110  idx = it - subproofs.begin();
111  } else {
112  subproofs.push_back(tuple);
113  idx = subproofs.size() - 1;
114  }
115 
116  return mk<LeafNode>("subproof " + relName + "(" + std::to_string(idx) + ")");
117  }
118 
119  tuple.push_back(levelNum);
120 
121  auto internalNode =
122  mk<InnerNode>(relName + "(" + joinedArgsStr + ")", "(R" + std::to_string(ruleNum) + ")");
123 
124  // make return vector pointer
125  std::vector<RamDomain> ret;
126 
127  // execute subroutine to get subproofs
128  prog.executeSubroutine(relName + "_" + std::to_string(ruleNum) + "_subproof", tuple, ret);
129 
130  // recursively get nodes for subproofs
131  size_t tupleCurInd = 0;
132  auto bodyRelations = info[std::make_pair(relName, ruleNum)];
133 
134  // start from begin + 1 because the first element represents the head atom
135  for (auto it = bodyRelations.begin() + 1; it < bodyRelations.end(); it++) {
136  std::string bodyLiteral = *it;
137  // split bodyLiteral since it contains relation name plus arguments
138  std::string bodyRel = splitString(bodyLiteral, ',')[0];
139 
140  // check whether the current atom is a constraint
141  assert(bodyRel.size() > 0 && "body of a relation should have positive length");
142  bool isConstraint = contains(constraintList, bodyRel);
143 
144  // handle negated atom names
145  auto bodyRelAtomName = bodyRel;
146  if (bodyRel[0] == '!' && bodyRel != "!=") {
147  bodyRelAtomName = bodyRel.substr(1);
148  }
149 
150  // traverse subroutine return
151  size_t arity;
152  size_t auxiliaryArity;
153  if (isConstraint) {
154  // we only handle binary constraints, and assume arity is 4 to account for hidden provenance
155  // annotations
156  arity = 4;
157  auxiliaryArity = 2;
158  } else {
159  arity = prog.getRelation(bodyRelAtomName)->getArity();
160  auxiliaryArity = prog.getRelation(bodyRelAtomName)->getAuxiliaryArity();
161  }
162  auto tupleEnd = tupleCurInd + arity;
163 
164  // store current tuple
165  std::vector<RamDomain> subproofTuple;
166 
167  for (; tupleCurInd < tupleEnd - auxiliaryArity; tupleCurInd++) {
168  subproofTuple.push_back(ret[tupleCurInd]);
169  }
170 
171  int subproofRuleNum = ret[tupleCurInd];
172  int subproofLevelNum = ret[tupleCurInd + 1];
173 
174  tupleCurInd += 2;
175 
176  // for a negation, display the corresponding tuple and do not recurse
177  if (bodyRel[0] == '!' && bodyRel != "!=") {
178  std::stringstream joinedTuple;
179  joinedTuple << join(decodeArguments(bodyRelAtomName, subproofTuple), ", ");
180  auto joinedTupleStr = joinedTuple.str();
181  internalNode->add_child(mk<LeafNode>(bodyRel + "(" + joinedTupleStr + ")"));
182  internalNode->setSize(internalNode->getSize() + 1);
183  // for a binary constraint, display the corresponding values and do not recurse
184  } else if (isConstraint) {
185  std::stringstream joinedConstraint;
186 
187  // FIXME: We need type info in order to figure out how to print arguments.
188  BinaryConstraintOp rawBinOp = toBinaryConstraintOp(bodyRel);
189  if (isOrderedBinaryConstraintOp(rawBinOp)) {
190  joinedConstraint << subproofTuple[0] << " " << bodyRel << " " << subproofTuple[1];
191  } else {
192  joinedConstraint << bodyRel << "(\"" << symTable.resolve(subproofTuple[0]) << "\", \""
193  << symTable.resolve(subproofTuple[1]) << "\")";
194  }
195 
196  internalNode->add_child(mk<LeafNode>(joinedConstraint.str()));
197  internalNode->setSize(internalNode->getSize() + 1);
198  // otherwise, for a normal tuple, recurse
199  } else {
200  auto child =
201  explain(bodyRel, subproofTuple, subproofRuleNum, subproofLevelNum, depthLimit - 1);
202  internalNode->setSize(internalNode->getSize() + child->getSize());
203  internalNode->add_child(std::move(child));
204  }
205 
206  tupleCurInd = tupleEnd;
207  }
208 
209  return internalNode;
210  }
211 
212  Own<TreeNode> explain(std::string relName, std::vector<std::string> args, size_t depthLimit) override {
213  auto tuple = argsToNums(relName, args);
214  if (tuple.empty()) {
215  return mk<LeafNode>("Relation not found");
216  }
217 
218  std::tuple<int, int> tupleInfo = findTuple(relName, tuple);
219 
220  int ruleNum = std::get<0>(tupleInfo);
221  int levelNum = std::get<1>(tupleInfo);
222 
223  if (ruleNum < 0 || levelNum == -1) {
224  return mk<LeafNode>("Tuple not found");
225  }
226 
227  return explain(relName, tuple, ruleNum, levelNum, depthLimit);
228  }
229 
230  Own<TreeNode> explainSubproof(std::string relName, RamDomain subproofNum, size_t depthLimit) override {
231  if (subproofNum >= (int)subproofs.size()) {
232  return mk<LeafNode>("Subproof not found");
233  }
234 
235  auto tup = subproofs[subproofNum];
236 
237  auto rel = prog.getRelation(relName);
238 
239  RamDomain ruleNum;
240  ruleNum = tup[rel->getArity() - rel->getAuxiliaryArity()];
241 
242  RamDomain levelNum;
243  levelNum = tup[rel->getArity() - rel->getAuxiliaryArity() + 1];
244 
245  tup.erase(tup.begin() + rel->getArity() - rel->getAuxiliaryArity(), tup.end());
246 
247  return explain(relName, tup, ruleNum, levelNum, depthLimit);
248  }
249 
250  std::vector<std::string> explainNegationGetVariables(
251  std::string relName, std::vector<std::string> args, size_t ruleNum) override {
252  std::vector<std::string> variables;
253 
254  // check that the tuple actually doesn't exist
255  std::tuple<int, int> foundTuple = findTuple(relName, argsToNums(relName, args));
256  if (std::get<0>(foundTuple) != -1 || std::get<1>(foundTuple) != -1) {
257  // return a sentinel value
258  return std::vector<std::string>({"@"});
259  }
260 
261  // atom meta information stored for the current rule
262  auto atoms = info[std::make_pair(relName, ruleNum)];
263 
264  // the info stores the set of atoms, if there is only 1 atom, then it must be the head, so it must be
265  // a fact
266  if (atoms.size() <= 1) {
267  return std::vector<std::string>({"@fact"});
268  }
269 
270  // atoms[0] represents variables in the head atom
271  auto headVariables = splitString(atoms[0], ',');
272 
273  auto isVariable = [&](std::string arg) {
274  return !(isNumber(arg.c_str()) || arg[0] == '\"' || arg == "_");
275  };
276 
277  // check that head variable bindings make sense, i.e. for a head like a(x, x), make sure both x are
278  // the same value
279  std::map<std::string, std::string> headVariableMapping;
280  for (size_t i = 0; i < headVariables.size(); i++) {
281  if (!isVariable(headVariables[i])) {
282  continue;
283  }
284 
285  if (headVariableMapping.find(headVariables[i]) == headVariableMapping.end()) {
286  headVariableMapping[headVariables[i]] = args[i];
287  } else {
288  if (headVariableMapping[headVariables[i]] != args[i]) {
289  return std::vector<std::string>({"@non_matching"});
290  }
291  }
292  }
293 
294  // get body variables
295  std::vector<std::string> uniqueBodyVariables;
296  for (auto it = atoms.begin() + 1; it < atoms.end(); it++) {
297  auto atomRepresentation = splitString(*it, ',');
298 
299  // atomRepresentation.begin() + 1 because the first element is the relation name of the atom
300  // which is not relevant for finding variables
301  for (auto atomIt = atomRepresentation.begin() + 1; atomIt < atomRepresentation.end(); atomIt++) {
302  if (!isVariable(*atomIt)) {
303  continue;
304  }
305 
306  if (!contains(uniqueBodyVariables, *atomIt) && !contains(headVariables, *atomIt)) {
307  uniqueBodyVariables.push_back(*atomIt);
308  }
309  }
310  }
311 
312  return uniqueBodyVariables;
313  }
314 
315  Own<TreeNode> explainNegation(std::string relName, size_t ruleNum, const std::vector<std::string>& tuple,
316  std::map<std::string, std::string>& bodyVariables) override {
317  // construct a vector of unique variables that occur in the rule
318  std::vector<std::string> uniqueVariables;
319 
320  // we also need to know the type of each variable
321  std::map<std::string, char> variableTypes;
322 
323  // atom meta information stored for the current rule
324  auto atoms = info[std::make_pair(relName, ruleNum)];
325 
326  // atoms[0] represents variables in the head atom
327  auto headVariables = splitString(atoms[0], ',');
328 
329  uniqueVariables.insert(uniqueVariables.end(), headVariables.begin(), headVariables.end());
330 
331  auto isVariable = [&](std::string arg) {
332  return !(isNumber(arg.c_str()) || arg[0] == '\"' || arg == "_");
333  };
334 
335  // get body variables
336  for (auto it = atoms.begin() + 1; it < atoms.end(); it++) {
337  auto atomRepresentation = splitString(*it, ',');
338 
339  // atomRepresentation.begin() + 1 because the first element is the relation name of the atom
340  // which is not relevant for finding variables
341  for (auto atomIt = atomRepresentation.begin() + 1; atomIt < atomRepresentation.end(); atomIt++) {
342  if (!contains(uniqueVariables, *atomIt) && !contains(headVariables, *atomIt)) {
343  // ignore non-variables
344  if (!isVariable(*atomIt)) {
345  continue;
346  }
347 
348  uniqueVariables.push_back(*atomIt);
349 
350  if (!contains(constraintList, atomRepresentation[0])) {
351  // store type of variable
352  auto currentRel = prog.getRelation(atomRepresentation[0]);
353  assert(currentRel != nullptr &&
354  ("relation " + atomRepresentation[0] + " doesn't exist").c_str());
355  variableTypes[*atomIt] =
356  *currentRel->getAttrType(atomIt - atomRepresentation.begin() - 1);
357  } else if (atomIt->find("agg_") != std::string::npos) {
358  variableTypes[*atomIt] = 'i';
359  }
360  }
361  }
362  }
363 
364  std::vector<RamDomain> args;
365 
366  size_t varCounter = 0;
367 
368  // construct arguments to pass in to the subroutine
369  // - this contains the variable bindings selected by the user
370 
371  // add number representation of tuple
372  auto tupleNums = argsToNums(relName, tuple);
373  args.insert(args.end(), tupleNums.begin(), tupleNums.end());
374  varCounter += tuple.size();
375 
376  while (varCounter < uniqueVariables.size()) {
377  auto var = uniqueVariables[varCounter];
378  auto varValue = bodyVariables[var];
379  if (variableTypes[var] == 's') {
380  if (varValue.size() >= 2 && varValue[0] == '"' && varValue[varValue.size() - 1] == '"') {
381  auto originalStr = varValue.substr(1, varValue.size() - 2);
382  args.push_back(symTable.lookup(originalStr));
383  } else {
384  // assume no quotation marks
385  args.push_back(symTable.lookup(varValue));
386  }
387  } else {
388  args.push_back(std::stoi(varValue));
389  }
390 
391  varCounter++;
392  }
393 
394  // set up return and error vectors for subroutine calling
395  std::vector<RamDomain> ret;
396 
397  // execute subroutine to get subproofs
398  prog.executeSubroutine(relName + "_" + std::to_string(ruleNum) + "_negation_subproof", args, ret);
399 
400  // ensure the subroutine returns the correct number of results
401  assert(ret.size() == atoms.size() - 1);
402 
403  // construct tree nodes
404  std::stringstream joinedArgsStr;
405  joinedArgsStr << join(tuple, ",");
406  auto internalNode = mk<InnerNode>(
407  relName + "(" + joinedArgsStr.str() + ")", "(R" + std::to_string(ruleNum) + ")");
408 
409  // store the head tuple in bodyVariables so we can print
410  for (size_t i = 0; i < headVariables.size(); i++) {
411  bodyVariables[headVariables[i]] = tuple[i];
412  }
413 
414  // traverse return vector and construct child nodes
415  // making sure we display existent and non-existent tuples correctly
416  int literalCounter = 1;
417  for (RamDomain returnCounter : ret) {
418  // check what the next contained atom is
419  bool atomExists = true;
420  if (returnCounter == 0) {
421  atomExists = false;
422  }
423 
424  // get the relation of the current atom
425  auto atomRepresentation = splitString(atoms[literalCounter], ',');
426  std::string bodyRel = atomRepresentation[0];
427 
428  // check whether the current atom is a constraint
429  bool isConstraint = contains(constraintList, bodyRel);
430 
431  // handle negated atom names
432  auto bodyRelAtomName = bodyRel;
433  if (bodyRel[0] == '!' && bodyRel != "!=") {
434  bodyRelAtomName = bodyRel.substr(1);
435  }
436 
437  // construct a label for a node containing a literal (either constraint or atom)
438  std::stringstream childLabel;
439  if (isConstraint) {
440  // for a binary constraint, display the corresponding values and do not recurse
441  assert(atomRepresentation.size() == 3 && "not a binary constraint");
442 
443  childLabel << bodyVariables[atomRepresentation[1]] << " " << bodyRel << " "
444  << bodyVariables[atomRepresentation[2]];
445  } else {
446  childLabel << bodyRel << "(";
447  for (size_t i = 1; i < atomRepresentation.size(); i++) {
448  // if it's a non-variable, print either _ for unnamed, or constant value
449  if (!isVariable(atomRepresentation[i])) {
450  childLabel << atomRepresentation[i];
451  } else {
452  childLabel << bodyVariables[atomRepresentation[i]];
453  }
454  if (i < atomRepresentation.size() - 1) {
455  childLabel << ", ";
456  }
457  }
458  childLabel << ")";
459  }
460 
461  // build a marker for existence of body atoms
462  if (atomExists) {
463  childLabel << " ✓";
464  } else {
465  childLabel << " x";
466  }
467 
468  internalNode->add_child(mk<LeafNode>(childLabel.str()));
469  internalNode->setSize(internalNode->getSize() + 1);
470 
471  literalCounter++;
472  }
473 
474  return internalNode;
475  }
476 
477  std::string getRule(std::string relName, size_t ruleNum) override {
478  auto key = make_pair(relName, ruleNum);
479 
480  auto rule = rules.find(key);
481  if (rule == rules.end()) {
482  return "Rule not found";
483  } else {
484  return rule->second;
485  }
486  }
487 
488  std::vector<std::string> getRules(const std::string& relName) override {
489  std::vector<std::string> relRules;
490  // go through all rules
491  for (auto& rule : rules) {
492  if (rule.first.first == relName) {
493  relRules.push_back(rule.second);
494  }
495  }
496 
497  return relRules;
498  }
499 
500  std::string measureRelation(std::string relName) override {
501  auto rel = prog.getRelation(relName);
502 
503  if (rel == nullptr) {
504  return "No relation found\n";
505  }
506 
507  auto size = rel->size();
508  int skip = size / 10;
509 
510  if (skip == 0) {
511  skip = 1;
512  }
513 
514  std::stringstream ss;
515 
516  auto before_time = std::chrono::high_resolution_clock::now();
517 
518  int numTuples = 0;
519  int proc = 0;
520  for (auto& tuple : *rel) {
521  auto tupleStart = std::chrono::high_resolution_clock::now();
522 
523  if (numTuples % skip != 0) {
524  numTuples++;
525  continue;
526  }
527 
528  std::vector<RamDomain> currentTuple;
529  for (arity_type i = 0; i < rel->getPrimaryArity(); i++) {
530  RamDomain n;
531  if (*rel->getAttrType(i) == 's') {
532  std::string s;
533  tuple >> s;
534  n = symTable.lookupExisting(s);
535  } else if (*rel->getAttrType(i) == 'f') {
536  RamFloat element;
537  tuple >> element;
538  n = ramBitCast(element);
539  } else if (*rel->getAttrType(i) == 'u') {
540  RamUnsigned element;
541  tuple >> element;
542  n = ramBitCast(element);
543  } else {
544  tuple >> n;
545  }
546 
547  currentTuple.push_back(n);
548  }
549 
550  RamDomain ruleNum;
551  tuple >> ruleNum;
552 
553  RamDomain levelNum;
554  tuple >> levelNum;
555 
556  std::cout << "Tuples expanded: "
557  << explain(relName, currentTuple, ruleNum, levelNum, 10000)->getSize();
558 
559  numTuples++;
560  proc++;
561 
562  auto tupleEnd = std::chrono::high_resolution_clock::now();
563  auto tupleDuration =
564  std::chrono::duration_cast<std::chrono::duration<double>>(tupleEnd - tupleStart);
565 
566  std::cout << ", Time: " << tupleDuration.count() << "\n";
567  }
568 
569  auto after_time = std::chrono::high_resolution_clock::now();
570  auto duration = std::chrono::duration_cast<std::chrono::duration<double>>(after_time - before_time);
571 
572  ss << "total: " << proc << " ";
573  ss << duration.count() << std::endl;
574 
575  return ss.str();
576  }
577 
578  void printRulesJSON(std::ostream& os) override {
579  os << "\"rules\": [\n";
580  bool first = true;
581  for (auto const& cur : rules) {
582  if (first) {
583  first = false;
584  } else {
585  os << ",\n";
586  }
587  os << "\t{ \"rule-number\": \"(R" << cur.first.second << ")\", \"rule\": \""
588  << stringify(cur.second) << "\"}";
589  }
590  os << "\n]\n";
591  }
592 
593  void queryProcess(const std::vector<std::pair<std::string, std::vector<std::string>>>& rels) override {
594  std::regex varRegex("[a-zA-Z_][a-zA-Z_0-9]*", std::regex_constants::extended);
595  std::regex symbolRegex("\"([^\"]*)\"", std::regex_constants::extended);
596  std::regex numberRegex("[0-9]+", std::regex_constants::extended);
597 
598  std::smatch argsMatcher;
599 
600  // map for variable name and corresponding equivalence class
601  std::map<std::string, Equivalence> nameToEquivalence;
602 
603  // const constraints that solution must satisfy
604  ConstConstraint constConstraints;
605 
606  // relations of tuples containing variables
607  std::vector<Relation*> varRels;
608 
609  // counter for adding element to varRels
610  size_t idx = 0;
611 
612  // parse arguments in each relation Tuple
613  for (const auto& rel : rels) {
614  Relation* relation = prog.getRelation(rel.first);
615  // number/symbol index for constant arguments in tuple
616  std::vector<RamDomain> constTuple;
617  // relation does not exist
618  if (relation == nullptr) {
619  std::cout << "Relation <" << rel.first << "> does not exist" << std::endl;
620  return;
621  }
622  // arity error
623  if (relation->getPrimaryArity() != rel.second.size()) {
624  std::cout << "<" + rel.first << "> has arity of " << relation->getPrimaryArity() << std::endl;
625  return;
626  }
627 
628  // check if args contain variable
629  bool containVar = false;
630  for (size_t j = 0; j < rel.second.size(); ++j) {
631  // arg is a variable
632  if (std::regex_match(rel.second[j], argsMatcher, varRegex)) {
633  containVar = true;
634  auto nameToEquivalenceIter = nameToEquivalence.find(argsMatcher[0]);
635  // if variable has not shown up before, create an equivalence class for add it to
636  // nameToEquivalence map, otherwise add its indices to corresponding equivalence class
637  if (nameToEquivalenceIter == nameToEquivalence.end()) {
638  nameToEquivalence.insert(
639  {argsMatcher[0], Equivalence(*(relation->getAttrType(j)), argsMatcher[0],
640  std::make_pair(idx, j))});
641  } else {
642  nameToEquivalenceIter->second.push_back(std::make_pair(idx, j));
643  }
644  continue;
645  }
646 
647  RamDomain rd;
648  switch (*(relation->getAttrType(j))) {
649  case 's':
650  if (!std::regex_match(rel.second[j], argsMatcher, symbolRegex)) {
651  std::cout << argsMatcher.str(0) << " does not match type defined in relation"
652  << std::endl;
653  return;
654  }
655  rd = prog.getSymbolTable().lookup(argsMatcher[1]);
656  break;
657  case 'f':
658  if (!canBeParsedAsRamFloat(rel.second[j])) {
659  std::cout << rel.second[j] << " does not match type defined in relation"
660  << std::endl;
661  return;
662  }
663  rd = ramBitCast(RamFloatFromString(rel.second[j]));
664  break;
665  case 'i':
666  if (!canBeParsedAsRamSigned(rel.second[j])) {
667  std::cout << rel.second[j] << " does not match type defined in relation"
668  << std::endl;
669  return;
670  }
671  rd = ramBitCast(RamSignedFromString(rel.second[j]));
672  break;
673  case 'u':
674  if (!canBeParsedAsRamUnsigned(rel.second[j])) {
675  std::cout << rel.second[j] << " does not match type defined in relation"
676  << std::endl;
677  return;
678  }
679  rd = ramBitCast(RamUnsignedFromString(rel.second[j]));
680  break;
681  default: continue;
682  }
683 
684  constConstraints.push_back(std::make_pair(std::make_pair(idx, j), rd));
685  if (!containVar) {
686  constTuple.push_back(rd);
687  }
688  }
689 
690  // if tuple does not contain any variable, check if existence of the tuple
691  if (!containVar) {
692  bool tupleExist = containsTuple(relation, constTuple);
693 
694  // if relation contains this tuple, remove all related constraints
695  if (tupleExist) {
696  constConstraints.getConstraints().erase(constConstraints.getConstraints().end() -
697  relation->getArity() +
698  relation->getAuxiliaryArity(),
699  constConstraints.getConstraints().end());
700  // otherwise, there is no solution for given query
701  } else {
702  std::cout << "false." << std::endl;
703  std::cout << "Tuple " << rel.first << "(";
704  for (size_t l = 0; l < rel.second.size() - 1; ++l) {
705  std::cout << rel.second[l] << ", ";
706  }
707  std::cout << rel.second.back() << ") does not exist" << std::endl;
708  return;
709  }
710  } else {
711  varRels.push_back(relation);
712  ++idx;
713  }
714  }
715 
716  // if varRels size is 0, all given tuples only contain constant args and exist, no variable to
717  // resolve, Output true and return
718  if (varRels.size() == 0) {
719  std::cout << "true." << std::endl;
720  return;
721  }
722 
723  // find solution for parameterised query
724  findQuerySolution(varRels, nameToEquivalence, constConstraints);
725  }
726 
727 private:
728  std::map<std::pair<std::string, size_t>, std::vector<std::string>> info;
729  std::map<std::pair<std::string, size_t>, std::string> rules;
730  std::vector<std::vector<RamDomain>> subproofs;
731  std::vector<std::string> constraintList = {
732  "=", "!=", "<", "<=", ">=", ">", "match", "contains", "not_match", "not_contains"};
733 
734  std::tuple<int, int> findTuple(const std::string& relName, std::vector<RamDomain> tup) {
735  auto rel = prog.getRelation(relName);
736 
737  if (rel == nullptr) {
738  return std::make_tuple(-1, -1);
739  }
740 
741  // find correct tuple
742  for (auto& tuple : *rel) {
743  bool match = true;
744  std::vector<RamDomain> currentTuple;
745 
746  for (arity_type i = 0; i < rel->getPrimaryArity(); i++) {
747  RamDomain n;
748  if (*rel->getAttrType(i) == 's') {
749  std::string s;
750  tuple >> s;
751  n = symTable.lookupExisting(s);
752  } else if (*rel->getAttrType(i) == 'f') {
753  RamFloat element;
754  tuple >> element;
755  n = ramBitCast(element);
756  } else if (*rel->getAttrType(i) == 'u') {
757  RamUnsigned element;
758  tuple >> element;
759  n = ramBitCast(element);
760  } else {
761  tuple >> n;
762  }
763 
764  currentTuple.push_back(n);
765 
766  if (n != tup[i]) {
767  match = false;
768  break;
769  }
770  }
771 
772  if (match) {
773  RamDomain ruleNum;
774  tuple >> ruleNum;
775 
776  RamDomain levelNum;
777  tuple >> levelNum;
778 
779  return std::make_tuple(ruleNum, levelNum);
780  }
781  }
782 
783  // if no tuple exists
784  return std::make_tuple(-1, -1);
785  }
786 
787  /*
788  * Find solution for parameterised query satisfying constant constraints and equivalence constraints
789  * @param varRels, reference to vector of relation of tuple contains at least one variable in its
790  * arguments
791  * @param nameToEquivalence, reference to variable name and corresponding equivalence class
792  * @param constConstraints, reference to const constraints must be satisfied
793  * */
794  void findQuerySolution(const std::vector<Relation*>& varRels,
795  const std::map<std::string, Equivalence>& nameToEquivalence,
796  const ConstConstraint& constConstraints) {
797  // vector of iterators for relations in varRels
798  std::vector<Relation::iterator> varRelationIterators;
799  for (auto relation : varRels) {
800  varRelationIterators.push_back(relation->begin());
801  }
802 
803  size_t solutionCount = 0;
804  std::stringstream solution;
805 
806  // iterate through the vector of iterators to find solution
807  while (true) {
808  bool isSolution = true;
809 
810  // vector contains the tuples the iterators currently points to
811  std::vector<tuple> element;
812  for (auto it : varRelationIterators) {
813  element.push_back(*it);
814  }
815  // check if tuple satisfies variable equivalence
816  for (auto var : nameToEquivalence) {
817  if (!var.second.verify(element)) {
818  isSolution = false;
819  break;
820  }
821  }
822  if (isSolution) {
823  // check if tuple satisfies constant constraints
824  isSolution = constConstraints.verify(element);
825  }
826 
827  if (isSolution) {
828  std::cout << solution.str(); // print previous solution (if any)
829  solution.str(std::string()); // reset solution and process
830 
831  size_t c = 0;
832  for (auto&& var : nameToEquivalence) {
833  auto idx = var.second.getFirstIdx();
834  auto raw = element[idx.first][idx.second];
835 
836  solution << var.second.getSymbol() << " = ";
837  switch (var.second.getType()) {
838  case 'i': solution << ramBitCast<RamSigned>(raw); break;
839  case 'f': solution << ramBitCast<RamFloat>(raw); break;
840  case 'u': solution << ramBitCast<RamUnsigned>(raw); break;
841  case 's': solution << prog.getSymbolTable().resolve(raw); break;
842  default: fatal("invalid type: `%c`", var.second.getType());
843  }
844 
845  auto sep = ++c < nameToEquivalence.size() ? ", " : " ";
846  solution << sep;
847  }
848 
849  solutionCount++;
850  // query has more than one solution; query whether to find next solution or stop
851  if (1 < solutionCount) {
852  for (std::string input; getline(std::cin, input);) {
853  if (input == ";") break; // print next solution?
854  if (input == ".") return; // break from query?
855 
856  std::cout << "use ; to find next solution, use . to break from current query\n";
857  }
858  }
859  }
860 
861  // increment the iterators
862  size_t i = varRels.size() - 1;
863  bool terminate = true;
864  for (auto it = varRelationIterators.rbegin(); it != varRelationIterators.rend(); ++it) {
865  if ((++(*it)) != varRels[i]->end()) {
866  terminate = false;
867  break;
868  } else {
869  (*it) = varRels[i]->begin();
870  --i;
871  }
872  }
873 
874  if (terminate) {
875  // if there is no solution, output false
876  if (solutionCount == 0) {
877  std::cout << "false." << std::endl;
878  // otherwise print the last solution
879  } else {
880  std::cout << solution.str() << "." << std::endl;
881  }
882  break;
883  }
884  }
885  }
886 
887  // check if constTuple exists in relation
888  bool containsTuple(Relation* relation, const std::vector<RamDomain>& constTuple) {
889  bool tupleExist = false;
890  for (auto it = relation->begin(); it != relation->end(); ++it) {
891  bool eq = true;
892  for (size_t j = 0; j < constTuple.size(); ++j) {
893  if (constTuple[j] != (*it)[j]) {
894  eq = false;
895  break;
896  }
897  }
898  if (eq) {
899  tupleExist = true;
900  break;
901  }
902  }
903  return tupleExist;
904  }
905 };
906 
907 } // end of namespace souffle
ExplainProvenance.h
souffle::RamUnsigned
uint32_t RamUnsigned
Definition: RamTypes.h:58
souffle::RamSignedFromString
RamSigned RamSignedFromString(const std::string &str, std::size_t *position=nullptr, const int base=10)
Converts a string to a RamSigned.
Definition: StringUtil.h:51
BinaryConstraintOps.h
TCB_SPAN_NAMESPACE_NAME::detail::size
constexpr auto size(const C &c) -> decltype(c.size())
Definition: span.h:198
souffle::explain
void explain(SouffleProgram &prog, bool ncurses)
Definition: Explain.h:627
souffle::RamDomain
int32_t RamDomain
Definition: RamTypes.h:56
souffle::isNumber
bool isNumber(const char *str)
Check whether a string is a sequence of digits.
Definition: StringUtil.h:217
souffle::canBeParsedAsRamFloat
bool canBeParsedAsRamFloat(const std::string &string)
Can a string be parsed as RamFloat.
Definition: StringUtil.h:192
SymbolTable.h
souffle::contains
bool contains(const C &container, const typename C::value_type &element)
A utility to check generically whether a given element is contained in a given container.
Definition: ContainerUtil.h:75
relation
Relation & relation
Definition: Reader.h:130
souffle::RamFloat
float RamFloat
Definition: RamTypes.h:60
MiscUtil.h
souffle::ConstConstraint
Constant constraints for values in query command.
Definition: ExplainProvenance.h:112
ExplainTree.h
n
var n
Definition: htmlJsChartistMin.h:15
j
var j
Definition: htmlJsChartistMin.h:15
souffle::Relation
Object-oriented wrapper class for Souffle's templatized relations.
Definition: SouffleInterface.h:49
souffle::now
time_point now()
Definition: MiscUtil.h:89
l
var l
Definition: htmlJsChartistMin.h:15
souffle::stringify
std::string stringify(const std::string &input)
Stringify a string using escapes for escape, newline, tab, double-quotes and semicolons.
Definition: StringUtil.h:334
i
size_t i
Definition: json11.h:663
ContainerUtil.h
souffle::join
detail::joined_sequence< Iter, Printer > join(const Iter &a, const Iter &b, const std::string &sep, const Printer &p)
Creates an object to be forwarded to some output stream for printing sequences of elements interspers...
Definition: StreamUtil.h:175
StringUtil.h
souffle::toBinaryConstraintOp
BinaryConstraintOp toBinaryConstraintOp(const std::string &symbol)
Converts symbolic representation of an operator to the operator.
Definition: BinaryConstraintOps.h:371
souffle::canBeParsedAsRamUnsigned
bool canBeParsedAsRamUnsigned(const std::string &string)
Can a string be parsed as RamUnsigned.
Definition: StringUtil.h:179
souffle::ConstConstraint::getConstraints
std::vector< std::pair< std::pair< size_t, size_t >, RamDomain > > & getConstraints()
Get the constant constraint vector.
Definition: ExplainProvenance.h:133
souffle::BinaryConstraintOp
BinaryConstraintOp
Binary Constraint Operators.
Definition: BinaryConstraintOps.h:41
souffle::Equivalence
Equivalence class for variables in query command.
Definition: ExplainProvenance.h:45
souffle::test::duration
long duration(const time_point &start, const time_point &end)
Definition: btree_multiset_test.cpp:406
souffle::ConstConstraint::push_back
void push_back(std::pair< std::pair< size_t, size_t >, RamDomain > constr)
Add constant constraint at the end of constConstrs vector.
Definition: ExplainProvenance.h:121
souffle::canBeParsedAsRamSigned
bool canBeParsedAsRamSigned(const std::string &string)
Can a string be parsed as RamSigned.
Definition: StringUtil.h:162
RamTypes.h
souffle::splitString
std::vector< std::string > splitString(const std::string &str, char delimiter)
Splits a string given a delimiter.
Definition: StringUtil.h:321
rule
Rule & rule
Definition: Reader.h:85
StreamUtil.h
souffle::fatal
void fatal(const char *format, const Args &... args)
Definition: MiscUtil.h:198
souffle
Definition: AggregateOp.h:25
souffle::ExplainProvenanceImpl::arity_type
Relation::arity_type arity_type
Definition: ExplainProvenanceImpl.h:55
souffle::RamUnsignedFromString
RamUnsigned RamUnsignedFromString(const std::string &str, std::size_t *position=nullptr, const int base=10)
Converts a string to a RamUnsigned.
Definition: StringUtil.h:110
souffle::ramBitCast
To ramBitCast(From source)
In C++20 there will be a new way to cast between types by reinterpreting bits (std::bit_cast),...
Definition: RamTypes.h:87
souffle::RamFloatFromString
RamFloat RamFloatFromString(const std::string &str, std::size_t *position=nullptr)
Converts a string to a RamFloat.
Definition: StringUtil.h:93
souffle::Relation::arity_type
uint32_t arity_type
Definition: SouffleInterface.h:51
rel
void rel(size_t limit, bool showLimit=true)
Definition: Tui.h:1086
souffle::tuple
Defines a tuple for the OO interface such that relations with varying columns can be accessed.
Definition: SouffleInterface.h:443
SouffleInterface.h
souffle::isOrderedBinaryConstraintOp
bool isOrderedBinaryConstraintOp(const BinaryConstraintOp op)
Determines whether arguments of constraint are orderable.
Definition: BinaryConstraintOps.h:389
souffle::profile::ss
class souffle::profile::Tui ss
Definition: Tui.h:336