Пример #1
0
 def __init__(self, name=None, abbr=None, schema=None, theories=None):
     super(NonrecursiveRuleTheory, self).__init__(name=name,
                                                  abbr=abbr,
                                                  theories=theories,
                                                  schema=schema)
     # dictionary from table name to list of rules with that table in head
     self.rules = RuleSet()
     self.kind = NONRECURSIVE_POLICY_TYPE
Пример #2
0
 def __init__(self, name=None, abbr=None, theories=None):
     super(DeltaRuleTheory, self).__init__(name=name,
                                           abbr=abbr,
                                           theories=theories)
     # dictionary from table name to list of rules with that table as
     # trigger
     self.rules = RuleSet()
     # dictionary from delta_rule to the rule from which it was derived
     self.originals = set()
     # dictionary from table name to number of rules with that table in
     # head
     self.views = {}
     # all tables
     self.all_tables = {}
     self.kind = DELTA_POLICY_TYPE
Пример #3
0
class DeltaRuleTheory(Theory):
    """A collection of DeltaRules.  Not useful by itself as a policy."""
    def __init__(self, name=None, abbr=None, theories=None):
        super(DeltaRuleTheory, self).__init__(name=name,
                                              abbr=abbr,
                                              theories=theories)
        # dictionary from table name to list of rules with that table as
        # trigger
        self.rules = RuleSet()
        # dictionary from delta_rule to the rule from which it was derived
        self.originals = set()
        # dictionary from table name to number of rules with that table in
        # head
        self.views = {}
        # all tables
        self.all_tables = {}
        self.kind = DELTA_POLICY_TYPE

    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", 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, 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 insert_delta(self, delta):
        """Insert a delta rule."""
        self.log(None, "Inserting delta rule %s", delta)
        # views (tables occurring in head)
        if delta.head.table in self.views:
            self.views[delta.head.table] += 1
        else:
            self.views[delta.head.table] = 1

        # tables
        for table in delta.tablenames():
            if table in self.all_tables:
                self.all_tables[table] += 1
            else:
                self.all_tables[table] = 1

        # contents
        # TODO(thinrichs): eliminate dups, maybe including
        #     case where bodies are reorderings of each other
        self.rules.add_rule(delta.trigger.table, delta)

    def delete(self, rule):
        """Delete a compile.Rule from theory.

        Assumes that COMPUTE_DELTA_RULES is deterministic.
        Returns True iff the theory changed.
        """
        self.log(rule.tablename(), "Delete: %s", rule)
        if rule not in self.originals:
            return False
        for delta in self.compute_delta_rules([rule]):
            self.delete_delta(delta)
        self.originals.remove(rule)
        return True

    def delete_delta(self, delta):
        """Delete the DeltaRule DELTA from the theory."""
        # views
        if delta.head.table in self.views:
            self.views[delta.head.table] -= 1
            if self.views[delta.head.table] == 0:
                del self.views[delta.head.table]

        # tables
        for table in delta.tablenames():
            if table in self.all_tables:
                self.all_tables[table] -= 1
                if self.all_tables[table] == 0:
                    del self.all_tables[table]

        # contents
        self.rules.discard_rule(delta.trigger.table, delta)

    def policy(self):
        return self.originals

    def get_arity_self(self, tablename):
        for p in self.originals:
            if p.head.table == tablename:
                return len(p.head.arguments)
        return None

    def __contains__(self, formula):
        return formula in self.originals

    def __str__(self):
        return str(self.rules)

    def rules_with_trigger(self, table):
        """Return the list of DeltaRules that trigger on the given TABLE."""
        if table in self.rules:
            return self.rules.get_rules(table)
        else:
            return []

    def is_view(self, x):
        return x in self.views

    def is_known(self, x):
        return x in self.all_tables

    def base_tables(self):
        base = []
        for table in self.all_tables:
            if table not in self.views:
                base.append(table)
        return base

    @classmethod
    def eliminate_self_joins(cls, formulas):
        """Remove self joins.

        Return new list of formulas that is equivalent to
        the list of formulas FORMULAS except that there
        are no self-joins.
        """
        def new_table_name(name, arity, index):
            return "___{}_{}_{}".format(name, arity, index)

        def n_variables(n):
            vars = []
            for i in xrange(0, n):
                vars.append("x" + str(i))
            return vars

        # dict from (table name, arity) tuple to
        #      max num of occurrences of self-joins in any rule
        global_self_joins = {}
        # remove self-joins from rules
        results = []
        for rule in formulas:
            if rule.is_atom():
                results.append(rule)
                continue
            LOG.debug("eliminating self joins from %s", rule)
            occurrences = {}  # for just this rule
            for atom in rule.body:
                table = atom.tablename()
                arity = len(atom.arguments)
                tablearity = (table, arity)
                if tablearity not in occurrences:
                    occurrences[tablearity] = 1
                else:
                    # change name of atom
                    atom.table = new_table_name(table, arity,
                                                occurrences[tablearity])
                    # update our counters
                    occurrences[tablearity] += 1
                    if tablearity not in global_self_joins:
                        global_self_joins[tablearity] = 1
                    else:
                        global_self_joins[tablearity] = (max(
                            occurrences[tablearity] - 1,
                            global_self_joins[tablearity]))
            results.append(rule)
            LOG.debug("final rule: %s", rule)
        # add definitions for new tables
        for tablearity in global_self_joins:
            table = tablearity[0]
            arity = tablearity[1]
            for i in xrange(1, global_self_joins[tablearity] + 1):
                newtable = new_table_name(table, arity, i)
                args = [compile.Variable(var) for var in n_variables(arity)]
                head = compile.Literal(newtable, args)
                body = [compile.Literal(table, args)]
                results.append(compile.Rule(head, body))
                LOG.debug("Adding rule %s", results[-1])
        return results

    @classmethod
    def compute_delta_rules(cls, formulas):
        """Return list of DeltaRules computed from formulas.

        Assuming FORMULAS has no self-joins, return a list of DeltaRules
        derived from those FORMULAS.
        """
        # Should do the following for correctness, but it needs to be
        #    done elsewhere so that we can properly maintain the tables
        #    that are generated.
        # formulas = cls.eliminate_self_joins(formulas)
        delta_rules = []
        for rule in formulas:
            if rule.is_atom():
                continue
            rule = compile.reorder_for_safety(rule)
            for literal in rule.body:
                if builtin_registry.is_builtin(literal.table,
                                               len(literal.arguments)):
                    continue
                newbody = [lit for lit in rule.body if lit is not literal]
                delta_rules.append(DeltaRule(literal, rule.head, newbody,
                                             rule))
        return delta_rules
Пример #4
0
class NonrecursiveRuleTheory(TopDownTheory):
    """A non-recursive collection of Rules."""
    def __init__(self, name=None, abbr=None, schema=None, theories=None):
        super(NonrecursiveRuleTheory, self).__init__(name=name,
                                                     abbr=abbr,
                                                     theories=theories,
                                                     schema=schema)
        # dictionary from table name to list of rules with that table in head
        self.rules = RuleSet()
        self.kind = NONRECURSIVE_POLICY_TYPE

    # External Interface

    # SELECT implemented by TopDownTheory

    def insert(self, rule):
        changes = self.update([Event(formula=rule, insert=True)])
        return [event.formula for event in changes]

    def delete(self, rule):
        changes = self.update([Event(formula=rule, insert=False)])
        return [event.formula for event in changes]

    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", iterstr(events))
        try:
            for event in events:
                formula = compile.reorder_for_safety(event.formula)
                if event.insert:
                    if self.insert_actual(formula):
                        changes.append(event)
                else:
                    if self.delete_actual(formula):
                        changes.append(event)
        except Exception as e:
            LOG.exception("runtime caught an exception")
            raise e

        return changes

    def update_would_cause_errors(self, events):
        """Return a list of compile.CongressException.

        Return a list of compile.CongressException 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", iterstr(events))
        errors = []
        for event in events:
            if not compile.is_datalog(event.formula):
                errors.append(
                    compile.CongressException("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 define(self, rules):
        """Empties and then inserts RULES."""
        self.empty()
        return self.update(
            [Event(formula=rule, insert=True) for rule in rules])

    def empty(self):
        """Deletes contents of theory."""
        self.rules.clear()

    def policy(self):
        # eliminate all rules with empty bodies
        return [p for p in self.content() if len(p.body) > 0]

    def get_arity_self(self, tablename, theory):
        if tablename not in self.rules:
            return None
        rules = self.rules.get_rules(tablename)
        if len(rules) == 0:
            return None
        try:
            rule = next(rule for rule in rules if rule.head.theory == theory)
        except StopIteration:
            return None
        return len(rule.head.arguments)

    def __contains__(self, formula):
        return formula in self.rules

    # Internal Interface

    def insert_actual(self, rule):
        """Insert RULE and return True if there was a change."""
        if compile.is_atom(rule):
            rule = compile.Rule(rule, [], rule.location)
        self.log(rule.head.table, "Insert: %s", rule)
        return self.rules.add_rule(rule.head.table, rule)

    def delete_actual(self, rule):
        """Delete RULE and return True if there was a change."""
        if compile.is_atom(rule):
            rule = compile.Rule(rule, [], rule.location)
        self.log(rule.head.table, "Delete: %s", rule)
        return self.rules.discard_rule(rule.head.table, rule)

    def content(self, tablenames=None):
        if tablenames is None:
            tablenames = self.rules.keys()
        results = []
        for table in tablenames:
            if table in self.rules:
                results.extend(self.rules.get_rules(table))
        return results

    def head_index(self, table, match_literal=None):
        """Return head index.

        This routine must return all the formulas pertinent for
        top-down evaluation when a literal with TABLE is at the top
        of the stack.
        """
        if table in self.rules:
            return self.rules.get_rules(table, match_literal)
        return []

    def arity(self, tablename):
        """Return the number of arguments TABLENAME takes.

        None if unknown because TABLENAME is not defined here.
        """
        # assuming a fixed arity for all tables
        formulas = self.head_index(tablename)
        if len(formulas) == 0:
            return None
        first = formulas[0]
        # should probably have an overridable function for computing
        #   the arguments of a head.  Instead we assume heads have .arguments
        return len(self.head(first).arguments)

    def defined_tablenames(self):
        """Returns list of table names defined in/written to this theory."""
        return self.rules.keys()

    def head(self, formula):
        """Given the output from head_index(), return the formula head.

        Given a FORMULA, return the thing to unify against.
        Usually, FORMULA is a compile.Rule, but it could be anything
        returned by HEAD_INDEX.
        """
        return formula.head

    def body(self, formula):
        """Return formula body.

        Given a FORMULA, return a list of things to push onto the
        top-down eval stack.
        """
        return formula.body
Пример #5
0
 def setUp(self):
     super(TestRuleSet, self).setUp()
     self.ruleset = RuleSet()
Пример #6
0
class TestRuleSet(base.TestCase):
    def setUp(self):
        super(TestRuleSet, self).setUp()
        self.ruleset = RuleSet()

    def test_empty_ruleset(self):
        self.assertFalse('p' in self.ruleset)
        self.assertEqual([], self.ruleset.keys())

    def test_clear_ruleset(self):
        rule1 = compile.parse1('p(x,y) :- q(x), r(y)')
        self.ruleset.add_rule('p', rule1)
        self.ruleset.clear()

        self.assertFalse('p' in self.ruleset)
        self.assertEqual([], self.ruleset.keys())

    def test_add_rule(self):
        rule1 = compile.parse1('p(x,y) :- q(x), r(y)')
        self.assertTrue(self.ruleset.add_rule('p', rule1))
        self.assertTrue('p' in self.ruleset)
        self.assertEqual([rule1], self.ruleset.get_rules('p'))
        self.assertEqual(['p'], self.ruleset.keys())

    def test_add_existing_rule(self):
        rule1 = compile.parse1('p(x,y) :- q(x), r(y)')
        self.assertTrue(self.ruleset.add_rule('p', rule1))
        self.assertTrue('p' in self.ruleset)
        self.assertEqual([rule1], self.ruleset.get_rules('p'))
        self.assertEqual(['p'], self.ruleset.keys())

        self.assertFalse(self.ruleset.add_rule('p', rule1))
        self.assertTrue('p' in self.ruleset)
        self.assertEqual([rule1], self.ruleset.get_rules('p'))
        self.assertEqual(['p'], self.ruleset.keys())

    def test_add_rules_with_same_head(self):
        rule1 = compile.parse1('p(x,y) :- q(x), r(y)')
        rule2 = compile.parse1('p(x,y) :- s(x), t(y)')

        self.assertTrue(self.ruleset.add_rule('p', rule1))
        self.assertTrue('p' in self.ruleset)
        self.assertEqual([rule1], self.ruleset.get_rules('p'))
        self.assertEqual(['p'], self.ruleset.keys())

        self.assertTrue(self.ruleset.add_rule('p', rule2))
        self.assertTrue('p' in self.ruleset)
        self.assertTrue(rule1 in self.ruleset.get_rules('p'))
        self.assertTrue(rule2 in self.ruleset.get_rules('p'))
        self.assertEqual(['p'], self.ruleset.keys())

    def test_add_rules_with_different_head(self):
        rule1 = compile.parse1('p1(x,y) :- q(x), r(y)')
        rule2 = compile.parse1('p2(x,y) :- s(x), t(y)')

        self.assertTrue(self.ruleset.add_rule('p1', rule1))
        self.assertTrue(self.ruleset.add_rule('p2', rule2))

        self.assertTrue('p1' in self.ruleset)
        self.assertEqual([rule1], self.ruleset.get_rules('p1'))
        self.assertTrue('p1' in self.ruleset.keys())

        self.assertTrue('p2' in self.ruleset)
        self.assertEqual([rule2], self.ruleset.get_rules('p2'))
        self.assertTrue('p2' in self.ruleset.keys())

    def test_discard_rule(self):
        rule1 = compile.parse1('p(x,y) :- q(x), r(y)')
        self.assertTrue(self.ruleset.add_rule('p', rule1))
        self.assertTrue('p' in self.ruleset)
        self.assertEqual([rule1], self.ruleset.get_rules('p'))

        self.assertTrue(self.ruleset.discard_rule('p', rule1))
        self.assertFalse('p' in self.ruleset)
        self.assertEqual([], self.ruleset.keys())

    def test_discard_nonexistent_rule(self):
        rule1 = compile.parse1('p(x,y) :- q(x), r(y)')
        self.assertFalse(self.ruleset.discard_rule('p', rule1))
        self.assertFalse('p' in self.ruleset)
        self.assertEqual([], self.ruleset.keys())

    def test_discard_rules_with_same_head(self):
        rule1 = compile.parse1('p(x,y) :- q(x), r(y)')
        rule2 = compile.parse1('p(x,y) :- s(x), t(y)')
        self.assertTrue(self.ruleset.add_rule('p', rule1))
        self.assertTrue(self.ruleset.add_rule('p', rule2))
        self.assertTrue('p' in self.ruleset)
        self.assertTrue(rule1 in self.ruleset.get_rules('p'))
        self.assertTrue(rule2 in self.ruleset.get_rules('p'))

        self.assertTrue(self.ruleset.discard_rule('p', rule1))
        self.assertTrue(self.ruleset.discard_rule('p', rule2))
        self.assertFalse('p' in self.ruleset)
        self.assertEqual([], self.ruleset.keys())

    def test_discard_rules_with_different_head(self):
        rule1 = compile.parse1('p1(x,y) :- q(x), r(y)')
        rule2 = compile.parse1('p2(x,y) :- s(x), t(y)')
        self.assertTrue(self.ruleset.add_rule('p1', rule1))
        self.assertTrue(self.ruleset.add_rule('p2', rule2))
        self.assertTrue('p1' in self.ruleset)
        self.assertTrue('p2' in self.ruleset)
        self.assertTrue(rule1 in self.ruleset.get_rules('p1'))
        self.assertTrue(rule2 in self.ruleset.get_rules('p2'))

        self.assertTrue(self.ruleset.discard_rule('p1', rule1))
        self.assertTrue(self.ruleset.discard_rule('p2', rule2))
        self.assertFalse('p1' in self.ruleset)
        self.assertFalse('p2' in self.ruleset)
        self.assertEqual([], self.ruleset.keys())
Пример #7
0
class NonrecursiveRuleTheory(TopDownTheory):
    """A non-recursive collection of Rules."""
    def __init__(self, name=None, abbr=None, schema=None, theories=None):
        super(NonrecursiveRuleTheory, self).__init__(name=name,
                                                     abbr=abbr,
                                                     theories=theories,
                                                     schema=schema)
        # dictionary from table name to list of rules with that table in head
        self.rules = RuleSet()
        self.kind = NONRECURSIVE_POLICY_TYPE

    # External Interface

    # SELECT implemented by TopDownTheory

    def insert(self, rule):
        changes = self.update([Event(formula=rule, insert=True)])
        return [event.formula for event in changes]

    def delete(self, rule):
        changes = self.update([Event(formula=rule, insert=False)])
        return [event.formula for event in changes]

    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", iterstr(events))
        try:
            for event in events:
                formula = compile.reorder_for_safety(event.formula)
                if event.insert:
                    if self.insert_actual(formula):
                        changes.append(event)
                else:
                    if self.delete_actual(formula):
                        changes.append(event)
        except Exception as e:
            LOG.exception("runtime caught an exception")
            raise e

        return changes

    def update_would_cause_errors(self, events):
        """Return a list of compile.CongressException.

        Return a list of compile.CongressException 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", iterstr(events))
        errors = []
        for event in events:
            if not compile.is_datalog(event.formula):
                errors.append(
                    compile.CongressException("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 define(self, rules):
        """Empties and then inserts RULES."""
        self.empty()
        return self.update(
            [Event(formula=rule, insert=True) for rule in rules])

    def empty(self):
        """Deletes contents of theory."""
        self.rules.clear()

    def policy(self):
        # eliminate all rules with empty bodies
        return [p for p in self.content() if len(p.body) > 0]

    def get_arity_self(self, tablename):
        if tablename not in self.rules:
            return None
        if len(self.rules.get_rules(tablename)) == 0:
            return None
        return len(list(self.rules.get_rules(tablename))[0].head.arguments)

    def __contains__(self, formula):
        return formula in self.rules

    # Internal Interface

    def insert_actual(self, rule):
        """Insert RULE and return True if there was a change."""
        if compile.is_atom(rule):
            rule = compile.Rule(rule, [], rule.location)
        self.log(rule.head.table, "Insert: %s", rule)
        return self.rules.add_rule(rule.head.table, rule)

    def delete_actual(self, rule):
        """Delete RULE and return True if there was a change."""
        if compile.is_atom(rule):
            rule = compile.Rule(rule, [], rule.location)
        self.log(rule.head.table, "Delete: %s", rule)
        return self.rules.discard_rule(rule.head.table, rule)

    def content(self, tablenames=None):
        if tablenames is None:
            tablenames = self.rules.keys()
        results = []
        for table in tablenames:
            if table in self.rules:
                results.extend(self.rules.get_rules(table))
        return results