The fast lane¶
Most queries skip Django's query building and SQL compiler, and Rust builds their results. They run two to four times faster, sync or async (benchmarks). Nothing to turn on.
await Book.objects.aget(pk=5)
[book async for book in Book.objects.filter(pages__gt=400).order_by("-pages")[:20]]
Book.objects.filter(Q(author__name="Jane Austen") | Q(in_print=False)).exclude(pages__lt=100)
Book.objects.annotate(chapter_count=Count("chapters")).filter(chapter_count__gt=10)
await Book.objects.filter(pages__gt=500).aaggregate(total=Sum("pages"))
await Book.objects.filter(pk=5).aupdate(pages=F("pages") + 1)
await Book.objects.acreate(title="Dune", pages=412, author=author)
What takes the fast lane¶
A query made of these calls, in any order Django accepts:
filter()andexclude(),Qobjects included (|,&,~), on the model's fields or through any relation (author__name,books__editions__number, a many-to-many field), withexact,gt,gte,lt,lte,inorisnull, comparing with plain values (numbers, text, booleans, dates, decimals, UUIDs, model instances) or with Django's expressions (pages__gt=F("chapters") * 10), and on annotations (chapter_count__gt=10);annotate()andalias()with Django's expressions:F(), arithmetic,Value(), aggregates (Count,Sum,Avg,Max,Min, withfilter=Q(...)anddefault=), and Django's functions (Lower,Coalesce,TruncDay...);order_by()with names or expressions (F("pages").desc(nulls_last=True)),reverse(),distinct(), and slices ([10:20]);values()andvalues_list(), with names or expressions (flat=Trueandnamed=Trueincluded);select_related(), and anyprefetch_related();- then read or written with one of these methods:
| Kind | Async | Sync |
|---|---|---|
| Rows | async for, aiterator(), aget(), afirst(), alast() |
for, list(), iterator(), get(), first(), last(), queryset[3] |
| Answers | acount(), aexists(), acontains(), aaggregate() |
count(), exists(), contains(), aggregate() |
| Writes | aupdate(...), adelete() |
update(...), delete() |
update()takes plain values and Django's expressions (F("pages") + 1).delete()takes the fast lane when Django would delete with one statement too: noon_deletework and no delete signal receivers.- Integer and UUID ids given as text (
pk="5", as URLs and DRF give them) take it too.
Everything else runs the usual way, with the same results: subqueries
(Exists(), Subquery(), OuterRef()), expression classes of your own,
transforms in names (published__year), XOR, extra(), only() and
defer(), union(), and models with parent models.
New objects¶
create(), acreate() and, on a djox.Model,
asave() of a new object insert it on the fast lane:
- The
INSERTis Django's. The first new object of a model is saved Django's way, and djox keeps the SQL Django wrote. The next ones only compute their values, like Django's compiler (auto_nowdates, aUUIDprimary key's default...). - The rest is
save()'s: the check of related objects,pre_saveandpost_save(sent withasend()from async code), the new id on the object. - Left to Django, on a djox thread from async code: models with their own
save(), parent models ororder_with_respect_to, fields with their ownpre_save()(likeFileField), a value from the database (db_default), a new object given a primary key withoutforce_insert(Django tries anUPDATEfirst), and, from async code, models with syncpre_saveorpost_savereceivers.
How it stays correct¶
- The SQL is Django's. A query is noted as its chain of calls. The first time djox meets a chain's shape (the same calls, names and expressions, with any values), Django builds the query and writes the SQL. djox keeps it, with where each parameter comes from, and reuses it for that shape. If it can't tell where a parameter comes from, Django keeps that shape.
- Errors come when Django raises them. Values are prepared by their
field when
filter()runs, and Django checks each new kind ofannotate(),order_by()... call once, when it is made: a wrong name raises right away, as in Django. - Conversions match Django's: Rust converts dates, times, decimals and booleans like Django's converters, and leaves any value it doesn't handle to Django.
- Sync methods join the thread's Django connection, like Django's own:
its transaction, its query log, and
SynchronousOnlyOperationin async code.