Alexander Ioffe
06/09/2025, 5:54 PM==
, if
, when
, Elvis, and lambdas) into vendor-perfect SQL _at compile time_—and runs the same query on Postgres, SQLite, or SQL Server across KMP targets!
▶️ Watch the video:
💻 Grab the code samples: https://github.com/ExoQuery/lambdaconf-2025
Drop your gnarliest query in the thread—let’s see if I can tame it with ExoQuery!gildor
06/10/2025, 5:37 AMandylamax
06/10/2025, 5:47 AMAlexander Ioffe
06/10/2025, 6:23 AMAlexander Ioffe
06/10/2025, 6:27 AMgildor
06/10/2025, 7:10 AMAlexander Ioffe
06/10/2025, 7:25 AMandylamax
06/10/2025, 2:06 PMandylamax
06/10/2025, 2:06 PMAlexander Ioffe
06/10/2025, 2:17 PMandylamax
06/10/2025, 10:36 PMCREATE TABLE IF NOT EXISTS ow_core_operational_units(
uid INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT,
name TEXT NOT NULL,
parent INTEGER,
leaf BOOLEAN NOT NULL DEFAULT FALSE,
FOREIGN KEY (parent) REFERENCES ow_core_operational_units(uid) ON DELETE RESTRICT ON UPDATE CASCADE
);
WITH RECURSIVE OperationalUnitAncestors AS (
SELECT * FROM ow_core_operational_units WHERE uid = ?
UNION ALL
SELECT ou.* FROM ow_core_operational_units AS ou
JOIN OperationalUnitAncestors ON ou.uid = OperationalUnitAncestors.parent
) SELECT * FROM OperationalUnitAncestors;
Not too complicated but even SQLDelight was a bit struggling with itYoussef Shoaib [MOD]
06/11/2025, 3:29 AMsuspend
allows us to get delimited continuations, which can be massaged (and with a bit of reflection, or a Gradle plugin to make continuations shallow-clonable) can be made multishot. This allows for direct-style code. I've been working on exactly that, and I wonder if it somehow can simplify or replace the need for a compiler plugin for ExoQuery entirely! ==
likely wouldn't work because it can't be made suspend
, but a lot of other constructs in Kotlin can.
I'll try to make some ExoQuery-like demo to see if this is possible w/o compiler plugin. I'm pretty sure a plugin is necessary to prevent e.g. spurious println
inside of the queries, but theoretically that plugin would be just a checker, and wouldn't need to do anything to the generated bytecodeAlexander Ioffe
06/11/2025, 5:11 AMfree
blocks you can do something like this:
data class OwCoreOperationalUnits(val uid: Int, val name: String, val parent: Int? = null, val leaf: Boolean = false)
data class OperationalUnitAncestors(val uid: Int, val name: String, val parent: Int? = null, val leaf: Boolean = false)
val q = capture {
free("WITH RECURSIVE OperationalUnitAncestors AS (${
Table<OwCoreOperationalUnits>().filter { it.uid == param(initialUid) }
unionAll
select {
val ou = from(Table<OwCoreOperationalUnits>())
val oa = join(Table<OperationalUnitAncestors>()) { oa -> oa.parent == ou.uid }
ou
}
})")<SqlQuery<OperationalUnitAncestors>>()
}
It produces this:
WITH RECURSIVE OperationalUnitAncestors AS (
(
SELECT
it.uid AS uid,
it.name AS name,
it.parent AS parent,
it.leaf AS leaf
FROM
OwCoreOperationalUnits it
WHERE
it.uid = ?
)
UNION ALL
(
SELECT
ou.uid AS uid,
ou.name AS name,
ou.parent AS parent,
ou.leaf AS leaf
FROM
OwCoreOperationalUnits ou
INNER JOIN OperationalUnitAncestors oa ON oa.parent = ou.uid
)
)
That looks right to me.
I'm surprised SQL Delight doesn't like this query. Was the parser having an issue?Alexander Ioffe
06/11/2025, 5:26 AMYoussef Shoaib [MOD]
06/11/2025, 5:34 AMExoString
with all operations being suspend
). User defined types can either use those custom types, or they can maybe have suspend
functions.
I think ExoQuery is definitely the end-game though in terms of clarity! I'm just wondering if one can get pretty close without needing to, you know, maintain a compiler plugin and all that. Maybe a combo of suspend
, a KSP plugin, and an optional plugin that produces compile-time errors on a best-effort basis (but doesn't affect code generation) could be sufficient.
Anyways, I'll refrain from brain-dumping here and instead try to actually make a PoC!Alexander Ioffe
06/11/2025, 5:43 AM