Merge "Installed builtin reordering everywhere"

This commit is contained in:
Jenkins 2014-10-23 17:44:17 +00:00 committed by Gerrit Code Review
commit 93b47129f3
5 changed files with 93 additions and 91 deletions

View File

@ -119,7 +119,8 @@ class DatetimeBuiltins(object):
return cls.to_datetime(x) == cls.to_datetime(y)
start_builtin_map = {
# the registry for builtins
_builtin_map = {
'comparison': [
{'func': 'lt(x,y)', 'num_inputs': 2, 'code': lambda x, y: x < y},
{'func': 'lteq(x,y)', 'num_inputs': 2, 'code': lambda x, y: x <= y},
@ -182,6 +183,7 @@ class CongressBuiltinPred(object):
self.predargs = arglist
self.num_inputs = num_inputs
self.code = code
self.num_outputs = len(arglist) - num_inputs
def __str__(self):
predall = str(self.predname) + " " + str(self.predargs)\
@ -195,8 +197,8 @@ class CongressBuiltinPred(object):
except Exception:
print "Unexpected error in parsing predicate string"
def pred_to_string(self):
return self.predname + '(' + str(self.predargs) + ')'
def __str__(self):
return self.predname + '(' + ",".join(self.predargs) + ')'
class CongressBuiltinCategoryMap(object):
@ -235,7 +237,7 @@ class CongressBuiltinCategoryMap(object):
# print 'category exists'
for predtriple in value:
pred = self.dict_predtriple_to_pred(predtriple)
if not self.check_if_builtin(pred):
if not self.builtin_is_registered(pred):
self.categorydict[key].append(pred)
self.sync_with_predlist(pred.predname, pred, key, 'add')
@ -267,7 +269,7 @@ class CongressBuiltinCategoryMap(object):
self.categorydict[category].remove(pred)
self.sync_with_predlist(name, pred, category, 'del')
def get_builtin_category_name(self, predname, predinputs):
def get_category_name(self, predname, predinputs):
if predname in self.preddict:
if self.preddict[predname][0].num_inputs == predinputs:
return self.preddict[predname][1]
@ -309,54 +311,52 @@ class CongressBuiltinCategoryMap(object):
else:
assert("Category does not exist")
def check_if_builtin(self, predtotest):
def builtin_is_registered(self, predtotest):
"""Given a CongressBuiltinPred, check if it has been registered."""
pname = predtotest.predname
if pname in self.preddict:
if self.preddict[pname][0].num_inputs == predtotest.num_inputs:
return True
return False
def check_if_builtin_by_name(self, predname, arity):
def is_builtin(self, predname, arity):
"""Given a name and arity, check if it is a builtin."""
# print "check_if_builtin_by_name {} {}".format(predname, arity)
if predname in self.preddict:
if len(self.preddict[predname][0].predargs) == arity:
return True
return False
def return_builtin_pred(self, predname):
def builtin(self, predname):
"""Return a CongressBuiltinPred with name PREDNAME or None."""
if predname in self.preddict:
return self.preddict[predname][0]
return None
def builtin_num_outputs(self, predname):
if predname in self.preddict:
pred = self.preddict[predname][0]
return len(pred.predargs) - pred.num_inputs
return 0
def list_available_builtins(self):
"""Print out the list of builtins, by category."""
for key, value in self.categorydict.items():
predlist = self.categorydict[key]
for pred in predlist:
print pred
print pred.pred_to_string()
print str(pred)
def eval_builtin(self, code, arglist):
return code(*arglist)
# a Singleton that serves as the entry point for builtin functionality
builtin_registry = CongressBuiltinCategoryMap(_builtin_map)
def main():
cbcmap = CongressBuiltinCategoryMap(start_builtin_map)
cbcmap = CongressBuiltinCategoryMap(_builtin_map)
cbcmap.list_available_builtins()
predl = cbcmap.return_builtin_pred('lt')
predl = cbcmap.builtin('lt')
print predl
print 'printing pred'
predl.string_to_pred('ltc(x,y)')
cbcmap.list_available_builtins()
cbcmap.delete_builtin('arithmetic', 'max', 2)
cbcmap.list_available_builtins()
predl = cbcmap.return_builtin_pred('plus')
result = cbcmap.eval_builtin(predl.code, [1, 2])
predl = cbcmap.builtin('plus')
result = predl.code(1, 2)
print result
print cbcmap

View File

@ -18,10 +18,10 @@
import unittest
from congress.openstack.common import log as logging
from congress.policy.builtin.congressbuiltin \
import CongressBuiltinCategoryMap as builtins
from congress.policy.builtin.congressbuiltin import _builtin_map
from congress.policy.builtin.congressbuiltin import CongressBuiltinCategoryMap
from congress.policy.builtin.congressbuiltin import CongressBuiltinPred
from congress.policy.builtin.congressbuiltin import start_builtin_map
from congress.policy import compile
from congress.policy import runtime
from congress.tests import helper
@ -40,15 +40,12 @@ append_builtin = {'arithmetic': [{'func': 'div(x,y)',
'num_inputs': 2,
'code': 'lambda x,y: x / y'}]}
NREC_THEORY = 'non-recursive theory'
DB_THEORY = 'database'
class TestBuiltins(unittest.TestCase):
def setUp(self):
self.cbcmap = builtins(start_builtin_map)
self.predl = self.cbcmap.return_builtin_pred('lt')
self.cbcmap = CongressBuiltinCategoryMap(_builtin_map)
self.predl = self.cbcmap.builtin('lt')
def test_add_and_delete_map(self):
cbcmap_before = self.cbcmap
@ -58,10 +55,10 @@ class TestBuiltins(unittest.TestCase):
def test_add_map_only(self):
self.cbcmap.add_map(append_builtin)
predl = self.cbcmap.return_builtin_pred('div')
predl = self.cbcmap.builtin('div')
self.assertNotEqual(predl, None)
self.cbcmap.add_map(addmap)
predl = self.cbcmap.return_builtin_pred('max')
predl = self.cbcmap.builtin('max')
self.assertNotEqual(predl, None)
def test_add_and_delete_builtin(self):
@ -71,27 +68,27 @@ class TestBuiltins(unittest.TestCase):
self.assertTrue(self.cbcmap.mapequal(cbcmap_before))
def test_string_pred_string(self):
predstring = self.predl.pred_to_string()
predstring = str(self.predl)
self.assertNotEqual(predstring, 'ltc(x,y')
def test_add_and_delete_to_category(self):
cbcmap_before = self.cbcmap
arglist = ['x', 'y', 'z']
pred = CongressBuiltinPred('testfunc', arglist, 1, 'lambda x: not x')
pred = CongressBuiltinPred('testfunc', arglist, 1, lambda x: not x)
self.cbcmap.insert_to_category('arithmetic', pred)
self.cbcmap.delete_from_category('arithmetic', pred)
self.assertTrue(self.cbcmap.mapequal(cbcmap_before))
def test_all_checks(self):
predtotest = self.cbcmap.return_builtin_pred('lt')
self.assertTrue(self.cbcmap.check_if_builtin(predtotest))
predtotest = self.cbcmap.builtin('lt')
self.assertTrue(self.cbcmap.builtin_is_registered(predtotest))
def test_eval_builtin(self):
predl = self.cbcmap.return_builtin_pred('plus')
result = self.cbcmap.eval_builtin(predl.code, [1, 2])
predl = self.cbcmap.builtin('plus')
result = predl.code(1, 2)
self.assertEqual(result, 3)
predl = self.cbcmap.return_builtin_pred('gt')
result = self.cbcmap.eval_builtin(predl.code, [1, 2])
predl = self.cbcmap.builtin('gt')
result = predl.code(1, 2)
self.assertEqual(result, False)
@ -237,7 +234,12 @@ class TestReorder(unittest.TestCase):
'Unsafety propagates')
class TestNonrecursive(unittest.TestCase):
NREC_THEORY = 'non-recursive theory test'
MAT_THEORY = 'materialized view theory test'
class TestTheories(unittest.TestCase):
def prep_runtime(self, code=None, msg=None, target=None):
# compile source
if msg is not None:
@ -247,8 +249,10 @@ class TestNonrecursive(unittest.TestCase):
if target is None:
target = NREC_THEORY
run = runtime.Runtime()
run.theory[NREC_THEORY] = runtime.NonrecursiveRuleTheory()
run.theory[DB_THEORY] = runtime.Database(name="Database", abbr="DB")
run.theory[NREC_THEORY] = runtime.NonrecursiveRuleTheory(
name="Nonrecursive", abbr="NRT")
run.theory[MAT_THEORY] = runtime.MaterializedViewTheory(
name="Materialized", abbr="MAT")
run.debug_mode()
run.insert(code, target=target)
return run
@ -257,9 +261,11 @@ class TestNonrecursive(unittest.TestCase):
self.assertTrue(helper.datalog_equal(
actual_string, correct_string, msg))
def test_builtins(self):
def test_materialized_builtins(self):
self.test_builtins(MAT_THEORY)
def test_builtins(self, th=NREC_THEORY):
"""Test the mechanism that implements builtins."""
th = NREC_THEORY
run = self.prep_runtime()
run.insert('p(x) :- q(x,y), plus(x,y,z), r(z)'
'q(1,2)'
@ -316,10 +322,28 @@ class TestNonrecursive(unittest.TestCase):
self.check_equal(run.select('p(x)', target=th),
'p(4)', "Bound output")
def test_builtins_content(self):
run = self.prep_runtime()
run.insert('p(x, z) :- plus(x,y,z), q(x), r(y)'
'q(4)'
'r(5)', target=th)
self.check_equal(run.select('p(x, y)', target=th),
'p(4, 9)',
"Reordering")
run = self.prep_runtime()
run.insert('p(x, z) :- plus(x,y,z), q(x), q(y)'
'q(4)'
'q(5)', target=th)
self.check_equal(run.select('p(x, y)', target=th),
'p(4, 9) p(4, 8) p(5, 9) p(5, 10)',
"Reordering with self joins")
def test_materialized_builtins_content(self):
self.test_builtins_content(MAT_THEORY)
def test_builtins_content(self, th=NREC_THEORY):
"""Test the content of the builtins, not the mechanism"""
def check_true(code, msg):
th = NREC_THEORY
run = self.prep_runtime('')
run.insert(code, target=th)
self.check_equal(

View File

@ -24,8 +24,7 @@ import CongressLexer
import CongressParser
import utility
from builtin.congressbuiltin import CongressBuiltinCategoryMap as cbcmap
from builtin.congressbuiltin import start_builtin_map as initbuiltin
from builtin.congressbuiltin import builtin_registry
class CongressException (Exception):
@ -402,6 +401,8 @@ class Literal (object):
return self.table.endswith('+') or self.table.endswith('-')
def tablename(self):
# implemented simply so that we can call tablename() on either
# rules or literals
return self.table
@ -715,7 +716,8 @@ def reorder_for_safety(rule):
is evaluated. Reordering is stable, meaning that if the rule is
properly ordered, no changes are made.
"""
cbcmapinst = cbcmap(initbuiltin)
if not is_rule(rule):
return rule
safe_vars = set()
unsafe_literals = []
unsafe_variables = {} # dictionary from literal to its unsafe vars
@ -741,9 +743,8 @@ def reorder_for_safety(rule):
target_vars = None
if lit.is_negated():
target_vars = lit.variable_names()
elif cbcmapinst.check_if_builtin_by_name(
lit.table, len(lit.arguments)):
builtin = cbcmapinst.return_builtin_pred(lit.table)
elif builtin_registry.is_builtin(lit.table, len(lit.arguments)):
builtin = builtin_registry.builtin(lit.table)
target_vars = lit.arguments[0:builtin.num_inputs]
target_vars = set([x.name for x in target_vars if x.is_variable()])
else:

View File

@ -18,8 +18,7 @@ import cStringIO
import os
from unify import bi_unify_lists
from builtin.congressbuiltin import CongressBuiltinCategoryMap
from builtin.congressbuiltin import start_builtin_map
from builtin.congressbuiltin import builtin_registry
# FIXME there is a circular import here because compile.py imports runtime.py
import compile
@ -261,7 +260,6 @@ class Theory(object):
self.trace_prefix = self.abbr[0:maxlength]
else:
self.trace_prefix = self.abbr + " " * (maxlength - len(self.abbr))
self.cbcmap = CongressBuiltinCategoryMap(start_builtin_map)
def set_tracer(self, tracer):
self.tracer = tracer
@ -599,11 +597,9 @@ class TopDownTheory(Theory):
elif lit.tablename() == 'false':
self.print_fail(lit, context.binding, context.depth)
return False
elif self.cbcmap.check_if_builtin_by_name(lit.tablename(),
len(lit.arguments)):
elif builtin_registry.is_builtin(lit.table, len(lit.arguments)):
self.print_call(lit, context.binding, context.depth)
cbc = self.cbcmap.return_builtin_pred(lit.tablename())
builtin_code = cbc.code
builtin = builtin_registry.builtin(lit.table)
# copy arguments into variables
# PLUGGED is an instance of compile.Literal
plugged = lit.plug(context.binding)
@ -611,17 +607,16 @@ class TopDownTheory(Theory):
# PLUGGED.arguments is a list of compile.Term
# create args for function
args = []
for i in xrange(0, cbc.num_inputs):
for i in xrange(0, builtin.num_inputs):
assert plugged.arguments[i].is_object(), \
("Builtins must be evaluated only after their "
"inputs are ground: {} with num-inputs {}".format(
str(plugged), cbc.num_inputs))
str(plugged), builtin.num_inputs))
args.append(plugged.arguments[i].name)
# evaluate builtin: must return number, string, or iterable
# of numbers/strings
# print "args: " + str(args)
try:
result = self.cbcmap.eval_builtin(builtin_code, args)
result = builtin.code(*args)
except Exception as e:
errmsg = "Error in builtin: " + str(e)
self.print_note(lit, context.binding, context.depth, errmsg)
@ -632,7 +627,7 @@ class TopDownTheory(Theory):
# "Result: " + str(result))
success = None
undo = []
if self.cbcmap.builtin_num_outputs(lit.table) > 0:
if builtin.num_outputs > 0:
# with return values, local success means we can bind
# the results to the return value arguments
if isinstance(result, (int, long, float, basestring)):
@ -643,7 +638,7 @@ class TopDownTheory(Theory):
unifier = self.new_bi_unifier()
undo = bi_unify_lists(result,
unifier,
lit.arguments[cbc.num_inputs:],
lit.arguments[builtin.num_inputs:],
context.binding)
# print "unifier: " + str(undo)
success = undo is not None
@ -1239,11 +1234,12 @@ class NonrecursiveRuleTheory(TopDownTheory):
changes = []
self.log(None, "Update " + iterstr(events))
for event in events:
formula = compile.reorder_for_safety(event.formula)
if event.insert:
if self.insert_actual(event.formula):
if self.insert_actual(formula):
changes.append(event)
else:
if self.delete_actual(event.formula):
if self.delete_actual(formula):
changes.append(event)
return changes
@ -1423,7 +1419,6 @@ class DeltaRuleTheory (Theory):
return False
self.log(rule.tablename(), "Insert 2: {}".format(str(rule)))
for delta in self.compute_delta_rules([rule]):
self.reorder(delta)
self.insert_delta(delta)
self.originals.add(rule)
return True
@ -1588,23 +1583,16 @@ class DeltaRuleTheory (Theory):
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
@classmethod
def reorder(cls, delta):
"""Given a delta rule DELTA, re-order its body for efficient
and correct computation.
"""
# ensure negatives come after positives
positives = [lit for lit in delta.body if not lit.is_negated()]
negatives = [lit for lit in delta.body if lit.is_negated()]
positives.extend(negatives)
delta.body = positives
class MaterializedViewTheory(TopDownTheory):
"""A theory that stores the table contents of views explicitly.
@ -1778,7 +1766,6 @@ class MaterializedViewTheory(TopDownTheory):
# need to eliminate self-joins here so that we fill all
# the tables introduced by self-join elimination.
for rule in DeltaRuleTheory.eliminate_self_joins([formula]):
DeltaRuleTheory.reorder(rule)
new_event = Event(formula=rule, insert=event.insert,
target=event.target)
self.enqueue(new_event)

View File

@ -444,16 +444,6 @@ class TestRuntime(base.TestCase):
run, 'q(1,2) r(2,3) r(2,4) u(3,5) u(4,6) s(1,3) s(1,4)',
'Insert into non-unary with different propagation')
# Negation ordering
code = ("p(x) :- not q(x), r(x)")
run = self.prep_runtime(code, "Negation ordering")
run.insert('r(1)', MAT_THEORY)
run.insert('r(2)', MAT_THEORY)
run.insert('q(1)', MAT_THEORY)
self.check_class(
run, 'r(1) r(2) q(1) p(2)',
'Reordering negation')
def test_select(self):
"""Materialized Theory: test the SELECT event handler."""
code = ("p(x, y) :- q(x), r(y)")