Fixed bug where ordering of rules and data mattered

When rule inserted after data and that rule had self-join,
failed to get right result.

This fix first computes self-joins and then ensures to evaluate
all the resulting rules before computing delta rules and inserting them.

Added test to catch this case in the future.

All tests pass

Issue: #
Change-Id: Ie09dbc82c42069e0f4925d2919ba40e88b2d1a92
This commit is contained in:
Tim Hinrichs 2013-10-22 11:32:24 -07:00
parent fb2e900138
commit c3066ebf44
3 changed files with 36 additions and 12 deletions

View File

@ -29,7 +29,7 @@ same-group(user1, user2) :- cms:group(user1, group), cms:group(user2, group)
--- Commands ------------------------------------
cd congress/src/policy
python
PYTHONPATH=../../thirdparty python
>>> import runtime
>>> r = runtime.Runtime()
>>> r.load_file("../../examples/private_public_network.classify")

View File

@ -851,7 +851,7 @@ class DeltaRuleTheory (object):
self.views = {}
def insert(self, rule):
""" Insert a compile.Rule into the theory.
""" Insert a self-join free compile.Rule into the theory.
Return True iff the theory changed. """
assert isinstance(rule, compile.Rule), \
"DeltaRuleTheory only takes rules"
@ -928,8 +928,10 @@ class DeltaRuleTheory (object):
# dict from (table name, arity) to # of args for
arities = {}
# remove self-joins from rules
results = []
for rule in theory:
if rule.is_atom():
results.append(rule)
continue
logging.debug("eliminating self joins from {}".format(rule))
occurrences = {} # for just this rule
@ -951,6 +953,7 @@ class DeltaRuleTheory (object):
global_self_joins[tablearity] = \
max(occurrences[tablearity] - 1,
global_self_joins[tablearity])
results.append(rule)
logging.debug("final rule: {}".format(str(rule)))
# add definitions for new tables
for tablearity in global_self_joins:
@ -961,12 +964,14 @@ class DeltaRuleTheory (object):
args = [compile.Variable(var) for var in n_variables(arity)]
head = compile.Atom(newtable, args)
body = [compile.Atom(table, args)]
theory.append(compile.Rule(head, body))
logging.debug("Adding rule {}".format(str(theory[-1])))
return theory
results.append(compile.Rule(head, body))
logging.debug("Adding rule {}".format(results[-1]))
return results
@classmethod
def compute_delta_rules(cls, theory):
""" Assuming THEORY has no self-joins, return a list of DeltaRules
derived from that THEORY. """
theory = cls.eliminate_self_joins(theory)
delta_rules = []
for rule in theory:
@ -1068,13 +1073,23 @@ class MaterializedRuleTheory(TopDownTheory):
self.log(formula.table, "{}: {}".format(text, str(formula)))
return self.modify_tables_with_atom(formula, is_insert=is_insert)
else:
self.modify_tables_with_rule(
formula, is_insert=is_insert)
self.log(formula.head.table, "{}: {}".format(text, str(formula)))
if is_insert:
return self.delta_rules.insert(formula)
else:
return self.delta_rules.delete(formula)
# need to eliminate self-joins here so that we
# call modify_tables_with_rule on all the actual rules.
# Yes, the call to eliminate_self_joins is duplicated within
# delta_rules.insert, but it is nice and safe this way.
real_rules = DeltaRuleTheory.eliminate_self_joins([formula])
changed = False
for rule in real_rules:
self.modify_tables_with_rule(
rule, is_insert=is_insert)
self.log(formula.head.table, "{}: {}".format(text, str(rule)))
if is_insert:
if self.delta_rules.insert(rule):
changed = True
else:
if self.delta_rules.delete(rule):
changed = True
return changed
def modify_tables_with_rule(self, rule, is_insert):
""" Add rule (not a DeltaRule) to collection and update

View File

@ -285,6 +285,15 @@ class TestRuntime(unittest.TestCase):
'q(2,6) q(2,7)')
self.check(run, code, "Delete: larger self join")
# actual bug: insert data first, then
# insert rule with self-join
code = ('s(1)'
'q(1,1)'
'p(x,y) :- q(x,y), not r(x,y)'
'r(x,y) :- s(x), s(y)')
run = self.prep_runtime(code)
self.check(run, 's(1) q(1,1) r(1,1)')
def test_materialized_value_types(self):
""" Test the different value types """
# string