Operations
Call .query() or .update() on a Fragment to get an Operation<T> — a database action that produces a value of type T. It doesn't run until you call .transact(tx) or .run(conn).
Queries
Read rows from the database. Pass a RowCodec with a result mode to control how the ResultSet is consumed. For single-column results, shorthand methods skip the codec:
- Kotlin
- Java
- Scala
// Multi-column: pass a RowCodec with a result mode
val allUsers: Operation<List<User>> = fragment.query(userCodec.all())
val maybeUser: Operation<User?> = fragment.query(userCodec.maxOne())
val oneUser: Operation<User> = fragment.query(userCodec.exactlyOne())
// Single-column: shorthand methods skip the codec
val allIds: Operation<List<Int>> =
sql { "SELECT id FROM users" }.queryAll(PgTypes.int4)
val maybeName: Operation<String?> =
sql { "SELECT name FROM users LIMIT 1" }.queryMaxOne(PgTypes.text)
val count: Operation<Int> =
sql { "SELECT count(*) FROM users" }.queryExactlyOne(PgTypes.int4)
// Multi-column: pass a RowCodec with a result mode
Operation<List<User>> allUsers = fragment.query(userCodec.all());
Operation<Optional<User>> maybeUser = fragment.query(userCodec.maxOne());
Operation<User> oneUser = fragment.query(userCodec.exactlyOne());
// Single-column: shorthand methods skip the codec
Operation<List<Integer>> allIds =
Fragment.of("SELECT id FROM users").queryAll(PgTypes.int4);
Operation<Optional<String>> maybeName =
Fragment.of("SELECT name FROM users LIMIT 1").queryMaxOne(PgTypes.text);
Operation<Integer> count =
Fragment.of("SELECT count(*) FROM users").queryExactlyOne(PgTypes.int4);
// Multi-column: pass a RowCodec with a result mode
val allUsers: Operation[List[User]] = fragment.query(userCodec.all())
val maybeUser: Operation[Option[User]] = fragment.query(userCodec.maxOne())
val oneUser: Operation[User] = fragment.query(userCodec.exactlyOne())
// Single-column: shorthand methods skip the codec
val allIds: Operation[List[Int]] =
sql"SELECT id FROM users".queryAll(PgTypes.int4)
val maybeName: Operation[Option[String]] =
sql"SELECT name FROM users LIMIT 1".queryMaxOne(PgTypes.text)
val count: Operation[Int] =
sql"SELECT count(*) FROM users".queryExactlyOne(PgTypes.int4)
Updates
Write to the database — INSERT, UPDATE, DELETE, or DDL:
- Kotlin
- Java
- Scala
// Get the affected row count
val rowCount: Operation<Int> = fragment.update()
// Ignore the row count (DDL, fire-and-forget DML)
val ignored: Operation<Unit> = fragment.execute()
// Get the affected row count
Operation<Integer> rowCount = fragment.update();
// Ignore the row count (DDL, fire-and-forget DML)
Operation<Void> ignored = fragment.execute();
// Get the affected row count
val rowCount: Operation[Int] = fragment.update()
// Ignore the row count (DDL, fire-and-forget DML)
val ignored: Operation[Unit] = fragment.execute()
Returning Rows from Updates
INSERT ... RETURNING or UPDATE ... RETURNING — run a write and read back the affected rows:
- Kotlin
- Java
- Scala
// INSERT ... RETURNING id, name
val insertedUsers: Operation<List<User>> =
sql { "INSERT INTO users (name) VALUES ('alice') RETURNING id, name" }
.updateReturning(userCodec.all())
val insertedUser: Operation<User> =
sql { "INSERT INTO users (name) VALUES ('alice') RETURNING id, name" }
.updateReturning(userCodec.exactlyOne())
// For databases that use generated keys instead of RETURNING (SQL Server, MariaDB)
val generatedId: Operation<Int> =
sql { "INSERT INTO users (name) VALUES ('alice')" }
.updateReturningGeneratedKeys(arrayOf("id"), RowCodec.of(PgTypes.int4).exactlyOne())
// INSERT ... RETURNING id, name
Operation<List<User>> insertedUsers =
Fragment.of("INSERT INTO users (name) VALUES ('alice') RETURNING id, name")
.updateReturning(userCodec.all());
Operation<User> insertedUser =
Fragment.of("INSERT INTO users (name) VALUES ('alice') RETURNING id, name")
.updateReturning(userCodec.exactlyOne());
// For databases that use generated keys instead of RETURNING (SQL Server, MariaDB)
Operation<Integer> generatedId =
Fragment.of("INSERT INTO users (name) VALUES ('alice')")
.updateReturningGeneratedKeys(
new String[]{"id"},
RowCodec.of(PgTypes.int4).exactlyOne());
// INSERT ... RETURNING id, name
val insertedUsers: Operation[List[User]] =
sql"INSERT INTO users (name) VALUES ('alice') RETURNING id, name"
.updateReturning(userCodec.all())
val insertedUser: Operation[User] =
sql"INSERT INTO users (name) VALUES ('alice') RETURNING id, name"
.updateReturning(userCodec.exactlyOne())
// For databases that use generated keys instead of RETURNING (SQL Server, MariaDB)
val generatedId: Operation[Int] =
sql"INSERT INTO users (name) VALUES ('alice')"
.updateReturningGeneratedKeys(Array("id"), RowCodec.of(PgTypes.int4).exactlyOne())
Execute (No Result)
When you don't need the row count — DDL statements, fire-and-forget DML — use .execute() instead of .update(). It returns Operation<Void> (Java) / Operation<Unit> (Kotlin/Scala):
Fragment.of("CREATE TABLE users (id INT, name VARCHAR)").execute()
This is equivalent to .update().voided().
Running Operations
Use a Transactor to obtain a connection, run the operation, and handle commit/rollback automatically:
- Kotlin
- Java
- Scala
fun cities(): List<City> = tx.transact { conn ->
findCities.run(conn)
}
List<City> cities() {
return tx.execute(conn -> findCities.run(conn));
}
def cities(): List[City] = tx.transact { conn =>
findCities.run(conn)
}
For multiple operations in a single transaction, call .run(conn) on each one inside the same block:
- Kotlin
- Java
- Scala
val countUsers: Operation<Long> =
sql { "SELECT count(*) FROM users" }
.query(RowCodec.of(PgTypes.int8).exactlyOne())
val recentOrders: Operation<List<Order>> =
sql { "SELECT * FROM orders ORDER BY id DESC LIMIT 10" }
.query(orderCodec.all())
// Run both in one transaction using the connection directly
fun dashboard(): Dashboard =
tx.transact { conn ->
val count = countUsers.run(conn)
val orders = recentOrders.run(conn)
Dashboard(count, orders)
}
Operation<Long> countUsers =
Fragment.of("SELECT count(*) FROM users")
.query(RowCodec.of(PgTypes.int8).exactlyOne());
Operation<List<Order>> recentOrders =
Fragment.of("""
SELECT * FROM orders
ORDER BY id DESC LIMIT 10""")
.query(orderCodec.all());
// Run both in one transaction using the connection directly
Dashboard dashboard() {
return tx.execute(conn -> {
long count = countUsers.run(conn);
List<Order> orders = recentOrders.run(conn);
return new Dashboard(count, orders);
});
}
val countUsers: Operation[Long] =
sql"SELECT count(*) FROM users"
.query(RowCodec.of(PgTypes.int8).exactlyOne())
val recentOrders: Operation[List[Order]] =
sql"SELECT * FROM orders ORDER BY id DESC LIMIT 10"
.query(orderCodec.all())
// Run both using the connection directly
def dashboard(): Dashboard =
tx.transact { conn =>
val count = countUsers.run(conn)
val orders = recentOrders.run(conn)
Dashboard(count, orders)
}
Operation Modifiers
Every operation supports these modifiers before execution:
| Modifier | Effect |
|---|---|
.named("findUser") | Prepends /* findUser */ to the SQL — visible in pg_stat_activity, slow query logs, and listener callbacks |
.timeout(Duration.ofSeconds(5)) | Sets a query timeout via Statement.setQueryTimeout() |
.withListener(listener) | Attaches a QueryListener to this specific operation |
.map(row -> transform(row)) | Transforms the result after execution |
.voided() | Discards the result, returning Operation<Void> |
Composing Operations
Operations can be composed as values — combined, sequenced, and chained — so that multiple database actions run in a single transaction without manual connection handling. For the full set of combinators (.combineWith(), .then(), Operation.sequence(), Operation.ifEmpty(), and more), see Composing Operations.
Here's a quick taste — .combineWith() combines two independent operations:
- Kotlin
- Java
- Scala
fun dashboard(): Dashboard = tx.transact { conn ->
countUsers.combineWith(recentOrders, ::Dashboard).run(conn)
}
Dashboard dashboard() {
return tx.execute(conn ->
countUsers
.combineWith(recentOrders, Dashboard::new)
.run(conn));
}
def dashboard(): Dashboard = tx.transact { conn =>
countUsers.combineWith(recentOrders)(Dashboard.apply)
.run(conn)
}
Query Analysis can walk an entire composed operation tree and verify every SQL statement in one call.