Skip to main content

Fragments

A Fragment is a composable SQL building block — it holds a SQL string together with its bound parameters. There are two ways to build fragments: string interpolation (Kotlin and Scala) and the builder pattern (all languages).

String Interpolation

Kotlin uses sql { } and Scala uses sql"" to build fragments from string templates. Database values are embedded as typed, bound parameters — never concatenated into the SQL string.

For a thorough explanation of how sql { } works internally and its thread safety guarantees, see Kotlin String Interpolation.

val query: Fragment =
sql { """
SELECT * FROM users
WHERE id = ${PgTypes.int4(userId)}
AND status = ${PgTypes.text("active")}
AND created_at > ${PgTypes.timestamptz(cutoffDate)}
""" }

// Execute safely — parameters are bound, not interpolated
val users: List<User> =
query.query(userCodec.all()).run(connection)

Inside the interpolation block, you can embed:

  • Bound values${PgTypes.int4(userId)} becomes a ? parameter
  • Other fragments${codec.columnList}, ${Fragment.whereAnd(filters)}, or another sql { } / sql"" block are spliced into the SQL

Builder Pattern

The builder pattern works in all languages and is useful for constructing fragments programmatically:

// Build fragments programmatically with the builder pattern
val frag: Fragment =
Fragment.of("SELECT * FROM users WHERE id = ")
.value(PgTypes.int4, 42)
.append(" AND active = ")
.value(PgTypes.bool, true)

For parameterized templates with unfilled parameter holes, see Templates (Advanced).

Which style should I use?
  • Kotlin — Use sql { } for queries where all values are known. Use the builder pattern when you need parameter holes for Templates (Advanced).
  • Scala — Same guidance, using sql"" instead of sql { }.
  • Java — Use the builder pattern for everything (no string interpolation available).

Composing Fragments

Build small reusable fragments, then combine them into full queries. Static factories like Fragment.whereAnd(), Fragment.set(), and Fragment.comma() handle SQL syntax — commas, AND/OR separators, SET clauses — so you don't have to:

// Build small reusable filters
fun byName(name: String): Fragment =
sql { "name ILIKE ${PgTypes.text(name)}" }

fun cheaperThan(max: BigDecimal): Fragment =
sql { "price < ${PgTypes.numeric(max)}" }

// Compose dynamically — only include the filters that are present
fun query(): List<ProductRow> {
val filters = listOfNotNull(
byName("%widget%"),
maxPrice?.let(::cheaperThan)
)

return tx.transact { conn ->
sql { "SELECT * FROM product ${Fragment.whereAnd(filters)}" }
.query(rowCodec.all())
.run(conn)
}
}

The same approach works for UPDATE statements — build a list of assignments and let Fragment.set() join them:

// Dynamic UPDATE — only set the fields that changed
fun updateUser(userId: Int, newName: String?, newEmail: String?) {
val sets = listOfNotNull(
newName?.let { sql { "name = ${PgTypes.text(it)}" } },
newEmail?.let { sql { "email = ${PgTypes.text(it)}" } }
)

if (sets.isNotEmpty()) {
sql { "UPDATE users ${Fragment.set(sets)} WHERE id = ${PgTypes.int4(userId)}" }
.update()
.run(conn)
}
}

Other useful combinators: Fragment.and(), Fragment.or(), Fragment.whereOr(), Fragment.orderBy(), Fragment.comma(), Fragment.parentheses().