Pattern matching with match and entity_matching#
EQL provides a concise pattern-matching API for building nested structural queries.
Use match(type_)(...) to describe a nested pattern on attributes, and wrap the outermost match
with entity_matching(type_, domain)(...) when you also need to bind a search domain.
The following example shows how nested patterns translate
into an equivalent manual query built with entity(...) and predicates.
from krrood.entity_query_language.symbol_graph import SymbolGraph
from dataclasses import dataclass
from typing_extensions import List
from krrood.entity_query_language.entity import (
let, entity, Symbol,
)
from krrood.entity_query_language.quantify_entity import the, an
from krrood.entity_query_language.match import (
match,
entity_matching,
)
from krrood.entity_query_language.predicate import HasType
# --- Model -------------------------------------------------------------
@dataclass
class Body(Symbol):
name: str
@dataclass
class Handle(Body):
...
@dataclass
class Container(Body):
...
@dataclass
class Connection(Symbol):
parent: Body
child: Body
@dataclass
class FixedConnection(Connection):
...
@dataclass
class World:
connections: List[Connection]
@dataclass
class Drawer(Symbol):
handle: Handle
container: Container
@dataclass
class Cabinet(Symbol):
container: Container
drawers: List[Drawer]
SymbolGraph()
# Build a small world with a few connections
c1 = Container("Container1")
h1 = Handle("Handle1")
other_c = Container("ContainerX")
other_h = Handle("HandleY")
world = World(
connections=[
FixedConnection(parent=c1, child=h1),
FixedConnection(parent=other_c, child=h1),
]
)
Matching a nested structure#
entity_matching(FixedConnection, world.connections) selects from world.connections items of type
FixedConnection. Inner match(...) clauses describe constraints on attributes of that selected item.
fixed_connection_query = the(
entity_matching(FixedConnection, world.connections)(
parent=match(Container)(name="Container1"),
child=match(Handle)(name="Handle1"),
)
)
The equivalent manual query#
You can express the same query explicitly using entity, let, attribute comparisons, and HasType for
attribute type constraints:
fc = let(FixedConnection, domain=None)
fixed_connection_query_manual = the(
entity(
fc,
HasType(fc.parent, Container),
HasType(fc.child, Handle),
fc.parent.name == "Container1",
fc.child.name == "Handle1",
)
)
# The two query objects are structurally equivalent
assert fixed_connection_query == fixed_connection_query_manual
Evaluate the query#
fixed_connection = fixed_connection_query.evaluate()
print(type(fixed_connection).__name__, fixed_connection.parent.name, fixed_connection.child.name)
FixedConnection Container1 Handle1
Notes:
Use
entity_matchingfor the outer pattern when a domain is involved; inner attributes usematch.Nested
match(...)can be composed arbitrarily deep following your object graph.entity_matchingis a syntactic sugar over the explicitentity+ predicates form, so both are interchangeable.
Selecting inner objects with select()#
Use select(Type) when you want the matched inner objects to appear in the result. The evaluation then
returns a mapping from the selected variables to the concrete objects (a unification dictionary).
from krrood.entity_query_language.match import select
container, handle = select(Container), select(Handle)
fixed_connection_query = the(
entity_matching(FixedConnection, world.connections)(
parent=container(name="Container1"),
child=handle(name="Handle1"),
)
)
answers = fixed_connection_query.evaluate()
print(answers[container].name, answers[handle].name)
Container1 Handle1
Existential matches in collections with match_any()#
When matching a container-like attribute (for example, a list), use match_any(pattern) to express that
at least one element of the collection should satisfy the given pattern.
Below we add two simple view classes and build a small scene of drawers and a cabinet.
from krrood.entity_query_language.match import match_any
# Build a simple set of views
drawer1 = Drawer(handle=h1, container=c1)
drawer2 = Drawer(handle=Handle("OtherHandle"), container=other_c)
cabinet1 = Cabinet(container=c1, drawers=[drawer1, drawer2])
cabinet2 = Cabinet(container=other_c, drawers=[drawer2])
views = [drawer1, drawer2, cabinet1, cabinet2]
# Query: find the cabinet that has any drawer from the set {drawer1, drawer2}
cabinet_query = an(entity_matching(Cabinet, views)(drawers=match_any([drawer1, drawer2])))
found_cabinets = list(cabinet_query.evaluate())
assert len(found_cabinets) == 2
print(found_cabinets[0].container.name, found_cabinets[0].drawers[0].handle.name)
print(found_cabinets[1].container.name, found_cabinets[1].drawers[0].handle.name)
Container1 Handle1
ContainerX OtherHandle
Selecting elements from collections with select_any()#
If you want to retrieve a specific element from a collection attribute while matching, use select_any(Type).
It behaves like match_any(Type) but also selects the matched element so you can access it in the result.
from krrood.entity_query_language.match import select_any
selected_drawers = select_any([drawer1, drawer2])
# Query: find the cabinet that has any drawer from the set {drawer1, drawer2}
cabinet_query = an(entity_matching(Cabinet, views)(drawers=selected_drawers))
ans = list(cabinet_query.evaluate())
assert len(ans) == 2
print(ans)
[[Drawer(handle=Handle(name='Handle1'), container=Container(name='Container1')), Drawer(handle=Handle(name='OtherHandle'), container=Container(name='ContainerX'))], [Drawer(handle=Handle(name='OtherHandle'), container=Container(name='ContainerX'))]]
Selecting inner objects with select()#
Use select(Type) when you want the matched inner objects to appear in the result. The evaluation then
returns a mapping from the selected variables to the concrete objects (a unification dictionary).
from krrood.entity_query_language.match import select
container, handle = select(Container), select(Handle)
fixed_connection_query = the(
entity_matching(FixedConnection, world.connections)(
parent=container(name="Container1"),
child=handle(name="Handle1"),
)
)
answers = fixed_connection_query.evaluate()
print(answers[container].name, answers[handle].name)
Container1 Handle1
Existential matches in collections with match_any()#
When having multiple possible matches, and you care only if at least the attribute matches one possibility, use
match_any(IterableOfPossibleValues) to express that
at least one element of the collection should satisfy the given pattern.
Below we add two simple view classes and build a small scene of drawers and a cabinet.
from krrood.entity_query_language.match import match_any
# Build a simple set of views
drawer1 = Drawer(handle=h1, container=c1)
drawer2 = Drawer(handle=Handle("OtherHandle"), container=other_c)
cabinet1 = Cabinet(container=c1, drawers=[drawer1, drawer2])
cabinet2 = Cabinet(container=other_c, drawers=[drawer2])
views = [drawer1, drawer2, cabinet1, cabinet2]
# Query: find the cabinet that has any drawer from the set {drawer1, drawer2}
cabinet_query = an(entity_matching(Cabinet, views)(drawers=match_any([drawer1, drawer2])))
found_cabinets = list(cabinet_query.evaluate())
assert len(found_cabinets) == 2
print(found_cabinets[0].container.name, found_cabinets[0].drawers[0].handle.name)
print(found_cabinets[1].container.name, found_cabinets[1].drawers[0].handle.name)
Container1 Handle1
ContainerX OtherHandle
Selecting elements from collections with select_any()#
If you want to retrieve a specific element from a collection attribute while matching, use select_any(Type).
It behaves like match_any(Type) but also selects the matched element so you can access it in the result.
from krrood.entity_query_language.match import select_any, entity_selection
selected_drawers = select_any([drawer1, drawer2])
# Query: find the cabinet that has any drawer from the set {drawer1, drawer2}
cabinet = entity_selection(Cabinet, views)
cabinet_query = an(cabinet(drawers=selected_drawers))
ans = list(cabinet_query.evaluate())
assert len(ans) == 2
print(ans)
[{Cabinet: HashedValue(value=Cabinet(container=Container(name='Container1'), drawers=[Drawer(handle=Handle(name='Handle1'), container=Container(name='Container1')), Drawer(handle=Handle(name='OtherHandle'), container=Container(name='ContainerX'))]), id_=140181901702976), Cabinet.drawers: HashedValue(value=[Drawer(handle=Handle(name='Handle1'), container=Container(name='Container1')), Drawer(handle=Handle(name='OtherHandle'), container=Container(name='ContainerX'))], id_=140181901508672)}, {Cabinet: HashedValue(value=Cabinet(container=Container(name='ContainerX'), drawers=[Drawer(handle=Handle(name='OtherHandle'), container=Container(name='ContainerX'))]), id_=140181901701872), Cabinet.drawers: HashedValue(value=[Drawer(handle=Handle(name='OtherHandle'), container=Container(name='ContainerX'))], id_=140181901771904)}]