And fmt
This commit is contained in:
parent
debc7996ee
commit
bbae5ef63f
34 changed files with 956 additions and 886 deletions
projects/datalog
|
@ -9,17 +9,17 @@ from uuid import uuid4 as uuid
|
|||
|
||||
|
||||
with open("graph.dtl", "w") as f:
|
||||
nodes = []
|
||||
nodes = []
|
||||
|
||||
# Generate 10k edges
|
||||
for i in range(10000):
|
||||
if nodes:
|
||||
from_node = choice(nodes)
|
||||
else:
|
||||
from_node = uuid()
|
||||
# Generate 10k edges
|
||||
for i in range(10000):
|
||||
if nodes:
|
||||
from_node = choice(nodes)
|
||||
else:
|
||||
from_node = uuid()
|
||||
|
||||
to_node = uuid()
|
||||
to_node = uuid()
|
||||
|
||||
nodes.append(to_node)
|
||||
nodes.append(to_node)
|
||||
|
||||
f.write(f"edge({str(from_node)!r}, {str(to_node)!r}).\n")
|
||||
f.write(f"edge({str(from_node)!r}, {str(to_node)!r}).\n")
|
||||
|
|
|
@ -26,5 +26,7 @@ setup(
|
|||
],
|
||||
# Package setup
|
||||
package_dir={"": "src/python"},
|
||||
packages=["datalog",],
|
||||
packages=[
|
||||
"datalog",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -16,8 +16,8 @@ def constexpr_p(expr):
|
|||
|
||||
class Timing(object):
|
||||
"""
|
||||
A context manager object which records how long the context took.
|
||||
"""
|
||||
A context manager object which records how long the context took.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.start = None
|
||||
|
@ -36,8 +36,8 @@ class Timing(object):
|
|||
|
||||
def __call__(self):
|
||||
"""
|
||||
If the context is exited, return its duration. Otherwise return the duration "so far".
|
||||
"""
|
||||
If the context is exited, return its duration. Otherwise return the duration "so far".
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
|
|
@ -22,9 +22,9 @@ def read(text: str, db_cls=PartlyIndexedDataset):
|
|||
def q(t: Tuple[str]) -> LTuple:
|
||||
"""Helper for writing terse queries.
|
||||
|
||||
Takes a tuple of strings, and interprets them as a logic tuple.
|
||||
So you don't have to write the logic tuple out by hand.
|
||||
"""
|
||||
Takes a tuple of strings, and interprets them as a logic tuple.
|
||||
So you don't have to write the logic tuple out by hand.
|
||||
"""
|
||||
|
||||
def _x(s: str):
|
||||
if s[0].isupper():
|
||||
|
@ -50,38 +50,38 @@ def __result(results_bindings):
|
|||
def select(db: Dataset, query: Tuple[str], bindings=None) -> Sequence[Tuple]:
|
||||
"""Helper for interpreting tuples of strings as a query, and returning simplified results.
|
||||
|
||||
Executes your query, returning matching full tuples.
|
||||
"""
|
||||
Executes your query, returning matching full tuples.
|
||||
"""
|
||||
|
||||
return __mapv(__result, __select(db, q(query), bindings=bindings))
|
||||
|
||||
|
||||
def join(db: Dataset, query: Sequence[Tuple[str]], bindings=None) -> Sequence[dict]:
|
||||
"""Helper for interpreting a bunch of tuples of strings as a join query, and returning simplified
|
||||
results.
|
||||
results.
|
||||
|
||||
Executes the query clauses as a join, returning a sequence of tuples and binding mappings such
|
||||
that the join constraints are simultaneously satisfied.
|
||||
Executes the query clauses as a join, returning a sequence of tuples and binding mappings such
|
||||
that the join constraints are simultaneously satisfied.
|
||||
|
||||
|
||||
>>> db = read('''
|
||||
... edge(a, b).
|
||||
... edge(b, c).
|
||||
... edge(c, d).
|
||||
... ''')
|
||||
>>> join(db, [
|
||||
... ('edge', 'A', 'B'),
|
||||
... ('edge', 'B', 'C')
|
||||
... ])
|
||||
[((('edge', 'a', 'b'),
|
||||
('edge', 'b', 'c')),
|
||||
{'A': 'a', 'B': 'b', 'C': 'c'}),
|
||||
((('edge', 'b', 'c'),
|
||||
('edge', 'c', 'd')),
|
||||
{'A': 'b', 'B': 'c', 'C': 'd'}),
|
||||
((('edge', 'c', 'd'),
|
||||
('edge', 'd', 'f')),
|
||||
{'A': 'c', 'B': 'd', 'C': 'f'})]
|
||||
"""
|
||||
>>> db = read('''
|
||||
... edge(a, b).
|
||||
... edge(b, c).
|
||||
... edge(c, d).
|
||||
... ''')
|
||||
>>> join(db, [
|
||||
... ('edge', 'A', 'B'),
|
||||
... ('edge', 'B', 'C')
|
||||
... ])
|
||||
[((('edge', 'a', 'b'),
|
||||
('edge', 'b', 'c')),
|
||||
{'A': 'a', 'B': 'b', 'C': 'c'}),
|
||||
((('edge', 'b', 'c'),
|
||||
('edge', 'c', 'd')),
|
||||
{'A': 'b', 'B': 'c', 'C': 'd'}),
|
||||
((('edge', 'c', 'd'),
|
||||
('edge', 'd', 'f')),
|
||||
{'A': 'c', 'B': 'd', 'C': 'f'})]
|
||||
"""
|
||||
|
||||
return __mapv(__result, __join(db, [q(c) for c in query], bindings=bindings))
|
||||
|
|
|
@ -20,8 +20,8 @@ from datalog.types import (
|
|||
def match(tuple, expr, bindings=None):
|
||||
"""Attempt to construct lvar bindings from expr such that tuple and expr equate.
|
||||
|
||||
If the match is successful, return the binding map, otherwise return None.
|
||||
"""
|
||||
If the match is successful, return the binding map, otherwise return None.
|
||||
"""
|
||||
|
||||
bindings = bindings.copy() if bindings is not None else {}
|
||||
for a, b in zip(expr, tuple):
|
||||
|
@ -43,9 +43,9 @@ def match(tuple, expr, bindings=None):
|
|||
|
||||
def apply_bindings(expr, bindings, strict=True):
|
||||
"""Given an expr which may contain lvars, substitute its lvars for constants returning the
|
||||
simplified expr.
|
||||
simplified expr.
|
||||
|
||||
"""
|
||||
"""
|
||||
|
||||
if strict:
|
||||
return tuple((bindings[e] if isinstance(e, LVar) else e) for e in expr)
|
||||
|
@ -56,10 +56,10 @@ def apply_bindings(expr, bindings, strict=True):
|
|||
def select(db: Dataset, expr, bindings=None, _recursion_guard=None, _select_guard=None):
|
||||
"""Evaluate an expression in a database, lazily producing a sequence of 'matching' tuples.
|
||||
|
||||
The dataset is a set of tuples and rules, and the expression is a single tuple containing lvars
|
||||
and constants. Evaluates rules and tuples, returning
|
||||
The dataset is a set of tuples and rules, and the expression is a single tuple containing lvars
|
||||
and constants. Evaluates rules and tuples, returning
|
||||
|
||||
"""
|
||||
"""
|
||||
|
||||
def __select_tuples():
|
||||
# As an opt. support indexed scans, which is optional.
|
||||
|
@ -170,8 +170,8 @@ def select(db: Dataset, expr, bindings=None, _recursion_guard=None, _select_guar
|
|||
def join(db: Dataset, clauses, bindings, pattern=None, _recursion_guard=None):
|
||||
"""Evaluate clauses over the dataset, joining (or antijoining) with the seed bindings.
|
||||
|
||||
Yields a sequence of tuples and LVar bindings for which all joins and antijoins were satisfied.
|
||||
"""
|
||||
Yields a sequence of tuples and LVar bindings for which all joins and antijoins were satisfied.
|
||||
"""
|
||||
|
||||
def __join(g, clause):
|
||||
for ts, bindings in g:
|
||||
|
|
|
@ -27,13 +27,19 @@ class Actions(object):
|
|||
return self._db_cls(tuples, rules)
|
||||
|
||||
def make_symbol(self, input, start, end, elements):
|
||||
return LVar("".join(e.text for e in elements),)
|
||||
return LVar(
|
||||
"".join(e.text for e in elements),
|
||||
)
|
||||
|
||||
def make_word(self, input, start, end, elements):
|
||||
return Constant("".join(e.text for e in elements),)
|
||||
return Constant(
|
||||
"".join(e.text for e in elements),
|
||||
)
|
||||
|
||||
def make_string(self, input, start, end, elements):
|
||||
return Constant(elements[1].text,)
|
||||
return Constant(
|
||||
elements[1].text,
|
||||
)
|
||||
|
||||
def make_comment(self, input, start, end, elements):
|
||||
return None
|
||||
|
@ -81,11 +87,11 @@ class Actions(object):
|
|||
class Parser(Grammar):
|
||||
"""Implementation detail.
|
||||
|
||||
A slightly hacked version of the Parser class canopy generates, which lets us control what the
|
||||
parsing entry point is. This lets me play games with having one parser and one grammar which is
|
||||
used both for the command shell and for other things.
|
||||
A slightly hacked version of the Parser class canopy generates, which lets us control what the
|
||||
parsing entry point is. This lets me play games with having one parser and one grammar which is
|
||||
used both for the command shell and for other things.
|
||||
|
||||
"""
|
||||
"""
|
||||
|
||||
def __init__(self, input, actions, types):
|
||||
self._input = input
|
||||
|
|
|
@ -66,8 +66,8 @@ class Dataset(object):
|
|||
class CachedDataset(Dataset):
|
||||
"""An extension of the dataset which features a cache of rule produced tuples.
|
||||
|
||||
Note that this cache is lost when merging datasets - which ensures correctness.
|
||||
"""
|
||||
Note that this cache is lost when merging datasets - which ensures correctness.
|
||||
"""
|
||||
|
||||
# Inherits tuples, rules, merge
|
||||
|
||||
|
@ -90,11 +90,11 @@ class CachedDataset(Dataset):
|
|||
class TableIndexedDataset(CachedDataset):
|
||||
"""An extension of the Dataset type which features both a cache and an index by table & length.
|
||||
|
||||
The index allows more efficient scans by maintaining 'table' style partitions.
|
||||
It does not support user-defined indexing schemes.
|
||||
The index allows more efficient scans by maintaining 'table' style partitions.
|
||||
It does not support user-defined indexing schemes.
|
||||
|
||||
Note that index building is delayed until an index is scanned.
|
||||
"""
|
||||
Note that index building is delayed until an index is scanned.
|
||||
"""
|
||||
|
||||
# From Dataset:
|
||||
# tuples, rules, merge
|
||||
|
@ -126,11 +126,11 @@ class TableIndexedDataset(CachedDataset):
|
|||
|
||||
class PartlyIndexedDataset(TableIndexedDataset):
|
||||
"""An extension of the Dataset type which features both a cache and and a full index by table,
|
||||
length, tuple index and value.
|
||||
length, tuple index and value.
|
||||
|
||||
The index allows extremely efficient scans when elements of the tuple are known.
|
||||
The index allows extremely efficient scans when elements of the tuple are known.
|
||||
|
||||
"""
|
||||
"""
|
||||
|
||||
# From Dataset:
|
||||
# tuples, rules, merge
|
||||
|
|
|
@ -25,8 +25,19 @@ def test_id_query(db_cls):
|
|||
Constant("a"),
|
||||
Constant("b"),
|
||||
)
|
||||
assert not select(db_cls([], []), ("a", "b",))
|
||||
assert select(db_cls([ab], []), ("a", "b",)) == [((("a", "b"),), {},)]
|
||||
assert not select(
|
||||
db_cls([], []),
|
||||
(
|
||||
"a",
|
||||
"b",
|
||||
),
|
||||
)
|
||||
assert select(db_cls([ab], []), ("a", "b",)) == [
|
||||
(
|
||||
(("a", "b"),),
|
||||
{},
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("db_cls,", DBCLS)
|
||||
|
@ -47,7 +58,17 @@ def test_lvar_unification(db_cls):
|
|||
|
||||
d = read("""edge(b, c). edge(c, c).""", db_cls=db_cls)
|
||||
|
||||
assert select(d, ("edge", "X", "X",)) == [((("edge", "c", "c"),), {"X": "c"})]
|
||||
assert (
|
||||
select(
|
||||
d,
|
||||
(
|
||||
"edge",
|
||||
"X",
|
||||
"X",
|
||||
),
|
||||
)
|
||||
== [((("edge", "c", "c"),), {"X": "c"})]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("db_cls,", DBCLS)
|
||||
|
@ -105,12 +126,12 @@ no-b(X, Y) :-
|
|||
def test_nested_antijoin(db_cls):
|
||||
"""Test a query which negates a subquery which uses an antijoin.
|
||||
|
||||
Shouldn't exercise anything more than `test_antjoin` does, but it's an interesting case since you
|
||||
actually can't capture the same semantics using a single query. Antijoins can't propagate positive
|
||||
information (create lvar bindings) so I'm not sure you can express this another way without a
|
||||
different evaluation strategy.
|
||||
Shouldn't exercise anything more than `test_antjoin` does, but it's an interesting case since you
|
||||
actually can't capture the same semantics using a single query. Antijoins can't propagate positive
|
||||
information (create lvar bindings) so I'm not sure you can express this another way without a
|
||||
different evaluation strategy.
|
||||
|
||||
"""
|
||||
"""
|
||||
|
||||
d = read(
|
||||
"""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue