Skip to content

Queries

db.select(Model) returns a SelectQuery. Chain .where(...), .order_by(...), .limit(...), .offset(...), and .join(...) to build it, then call a terminal method (.all(), .first(), .one(), .count(), .exists()).

SelectQuery

query

SelectQuery: typed, chainable query builder.

SelectQuery

SelectQuery(database: Database, model_cls: type[T])

Bases: Generic[T]

A SELECT query over a single model.

Reads rows from the table (under the database lock) and filters / orders / limits in memory. All terminal methods (all, first, one, ...) acquire the lock and materialize results.

join

join(
    model: type[Model], *, on: Any, kind: str = "inner"
) -> Any

Begin a multi-model join rooted at this query's model.

Returns a :class:~tinystore.query.joins.JoinQuery; the on condition must compare two model field proxies, e.g. Post.author_id == User.id.

Joins

joins

JoinQuery: in-memory INNER / LEFT joins across registered models.

Joins are evaluated in memory: every participating table is read fully and combined with nested loops. This is deliberate and documented — TinyStore stores rows as JSON files, so a disk-efficient join is not the goal of v1. The result of a join is a list of :class:Row objects, one per combined row, with attribute access by lowercased model name (row.user, row.post) and positional access (row[0]).

Example

.. code-block:: python

rows = (
    db.select(User)
    .join(Post, on=Post.author_id == User.id)
    .where(User.name == "Alice")
    .order_by(Post.title)
    .all()
)
for r in rows:
    print(r.user.name, r.post.title)

Row

Row(
    models: tuple[type[Model], ...], values: tuple[Any, ...]
)

A single joined result row.

Behaves like a tuple of model instances (in the order the models were added to the query) and also supports attribute access by lowercased model name. Unmatched RIGHT rows in a LEFT join contribute None to their slot.

JoinQuery

JoinQuery(database: Database, base_model: type[Model])

A SELECT across multiple models with in-memory joins.

Expressions

Field proxies (User.age) build typed expressions when compared. Predicates combine with & (and), | (or), and ~ (not).

expressions

Query expression AST.

Expressions are composable objects (no eval). Each expression implements matches(row) where row is a stored JSON-row dict. Field proxies are produced by Model.field access (installed as non-data class descriptors in :mod:tinystore.model); e.g. User.name == "Alice" yields a :class:Comparison.

Evaluation semantics (see the project plan, decision D16):

  • None / missing fields are "absent", not a type error. Ordering operators against None return False; is_null/is_not_null are explicit.
  • For ==/!=/</<=/>/>=, if both the row value and the comparison value are non-None and belong to incompatible runtime type groups, a :class:~tinystore.exceptions.QueryError is raised (strict mode).

Expression

Bases: ABC

Base class for all query expressions.

JoinCondition

JoinCondition(
    a_model: Any, a_field: str, b_model: Any, b_field: str
)

A cross-table equality predicate produced by comparing two field proxies.

Post.author_id == User.id yields JoinCondition(Post, "author_id", User, "id"). The two sides are stored neutrally; the join resolves which side is the newly-joined model and which is already in the result set.

FieldProxy

FieldProxy(
    model: type, field: str, field_type: str = "any"
)

Returned by Model.field class access; builds expressions.

User.name == "Alice" returns a :class:Comparison. Note: defining __eq__ makes instances unhashable, which is fine (proxies are transient builders, not stored in sets/dicts).