Choosing the Right Abstraction Pattern
Guide to selecting the appropriate pattern for your use case
Guide to selecting the appropriate pattern for your use case
Category: Advanced Features
Before diving into specific patterns, use this guide to select the right approach for your use case.
Decision Tree
Do multiple queries share the same 2+ joins?
โโโ YES โ Use Bundle + Destructure + Extend (see Abstraction Patterns)
โ Define shared joins ONCE in a composite type, destructure in each query
โ
โโโ NO โ Do you need to navigate a single relationship (A โ B)?
โโโ YES โ Use composeFrom extensions (see SQL Fragments)
โ Define `fun A.withB()` and call `from(a.withB())`
โ
โโโ NO โ Do you need chainable filters on the same query type?
โโโ YES โ Use extension function fragments
โ Define `fun SqlQuery<T>.filtered(): SqlQuery<T>`
โ
โโโ NO โ Use basic sql.select { } with inline joins
Quick Reference Table
| Situation | Pattern | Key Construct |
|---|---|---|
3 queries share Order โ Customer โ Item joins | Bundle + Destructure | data class OrderBase(val o, val c, val i) + destructure with val (o, c, i) = from(base()) |
Query 2 adds Product, Query 3 adds Product + Shipment | Extend from destructured | val p = from(oi.product()) after destructuring |
Single Invoice โ Subscription navigation | composeFrom extension | fun Invoice.subscription() = sql { composeFrom.join(...) } |
| Filter variations on same joined structure | Extension function fragments | fun SqlQuery<T>.paidOnly(): SqlQuery<T> |
Common Mistake
Wrong: Using composeFrom extensions for everything, repeating the same base joins in every query:
// โ Repeats i.warehouse() and i.product() in every query
val query1 = sql.select {
val i = from(Table<Inventory>())
val w = from(i.warehouse())
val p = from(i.product())
// ...
}
val query2 = sql.select {
val i = from(Table<Inventory>())
val w = from(i.warehouse()) // Repeated!
val p = from(i.product()) // Repeated!
val s = from(p.supplier())
// ...
}
Right: Bundle shared joins, extend only what varies:
// โ Base joins defined once
@SqlFragment
fun inventoryBase(): SqlQuery<InvBase> = sql.select {
val i = from(Table<Inventory>())
val w = join(Table<Warehouse>()) { w -> w.id == i.warehouseId }
val p = join(Table<Product>()) { p -> p.id == i.productId }
InvBase(i, w, p)
}
val query1 = sql.select {
val (i, w, p) = from(inventoryBase())
// ...
}
val query2 = sql.select {
val (i, w, p) = from(inventoryBase())
val s = from(p.supplier()) // Only the extension
// ...
}
See Also
- SQL Fragments -
composeFromfor single-relationship navigation - Abstraction Patterns - Bundle + Destructure for shared multi-join bases
- Polymorphic Queries - Generic and interface-based query abstraction