def process_queue(self): """Data and rule propagation routine. Returns list of events that were not noops """ self.log(None, "Processing queue") history = [] while len(self.queue) > 0: event = self.queue.dequeue() self.log(event.tablename(), "Dequeued %s", event) if compile.is_regular_rule(event.formula): changes = self.delta_rules.modify(event) if len(changes) > 0: history.extend(changes) bindings = self.top_down_evaluation( event.formula.variables(), event.formula.body) self.log(event.formula.tablename(), "new bindings after top-down: %s", utility.iterstr(bindings)) self.process_new_bindings(bindings, event.formula.head, event.insert, event.formula) else: self.propagate(event) history.extend(self.database.modify(event)) self.log(event.tablename(), "History: %s", utility.iterstr(history)) return history
def process_new_bindings(self, bindings, atom, insert, original_rule): """Process new bindings. For each of BINDINGS, apply to ATOM, and enqueue it as an insert if INSERT is True and as a delete otherwise. """ # for each binding, compute generated tuple and group bindings # by the tuple they generated new_atoms = {} for binding in bindings: new_atom = atom.plug(binding) if new_atom not in new_atoms: new_atoms[new_atom] = [] new_atoms[new_atom].append( database.Database.Proof(binding, original_rule)) self.log(atom.table.table, "new tuples generated: %s", utility.iterstr(new_atoms)) # enqueue each distinct generated tuple, recording appropriate bindings for new_atom in new_atoms: # self.log(event.table, "new_tuple %s: %s", new_tuple, # new_tuples[new_tuple]) # Only enqueue if new data. # Putting the check here is necessary to support recursion. self.enqueue( compile.Event(formula=new_atom, proofs=new_atoms[new_atom], insert=insert))
def update_would_cause_errors(self, events): """Return a list of PolicyException. Return a list of PolicyException if we were to apply the events EVENTS to the current policy. """ self.log(None, "update_would_cause_errors %s", utility.iterstr(events)) errors = [] for event in events: if not compile.is_datalog(event.formula): errors.append( exception.PolicyException("Non-formula found: {}".format( str(event.formula)))) else: if event.formula.is_atom(): errors.extend( compile.fact_errors(event.formula, self.theories, self.name)) else: errors.extend( compile.rule_head_has_no_theory( event.formula, permit_head=lambda lit: lit.is_update())) # Should put this back in place, but there are some # exceptions that we don't handle right now. # Would like to mark some tables as only being defined # for certain bound/free arguments and take that into # account when doing error checking. # errors.extend(compile.rule_negation_safety(event.formula)) return errors
def update_would_cause_errors(self, events): """Return a list of PolicyException. Return a list of PolicyException if we were to apply the insert/deletes of policy statements dictated by EVENTS to the current policy. """ self.log(None, "update_would_cause_errors %s", utility.iterstr(events)) errors = [] for event in events: if not compile.is_datalog(event.formula): errors.append( exception.PolicyException("Non-formula found: {}".format( str(event.formula)))) else: if event.formula.is_atom(): errors.extend( compile.fact_errors(event.formula, self.theories, self.name)) else: errors.extend( compile.rule_errors(event.formula, self.theories, self.name)) # Would also check that rules are non-recursive, but that # is currently being handled by Runtime. The current implementation # disallows recursion in all theories. return errors
def update(self, events): """Apply EVENTS. And return the list of EVENTS that actually changed the theory. Each event is the insert or delete of a policy statement. """ changes = [] self.log(None, "Update %s", utility.iterstr(events)) try: for event in events: schema_changes = self.update_rule_schema( event.formula, event.insert) formula = compile.reorder_for_safety(event.formula) if event.insert: if self._insert_actual(formula): changes.append(event) else: self.revert_schema(schema_changes) else: if self._delete_actual(formula): changes.append(event) else: self.revert_schema(schema_changes) except Exception: LOG.exception("runtime caught an exception") raise return changes
def modify(self, event): """Modifies contents of theory to insert/delete FORMULA. Returns True iff the theory changed. """ self.log(None, "Materialized.modify") self.enqueue_any(event) changes = self.process_queue() self.log(event.formula.tablename(), "modify returns %s", utility.iterstr(changes)) return changes
def modify(self, event): """Insert/delete the compile.Rule RULE into the theory. Return list of changes (either the empty list or a list including just RULE). """ self.log(None, "DeltaRuleTheory.modify %s", event.formula) self.log(None, "originals: %s", utility.iterstr(self.originals)) if event.insert: if self.insert(event.formula): return [event] else: if self.delete(event.formula): return [event] return []
def insert(self, rule): """Insert a compile.Rule into the theory. Return True iff the theory changed. """ assert compile.is_regular_rule(rule), ( "DeltaRuleTheory only takes rules") self.log(rule.tablename(), "Insert: %s", rule) if rule in self.originals: self.log(None, utility.iterstr(self.originals)) return False self.log(rule.tablename(), "Insert 2: %s", rule) for delta in self.compute_delta_rules([rule]): self.insert_delta(delta) self.originals.add(rule) return True
def update_would_cause_errors(self, events): """Return a list of Policyxception. Return a list of PolicyException if we were to apply the events EVENTS to the current policy. """ self.log(None, "update_would_cause_errors %s", utility.iterstr(events)) errors = [] for event in events: if not compile.is_atom(event.formula): errors.append(exception.PolicyException( "Non-atomic formula is not permitted: {}".format( str(event.formula)))) else: errors.extend(compile.fact_errors( event.formula, self.theories, self.name)) return errors
def update_would_cause_errors(self, events): """Return a list of PolicyException. Return a list of PolicyException if we were to apply the events EVENTS to the current policy. """ self.log(None, "update_would_cause_errors %s", utility.iterstr(events)) errors = [] # compute new rule set for event in events: assert compile.is_datalog(event.formula), ( "update_would_cause_errors operates only on objects") self.log(None, "Updating %s", event.formula) if event.formula.is_atom(): errors.extend( compile.fact_errors(event.formula, self.theories, self.name)) else: errors.extend( compile.rule_errors(event.formula, self.theories, self.name)) return errors
def __str__(self): return "TopDownResult(binding={}, support={})".format( unify.binding_str(self.binding), utility.iterstr(self.support))
def __str__(self): return ("TopDownCaller<variables={}, binding={}, find_all={}, " "results={}, save={}, support={}>".format( utility.iterstr(self.variables), str(self.binding), str(self.find_all), utility.iterstr(self.results), repr(self.save), utility.iterstr(self.support)))