DEV Community

TeaQL Was 2,000x 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 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 entity0 belongs 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. Top comments (0)

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.