Skip to content

Commit 7024940

Browse files
feat: add expression builder to query builder
1 parent 2f6a3c2 commit 7024940

File tree

4 files changed

+795
-8
lines changed

4 files changed

+795
-8
lines changed

README.md

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,112 @@ provider_container = ProviderBuilder().node().build()
161161
provider_kaolin_ws = ProviderBuilder().kaolin().ws().build()
162162
```
163163

164+
### Query Builder
165+
166+
The query builder provides a clean, chainable API for querying entities. It wraps the lower-level `query_entities` method with a SQL-like interface.
167+
168+
#### Basic Usage
169+
170+
```python
171+
from arkiv import Arkiv, StrAttr
172+
from arkiv.types import KEY, ATTRIBUTES
173+
174+
client = Arkiv()
175+
176+
# Define typed attributes
177+
entity_type = StrAttr("type")
178+
status = StrAttr("status")
179+
180+
# Simple query - select all fields
181+
results = client.arkiv.select().where(entity_type == "user").fetch()
182+
for entity in results:
183+
print(f"Entity: {entity.key}")
184+
185+
# Select specific fields
186+
results = client.arkiv.select(KEY, ATTRIBUTES).where(status == "active").fetch()
187+
188+
# Count matching entities
189+
count = client.arkiv.select().where(entity_type == "user").count()
190+
print(f"Found {count} users")
191+
```
192+
193+
#### Expressions with IntAttr/StrAttr
194+
195+
The expression builder generates SQL-like query strings under the hood, providing a type-safe Python API for constructing filter conditions. The `.where()` method accepts either an `Expr` object from the expression builder or a raw SQL-like query string (see [Query Language](#query-language) below).
196+
197+
For dynamic query building with runtime type checking, use the expression builder:
198+
199+
```python
200+
from arkiv import Arkiv, IntAttr, StrAttr, IntSort, DESC
201+
202+
client = Arkiv()
203+
204+
# Define typed attributes
205+
age = IntAttr("age")
206+
status = StrAttr("status")
207+
role = StrAttr("role")
208+
209+
# Build expressions with operators
210+
results = client.arkiv.select() \
211+
.where((age >= 18) & (status == "active")) \
212+
.order_by(IntSort("age", DESC)) \
213+
.fetch()
214+
215+
# Complex expressions with OR and AND
216+
results = client.arkiv.select() \
217+
.where((role == "admin") | (role == "moderator") & (status == "active")) \
218+
.fetch()
219+
220+
# NOT operator
221+
results = client.arkiv.select() \
222+
.where((age >= 18) & ~(status == "banned")) \
223+
.fetch()
224+
225+
# Type checking catches errors early
226+
age == "18" # TypeError: IntAttr 'age' requires int, got str
227+
status == 1 # TypeError: StrAttr 'status' requires str, got int
228+
```
229+
230+
**Expression Operators:**
231+
- `&` - AND
232+
- `|` - OR
233+
- `~` - NOT
234+
235+
**Note:** Always use parentheses around comparisons when combining with `&`, `|`, or `~` due to Python operator precedence.
236+
237+
#### Sorting with IntSort/StrSort
238+
239+
Use type-specific sort classes for ORDER BY clauses:
240+
241+
```python
242+
from arkiv import Arkiv, IntSort, StrSort, StrAttr, DESC
243+
244+
client = Arkiv()
245+
246+
# Define typed attributes
247+
entity_type = StrAttr("type")
248+
status = StrAttr("status")
249+
250+
# Define sorting_criteria
251+
status_asc = StrSort("status")
252+
age_desc = IntSort("age", DESC)
253+
254+
# Sort by age descending
255+
results = client.arkiv.select() \
256+
.where(entity_type == "user") \
257+
.order_by(age_desc) \
258+
.fetch()
259+
260+
# Multi-field sorting: status ascending, then age descending
261+
results = client.arkiv.select() \
262+
.where(status == "active") \
263+
.order_by(status_asc, age_desc) \
264+
.fetch()
265+
```
266+
164267
### Query Iterator
165268

166-
The `query_entities` method returns an iterator that automatically handles pagination, making it easy to work with large result sets:
269+
The `query_entities` method returns an iterator that automatically handles pagination, making it easy to work with large result sets. This is the lower-level API that the fluent query builder wraps:
167270

168271
```python
169272
from arkiv import Arkiv
@@ -254,7 +357,34 @@ Note that the GLOB operator might be replace by a SQL standard LIKE operator in
254357

255358
Query results can be sorted by one or more attribute fields in ascending or descending order. Sorting supports both string and numeric attributes, with multi-field sorting following priority order (first field has highest priority).
256359

257-
#### Basic Sorting
360+
#### Using the Query Builder (Recommended)
361+
362+
The query builder provides a cleaner API for sorting with `IntSort` and `StrSort`:
363+
364+
```python
365+
from arkiv import Arkiv, IntSort, StrSort, StrAttr, DESC
366+
367+
client = Arkiv()
368+
369+
# Define typed attribute
370+
entity_type = StrAttr("type")
371+
372+
# Sort by name ascending (default)
373+
results = client.arkiv.select().where(entity_type == "user").order_by(StrSort("name")).fetch()
374+
375+
# Sort by age descending
376+
results = client.arkiv.select().where(entity_type == "user").order_by(IntSort("age", DESC)).fetch()
377+
378+
# Multi-field sorting: status ascending, then age descending
379+
results = client.arkiv.select() \
380+
.where(entity_type == "user") \
381+
.order_by(StrSort("status"), IntSort("age", DESC)) \
382+
.fetch()
383+
```
384+
385+
#### Using QueryOptions (Lower-Level API)
386+
387+
For more control, use `OrderByAttribute` with `QueryOptions`:
258388

259389
```python
260390
from arkiv import Arkiv, ASC, DESC, STR, INT, OrderByAttribute, QueryOptions

src/arkiv/__init__.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,15 @@
77
from .events import EventFilter
88
from .events_async import AsyncEventFilter
99
from .node import ArkivNode
10-
from .query_builder import AsyncQueryBuilder, IntSort, QueryBuilder, StrSort
10+
from .query_builder import (
11+
AsyncQueryBuilder,
12+
Expr,
13+
IntAttr,
14+
IntSort,
15+
QueryBuilder,
16+
StrAttr,
17+
StrSort,
18+
)
1119
from .query_iterator import QueryIterator
1220
from .types import (
1321
ASC,
@@ -41,12 +49,15 @@
4149
"CreateEvent",
4250
"DeleteEvent",
4351
"EventFilter",
52+
"Expr",
4453
"ExtendEvent",
54+
"IntAttr",
4555
"IntSort",
4656
"NamedAccount",
4757
"OrderByAttribute",
4858
"QueryBuilder",
4959
"QueryIterator",
60+
"StrAttr",
5061
"StrSort",
5162
"TransactionReceipt",
5263
"UpdateEvent",

0 commit comments

Comments
 (0)