TeaQL Was 2,000× Faster Than the Obvious SQLx Query—Here’s What Actually Happened
We recently measured two implementations of the same application request over the MusicBrainz dataset:
Load the newest 100 recordings that have linked works, and load at most ten work relations for each recording.
The controlled SQLx test returned the same 100 recordings, 103 relation rows, 103 links, 103 link types, and Work-ID checksum through both paths. One took 5,871.169 milliseconds. The other took 2.469 milliseconds—a 2,378× difference inside SQLx itself.
TeaQL Rust previously completed the corresponding typed graph workload in 2.864 milliseconds. That does not mean TeaQL has a PostgreSQL driver 2,000× faster than SQLx. The difference was the amount of work requested from the database. The expert SQLx control proves it.
What TeaQL is
TeaQL is a model-driven application runtime. A semantic model generates language-native Q APIs for queries, E APIs for loaded expressions, and governed graph mutation APIs. The same model can target seven runtimes: Rust, Java, TypeScript, Go, Swift, .NET, and Python.
Queries carry more than SQL structure. They retain relation bounds, loaded
state, tenant and authorization scope, version policy, and an operational
comment and purpose. That additional intent is what made this optimization
possible without turning application code into handcrafted SQL.
The obvious single-statement SQLx solution
A capable SQL developer may reach for a window function to express Top-N per parent:
WITH ranked AS (
SELECT relation.*,
row_number() OVER (
PARTITION BY relation.entity0
ORDER BY relation.link_order ASC, relation.id DESC
) AS rn
FROM l_recording_work relation
WHERE relation.version > 0
), roots AS (
SELECT recording.id
FROM recording
WHERE recording.version > 0
AND EXISTS (
SELECT 1 FROM ranked WHERE ranked.entity0 = recording.id
)
ORDER BY recording.id DESC
LIMIT 100
)
SELECT ...
FROM roots
LEFT JOIN ranked
ON ranked.entity0 = roots.id AND ranked.rn <= 10
LEFT JOIN link ...
LEFT JOIN link_type ...
LEFT JOIN work ...;
This is readable, set-oriented SQL. It is not an obviously careless query. It also asks PostgreSQL to rank the complete relation population—about 2.7 million rows in this fixture—before reducing the graph to 100 roots and 103 relations.
In the Rust SQLx control, the PostgreSQL median was 5,871.169 ms. An earlier raw JDBC run measured 5,579.224 ms, confirming that changing the client library does not remove the database work. DuckDB, whose vectorized analytical engine fits this global-ranking shape better, completed the earlier query in 808.158 ms.
The business request carried a stronger bound
The corresponding TeaQL request describes a bounded object graph. In an application, we can name and reuse each meaningful graph fragment instead of repeating the whole nested request at every call site:
fn link_type_details() -> LinkTypeRequest {
Q::link_types_minimal()
.select_name()
.select_description()
.select_link_phrase()
}
fn link_details() -> LinkRequest {
Q::links_minimal()
.select_ended()
.select_link_type_with(link_type_details())
}
fn recording_work_details() -> LRecordingWorkRequest {
Q::l_recording_works_minimal()
.select_link_order()
.select_link_with(link_details())
.select_entity1_with(Q::works_minimal().select_name())
.order_by_link_order_asc()
.order_by_id_desc()
.limit(10)
}
Q::recordings_minimal()
.select_name()
.select_length()
.have_l_recording_works()
.select_l_recording_work_list_with(recording_work_details())
.order_by_id_desc()
.limit(100)
.comment("what: load the MB03 recording-work graph")
.purpose("why: render bounded recording-work details")
.execute_for_list(&context)
.await?;
The main request now reads as a composition of business-relevant graph fragments. The helpers are ordinary typed Rust functions: they can be reused, tested, extended, and combined like building blocks. They do not hide raw SQL or switch to a second query system; each still returns a generated TeaQL query selection that the runtime can govern as one request.
See the same TeaQL request fully expanded
Q::recordings_minimal()
.select_name()
.select_length()
.have_l_recording_works()
.select_l_recording_work_list_with(
Q::l_recording_works_minimal()
.select_link_order()
.select_link_with(
Q::links_minimal()
.select_ended()
.select_link_type_with(
Q::link_types_minimal()
.select_name()
.select_description()
.select_link_phrase(),
),
)
.select_entity1_with(Q::works_minimal().select_name())
.order_by_link_order_asc()
.order_by_id_desc()
.limit(10),
)
.order_by_id_desc()
.limit(100)
.comment("what: load the MB03 recording-work graph")
.purpose("why: render bounded recording-work details")
.execute_for_list(&context)
.await?;
The important information is not the Rust syntax. It is the request shape:
- choose 100 matching roots;
- load no more than ten ordered relations for each selected root;
- hydrate only the referenced Link, LinkType, and Work objects;
- preserve version, policy, comment, and purpose semantics throughout.
TeaQL can select the roots first and constrain relation loading to those roots. It does not need to rank relations belonging to recordings that cannot appear on this page.
The SQLx control explains the result
We then wrote the optimization explicitly in ordinary SQLx:
- fetch the 100 root recording IDs;
- pass those IDs to a second parameterized query;
- rank only relations whose
entity0belongs to that root set; - join Link, LinkType, and Work for the bounded rows.
Using one initialized SQLx pool connection, three warmups, and ten sequential measurements, the medians were:
| Implementation | PostgreSQL median |
|---|---|
| SQLx, natural global-window statement | 5,871.169 ms |
| SQLx, expert root-first two-stage plan | 2.469 ms |
| TeaQL Rust, typed governed graph (separate retained run) | 2.864 ms |
The first two rows are the controlled 2,378× comparison. The TeaQL row comes from a separate retained run and is context, not part of that ratio. It includes generated typed requests, relation hydration, and identity-graph assembly; the SQLx control decodes aggregate tuples.
How much code did each plan require?
We counted the physical non-blank lines that declare and execute each query. Imports, connection setup, timing, result gates, and reporting were excluded; embedded SQL was included because the application owns it.
| Implementation | Query LOC |
|---|---|
| SQLx, natural global window | 26 |
| SQLx, expert root-first | 34 |
| TeaQL, typed graph request (fully expanded) | 27 |
The numbers are deliberately unexciting: TeaQL is not winning through a tiny code-golf example. Its fully expanded typed request is about the same size as the natural SQL. The composed version moves reusable graph fragments out of the call site; it improves local readability without pretending that their definitions vanished. The important difference is what those lines preserve. The expert SQLx version owns two SQL statements, transfers root IDs, binds the array, and must keep both stages semantically aligned. TeaQL declares the root and relation bounds once inside the graph request.
LOC is formatting-sensitive, and the SQLx benchmark returns aggregate tuples while TeaQL hydrates entities. It should be read as maintenance surface, not as a universal productivity score. A fuller hand-written implementation would also need typed SQLx rows, graph assembly, authorization, tenant isolation, loaded-state handling, and observability.
Performance is only half of the problem
An expert can—and in this benchmark did—write the fast SQLx plan. TeaQL's advantage is not that manual optimization is impossible. It is that application developers do not have to discover, implement, and repeatedly preserve it themselves.
Every handcrafted optimization creates another enforcement point. The root query may contain a tenant predicate while the child query accidentally omits it. The same can happen to authorization scope, soft-delete/version policy, privacy masking, or trace metadata. Such mistakes are especially easy when an AI coding agent rewrites a query for performance: the output looks faster and can still leak another tenant's children.
TeaQL keeps both stages inside the same governed execution model. This is part of TeaQL Harness Engineering: reduce the space in which generated code can be fast but semantically or operationally wrong.
What the benchmark proves—and what it does not
It proves that API semantics can give a runtime enough information to avoid a large amount of unnecessary database work. It also shows why query-count rules are incomplete: one elegant statement can do much more work than several bounded statements.
It does not prove that:
- TeaQL has an intrinsically faster PostgreSQL driver than SQLx;
- every window query is slow;
- multiple queries are always preferable;
- these timings generalize to other hardware, datasets, indexes, or databases;
- 2,378× is a general TeaQL-versus-SQLx performance ratio.
The controlled result is narrower and more useful:
The obvious SQLx query ranked 2.7 million rows for a page containing 103 relations. Once the business bounds were applied before ranking, most of that work disappeared.
Reproduce it
The public TeaQL runtime benchmark repository retains four related evidence packages:
- B001: Rust TeaQL, Diesel, and SeaORM typed graph workloads;
- B002: raw JDBC across PostgreSQL and DuckDB;
- B003: Java TeaQL across PostgreSQL and DuckDB;
- B004: natural and expert root-first SQLx plans with a correctness gate.
Each package records source, query shape, environment, warmups, measurements, cardinalities, and checksums. B004 also contains an executable LOC counter.
The 2,378× headline is therefore reproducible, but deliberately narrow. The broader TeaQL claim is about making the good plan declarative, typed, reusable, and governed.
