Skip to content

Commit 29b8921

Browse files
Merge pull request #52 from AbdelrhmanBassiouny/better_match
Better match
2 parents b322738 + a78c6d4 commit 29b8921

30 files changed

+941
-315
lines changed

examples/eql/cache.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ from dataclasses import dataclass
2828
2929
from typing_extensions import List
3030
31-
from krrood.entity_query_language.entity import entity, an, let, contains, Symbol
31+
from krrood.entity_query_language.entity import entity, let, contains, Symbol
32+
from krrood.entity_query_language.quantify_entity import an
3233
3334
3435
@dataclass

examples/eql/comparators.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ from dataclasses import dataclass
2121
from typing_extensions import List
2222
2323
from krrood.entity_query_language.entity import (
24-
entity, an, let, Symbol,
24+
entity, let, Symbol,
2525
in_, contains, not_, and_, or_,
2626
)
27+
from krrood.entity_query_language.quantify_entity import an
2728
2829
2930
@dataclass

examples/eql/domain_mapping.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,11 @@ from typing_extensions import List, Dict
2929
from krrood.entity_query_language.entity import (
3030
entity,
3131
set_of,
32-
an,
3332
let,
3433
flatten,
3534
Symbol,
3635
)
37-
36+
from krrood.entity_query_language.quantify_entity import an
3837
3938
@dataclass
4039
class Body(Symbol):

examples/eql/eql_for_sql_experts.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ from dataclasses import dataclass, field
5555
5656
from typing_extensions import List
5757
58-
from krrood.entity_query_language.entity import let, Symbol, entity, an, and_, in_, contains, set_of
58+
from krrood.entity_query_language.entity import let, Symbol, entity, and_, in_, contains, set_of
59+
from krrood.entity_query_language.quantify_entity import an
5960
6061
6162
@dataclass

examples/eql/intro.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ from dataclasses import dataclass
2828
2929
from typing_extensions import List
3030
31-
from krrood.entity_query_language.entity import entity, an, let, contains, Symbol
31+
from krrood.entity_query_language.entity import entity, let, contains, Symbol
32+
from krrood.entity_query_language.quantify_entity import an
3233
3334
3435
@dataclass

examples/eql/logical_operators.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ from dataclasses import dataclass
2121
2222
from typing_extensions import List
2323
24-
from krrood.entity_query_language.entity import entity, an, or_, Symbol, let, not_, and_
24+
from krrood.entity_query_language.entity import entity, or_, Symbol, let, not_, and_
25+
from krrood.entity_query_language.quantify_entity import an
2526
2627
2728
@dataclass

examples/eql/match.md

Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,17 @@ The following example shows how nested patterns translate
2121
into an equivalent manual query built with `entity(...)` and predicates.
2222

2323
```{code-cell} ipython3
24+
from krrood.entity_query_language.symbol_graph import SymbolGraph
2425
from dataclasses import dataclass
2526
from typing_extensions import List
2627
2728
from krrood.entity_query_language.entity import (
28-
let, entity, the,
29-
match, entity_matching, Symbol,
29+
let, entity, Symbol,
30+
)
31+
from krrood.entity_query_language.quantify_entity import the, an
32+
from krrood.entity_query_language.match import (
33+
match,
34+
entity_matching,
3035
)
3136
from krrood.entity_query_language.predicate import HasType
3237
@@ -61,7 +66,19 @@ class FixedConnection(Connection):
6166
@dataclass
6267
class World:
6368
connections: List[Connection]
69+
70+
@dataclass
71+
class Drawer(Symbol):
72+
handle: Handle
73+
container: Container
74+
75+
76+
@dataclass
77+
class Cabinet(Symbol):
78+
container: Container
79+
drawers: List[Drawer]
6480
81+
SymbolGraph()
6582
6683
# Build a small world with a few connections
6784
c1 = Container("Container1")
@@ -123,3 +140,131 @@ Notes:
123140
- Use `entity_matching` for the outer pattern when a domain is involved; inner attributes use `match`.
124141
- Nested `match(...)` can be composed arbitrarily deep following your object graph.
125142
- `entity_matching` is a syntactic sugar over the explicit `entity` + predicates form, so both are interchangeable.
143+
144+
## Selecting inner objects with `select()`
145+
146+
Use `select(Type)` when you want the matched inner objects to appear in the result. The evaluation then
147+
returns a mapping from the selected variables to the concrete objects (a unification dictionary).
148+
149+
```{code-cell} ipython3
150+
from krrood.entity_query_language.match import select
151+
152+
container, handle = select(Container), select(Handle)
153+
fixed_connection_query = the(
154+
entity_matching(FixedConnection, world.connections)(
155+
parent=container(name="Container1"),
156+
child=handle(name="Handle1"),
157+
)
158+
)
159+
160+
answers = fixed_connection_query.evaluate()
161+
print(answers[container].name, answers[handle].name)
162+
```
163+
164+
## Existential matches in collections with `match_any()`
165+
166+
When matching a container-like attribute (for example, a list), use `match_any(pattern)` to express that
167+
at least one element of the collection should satisfy the given pattern.
168+
169+
Below we add two simple view classes and build a small scene of drawers and a cabinet.
170+
171+
```{code-cell} ipython3
172+
from krrood.entity_query_language.match import match_any
173+
174+
# Build a simple set of views
175+
drawer1 = Drawer(handle=h1, container=c1)
176+
drawer2 = Drawer(handle=Handle("OtherHandle"), container=other_c)
177+
cabinet1 = Cabinet(container=c1, drawers=[drawer1, drawer2])
178+
cabinet2 = Cabinet(container=other_c, drawers=[drawer2])
179+
views = [drawer1, drawer2, cabinet1, cabinet2]
180+
181+
# Query: find the cabinet that has any drawer from the set {drawer1, drawer2}
182+
cabinet_query = an(entity_matching(Cabinet, views)(drawers=match_any([drawer1, drawer2])))
183+
184+
found_cabinets = list(cabinet_query.evaluate())
185+
assert len(found_cabinets) == 2
186+
print(found_cabinets[0].container.name, found_cabinets[0].drawers[0].handle.name)
187+
print(found_cabinets[1].container.name, found_cabinets[1].drawers[0].handle.name)
188+
```
189+
190+
## Selecting elements from collections with `select_any()`
191+
192+
If you want to retrieve a specific element from a collection attribute while matching, use `select_any(Type)`.
193+
It behaves like `match_any(Type)` but also selects the matched element so you can access it in the result.
194+
195+
```{code-cell} ipython3
196+
from krrood.entity_query_language.match import select_any
197+
198+
selected_drawers = select_any([drawer1, drawer2])
199+
# Query: find the cabinet that has any drawer from the set {drawer1, drawer2}
200+
cabinet_query = an(entity_matching(Cabinet, views)(drawers=selected_drawers))
201+
202+
ans = list(cabinet_query.evaluate())
203+
assert len(ans) == 2
204+
print(ans)
205+
```
206+
207+
## Selecting inner objects with `select()`
208+
209+
Use `select(Type)` when you want the matched inner objects to appear in the result. The evaluation then
210+
returns a mapping from the selected variables to the concrete objects (a unification dictionary).
211+
212+
```{code-cell} ipython3
213+
from krrood.entity_query_language.match import select
214+
215+
container, handle = select(Container), select(Handle)
216+
fixed_connection_query = the(
217+
entity_matching(FixedConnection, world.connections)(
218+
parent=container(name="Container1"),
219+
child=handle(name="Handle1"),
220+
)
221+
)
222+
223+
answers = fixed_connection_query.evaluate()
224+
print(answers[container].name, answers[handle].name)
225+
```
226+
227+
## Existential matches in collections with `match_any()`
228+
229+
When having multiple possible matches, and you care only if at least the attribute matches one possibility, use
230+
`match_any(IterableOfPossibleValues)` to express that
231+
at least one element of the collection should satisfy the given pattern.
232+
233+
Below we add two simple view classes and build a small scene of drawers and a cabinet.
234+
235+
```{code-cell} ipython3
236+
from krrood.entity_query_language.match import match_any
237+
238+
# Build a simple set of views
239+
drawer1 = Drawer(handle=h1, container=c1)
240+
drawer2 = Drawer(handle=Handle("OtherHandle"), container=other_c)
241+
cabinet1 = Cabinet(container=c1, drawers=[drawer1, drawer2])
242+
cabinet2 = Cabinet(container=other_c, drawers=[drawer2])
243+
views = [drawer1, drawer2, cabinet1, cabinet2]
244+
245+
# Query: find the cabinet that has any drawer from the set {drawer1, drawer2}
246+
cabinet_query = an(entity_matching(Cabinet, views)(drawers=match_any([drawer1, drawer2])))
247+
248+
found_cabinets = list(cabinet_query.evaluate())
249+
assert len(found_cabinets) == 2
250+
print(found_cabinets[0].container.name, found_cabinets[0].drawers[0].handle.name)
251+
print(found_cabinets[1].container.name, found_cabinets[1].drawers[0].handle.name)
252+
```
253+
254+
## Selecting elements from collections with `select_any()`
255+
256+
If you want to retrieve a specific element from a collection attribute while matching, use `select_any(Type)`.
257+
It behaves like `match_any(Type)` but also selects the matched element so you can access it in the result.
258+
259+
```{code-cell} ipython3
260+
from krrood.entity_query_language.match import select_any, entity_selection
261+
262+
selected_drawers = select_any([drawer1, drawer2])
263+
# Query: find the cabinet that has any drawer from the set {drawer1, drawer2}
264+
cabinet = entity_selection(Cabinet, views)
265+
cabinet_query = an(cabinet(drawers=selected_drawers))
266+
267+
ans = list(cabinet_query.evaluate())
268+
assert len(ans) == 2
269+
print(ans)
270+
```

examples/eql/predicate_and_symbolic_function.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ Lets first define our model and some sample data.
2626
from dataclasses import dataclass
2727
from typing_extensions import List
2828
29-
from krrood.entity_query_language.entity import entity, let, an, Symbol
29+
from krrood.entity_query_language.entity import entity, let, Symbol
3030
from krrood.entity_query_language.predicate import Predicate, symbolic_function
31+
from krrood.entity_query_language.quantify_entity import an
3132
3233
3334
@dataclass

examples/eql/result_quantifiers.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ from dataclasses import dataclass
2727
2828
from typing_extensions import List
2929
30-
from krrood.entity_query_language.entity import entity, let, the, Symbol, an
30+
from krrood.entity_query_language.entity import entity, let, Symbol
31+
from krrood.entity_query_language.quantify_entity import an, the
3132
from krrood.entity_query_language.result_quantification_constraint import AtLeast, AtMost, Exactly, Range
3233
from krrood.entity_query_language.failures import MultipleSolutionFound, LessThanExpectedNumberOfSolutions, GreaterThanExpectedNumberOfSolutions
3334
@@ -81,7 +82,7 @@ Below we reuse the same `World` and `Body` setup from above.
8182
The world contains exactly two bodies, so all the following examples will evaluate successfully.
8283

8384
```{code-cell} ipython3
84-
# Require at least two results
85+
# Require at least one result
8586
query = an(
8687
entity(body := let(Body, domain=world.bodies)),
8788
quantification=AtLeast(1),

examples/eql/writing_queries.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,15 @@ This approach ensures that your class definitions remain pure and decoupled from
3535
outside the explicit symbolic context. Consequently, your classes can focus exclusively on their domain logic,
3636
leading to better adherence to the [Single Responsibility Principle](https://realpython.com/solid-principles-python/#single-responsibility-principle-srp).
3737

38-
Here is a query that does work due to the missing `let` statement:
38+
Here is a query example that finds all bodies in a world whose name starts with "B":
3939

4040
```{code-cell} ipython3
4141
from dataclasses import dataclass
4242
4343
from typing_extensions import List
4444
45-
from krrood.entity_query_language.entity import entity, an, let, Symbol
45+
from krrood.entity_query_language.entity import entity, let, Symbol
46+
from krrood.entity_query_language.quantify_entity import an
4647
4748
4849
@dataclass

0 commit comments

Comments
 (0)