<exoquery.com> is live! :rocket: We built an inte...
# feed
a
exoquery.com is live! πŸš€ We built an interactive Kotlin playground where you can try ExoQuery directly in your browser - no setup, no downloads, just code. Here's what makes it fun: β†’ Hit "Run" and watch your Kotlin transform into SQL instantly β†’ Full autocomplete - put your cursor on any variable and press Ctrl+Space β†’ Every example comes with a real database schema so queries actually execute β†’ One-click "Download as Gradle Project" to take any sample home β†’ "Things to Try" suggestions under each example - like mini challenges to explore the edges We've got 12+ examples covering joins, aggregations, inserts, pagination, composable fragments, and more. Each one is fully editable - break things, fix things, experiment. The playground runs on a custom Kotlin backend that compiles and executes your code with a live Postgres database. It's basically a REPL for database queries. Stop reading docs. Start playing. πŸ‘‰ https://exoquery.com
K 6
amaze 1
πŸ‘€ 4
😍 1
h
how does this compare to jooq?
a
JOOQ like all of the other SQL-creation products out there is a DSL. You can't do
columnA == columnB
or
if (columnA == X) Y else Z
you need to use DSL constructs like
columnA eq columnB
or
Case.When(columnA eq X).Then(Y).Else(Z)
. This is a much bigger problem when you start joining many tables and doing anything that involves any sort of complexity. For example, here's a ExoQuery query that looks like something you'll see in the wild:
Copy code
sql.select {
    val c = from(Table<Customer>())
    val o = join(Table<Order>())     { o -> o.customerId == c.id }
    val oi = join(Table<OrderItem>()) { oi -> oi.orderId   == o.id }
    where {
      c.status == "ACTIVE" && o.orderDate >= param(startDate)
      (oi.quantity > 0 && (oi.discountPercent == null || oi.discountPercent <= param(BigDecimal("50"))))
    }
    // Grouping keys
    groupBy(c.id, c.firstName, c.lastName, o.id, o.orderDate)
    having {
      val total = sum(oi.quantity.toBigDecimal() * oi.unitPrice)
      total >= param(minTotal) && total <= param(maxTotal)
    }
    OrderSummary(
      customerFullName = c.firstName + " " + c.lastName,
      orderId          = o.id,
      orderDate        = o.orderDate,
      orderTotal       = sum(oi.quantity.toBigDecimal() * oi.unitPrice),
      distinctProducts = count(oi.productId)
    )
  }
It's complex, but comprehensible. Now compare it to what the equivalent in JOOQ would be:
Copy code
var c  = CUSTOMER.as("c");
var o  = ORDERS.as("o");
var oi = CUSTOMER_ORDER_ITEM.as("oi");

ctx.select(
        c.FIRST_NAME.concat(inline(" ")).concat(c.LAST_NAME).as("customer_full_name"),
        o.ORDER_DATE.as("order_date"),
        sum(oi.QUANTITY.cast(BigDecimal.class).mul(oi.UNIT_PRICE)).as("order_total"),
        countDistinct(oi.PRODUCT_ID).as("distinct_products")
    )
   .from(c)
   .join(o).on(o.CUSTOMER_ID.eq(c.ID))
   .join(oi).on(oi.ORDER_ID.eq(o.ID))
   .where(
       c.STATUS.eq("ACTIVE")
        .and(o.ORDER_DATE.ge(LocalDate.now().minusMonths(3)))
        .and(
            oi.QUANTITY.cast(BigDecimal.class)
              .mul(oi.UNIT_PRICE)
              .gt(new BigDecimal("50.00"))
        )
   )
   .groupBy(
       c.FIRST_NAME, c.LAST_NAME, o.ID, o.ORDER_DATE
   )
   .orderBy(
       sum(oi.QUANTITY.cast(BigDecimal.class).mul(oi.UNIT_PRICE)).desc()
   )
It's much harder to read and heaven-forbid you get an alias wrong somewhere like doing
oi.ORDER_ID.eq(oi.ID)
instead of
oi.ORDER_ID.eq(o.ID)
. Hunting down that sort of thing in prod can turn into a huge time-suck.
πŸ‘ 4
n
How does it work under the hood? Is it like ksp processing the kotlin to generate sql? Anyway I like the idea of compile time safety 😍
a
Under the hood it's a compiler-plugin. I wish I could use KSP but it's not powerful enough.
n
I'll have a look I'm curious how that works. I've played with KSP a bit but never made a compiler plugin directly
a
Most Kotlin compiler plugins use Visitor patterns but unfortunately that's also not powerful enough. I had to build a deconstructive pattern-matching system called Decomat that takes apart the syntax tree sanely enough (it emulates similar capability in Scala/C#/etc...).
I hear some people don't like using compiler plugins. Let me know if that's an issue for you. I'm thinking of some ways to mitigate it.
n
No no, not an issue at all. I just asked because I was curious about how it works under the hood
πŸ‘ 1