Operations
Call .query() or .update() on a Fragment to get an OperationRead<T> or Operation<T> — a database action that produces a value of type T. It doesn't run until you call .transactRead(tx) / .transact(tx) or tx.execute(op).
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: OperationRead<List<User>> = fragment.query(userCodec.all())
val maybeUser: OperationRead<User?> = fragment.query(userCodec.maxOne())
val oneUser: OperationRead<User> = fragment.query(userCodec.exactlyOne())
// Single-column: shorthand methods skip the codec
val allIds: OperationRead<List<Int>> =
sql { "SELECT id FROM users" }.queryAll(PgTypes.int4)
val maybeName: OperationRead<String?> =
sql { "SELECT name FROM users LIMIT 1" }.queryMaxOne(PgTypes.text)
val count: OperationRead<Int> =
sql { "SELECT count(*) FROM users" }.queryExactlyOne(PgTypes.int4)
// Multi-column: pass a RowCodec with a result mode
OperationRead<List<User>> allUsers = fragment.query(userCodec.all());
OperationRead<Optional<User>> maybeUser = fragment.query(userCodec.maxOne());
OperationRead<User> oneUser = fragment.query(userCodec.exactlyOne());
// Single-column: shorthand methods skip the codec
OperationRead<List<Integer>> allIds = Fragment.of("SELECT id FROM users").queryAll(PgTypes.int4);
OperationRead<Optional<String>> maybeName =
Fragment.of("SELECT name FROM users LIMIT 1").queryMaxOne(PgTypes.text);
OperationRead<Integer> count =
Fragment.of("SELECT count(*) FROM users").queryExactlyOne(PgTypes.int4);
// Multi-column: pass a RowCodec with a result mode
val allUsers: OperationRead[List[User]] = fragment.query(userCodec.all())
val maybeUser: OperationRead[Option[User]] = fragment.query(userCodec.maxOne())
val oneUser: OperationRead[User] = fragment.query(userCodec.exactlyOne())
// Single-column: shorthand methods skip the codec
val allIds: OperationRead[List[Int]] =
sql"SELECT id FROM users".queryAll(PgTypes.int4)
val maybeName: OperationRead[Option[String]] =
sql"SELECT name FROM users LIMIT 1".queryMaxOne(PgTypes.text)
val count: OperationRead[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) / Operation[Unit] (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
// Single-operation form: .transact(tx) handles commit/rollback/close.
fun cities(): List<City> = findCities.transact(tx)
// Multiple operations in one transaction: pass a block to tx.transact { … }.
// Each .run(mc) inside the block shares the same transaction.
fun citiesWithCount(): List<City> = tx.transact { mc ->
val list = findCities.run(mc)
val count = countCities.run(mc)
println("rows: $count")
list
}
// Single-operation form: .transactRead(tx) handles commit/rollback/close.
List<City> cities() {
return findCities.transactRead(tx);
}
// Multiple operations in one transaction: pass a block to tx.transact(…).
// Each mc.execute(…) inside the block shares the same transaction.
List<City> citiesWithCount() {
return tx.transact(
mc -> {
var list = mc.execute(findCities);
long count = mc.execute(countCities);
System.out.println("rows: " + count);
return list;
});
}
// Single-operation form: .transact(tx) handles commit/rollback/close.
def cities(): List[City] = findCities.transact(tx)
// Multiple operations in one transaction: compose with combineWith.
def citiesWithCount(): List[City] =
findCities
.combineWith(countCities) { (list, count) =>
println(s"rows: $count")
list
}
.transact(tx)
For multiple operations in a single transaction, execute each one inside a transact block:
- Kotlin
- Java
- Scala
val countUsers: OperationRead<Long> =
sql { "SELECT count(*) FROM users" }
.query(RowCodec.of(PgTypes.int8).exactlyOne())
val recentOrders: OperationRead<List<Order>> =
sql { "SELECT * FROM orders ORDER BY id DESC LIMIT 10" }
.query(orderCodec.all())
// Run both in one transaction using a transact block
fun dashboard(): Dashboard = tx.transact { mc ->
val count = countUsers.run(mc)
val orders = recentOrders.run(mc)
Dashboard(count, orders)
}
OperationRead<Long> countUsers =
Fragment.of("SELECT count(*) FROM users").query(RowCodec.of(PgTypes.int8).exactlyOne());
OperationRead<List<Order>> recentOrders =
Fragment.of(
"""
SELECT * FROM orders
ORDER BY id DESC LIMIT 10\
""")
.query(orderCodec.all());
// Run both in one transaction using a transact block
Dashboard dashboard() {
return tx.transact(
mc -> {
long count = mc.execute(countUsers);
List<Order> orders = mc.execute(recentOrders);
return new Dashboard(count, orders);
});
}
val countUsers: OperationRead[Long] =
sql"SELECT count(*) FROM users"
.query(RowCodec.of(PgTypes.int8).exactlyOne())
val recentOrders: OperationRead[List[Order]] =
sql"SELECT * FROM orders ORDER BY id DESC LIMIT 10"
.query(orderCodec.all())
// Run both in one transaction using a transact block
// Connection is available implicitly inside transact { }
def dashboard(): Dashboard = tx.transact {
val count = countUsers.run
val orders = recentOrders.run
Dashboard(count, orders)
}
For void operations — DDL, schema setup — use mc.update() inside a transact block:
- Kotlin
- Java
- Scala
fun applySchema() {
val createTable = Fragment.of("CREATE TABLE users (id INT, name VARCHAR(100))").execute()
val createIndex = Fragment.of("CREATE INDEX idx_users_name ON users (name)").execute()
tx.execute(Operation.allOf(createTable, createIndex))
}
void applySchema() {
tx.transactVoid(
mc -> {
mc.update(Fragment.of("CREATE TABLE users (id INT, name VARCHAR(100))"));
mc.update(Fragment.of("CREATE INDEX idx_users_name ON users (name)"));
});
}
def applySchema(): Unit =
sql"CREATE TABLE users (id INT, name VARCHAR(100))"
.execute()
.combine(sql"CREATE INDEX idx_users_name ON users (name)".execute())
.voided()
.transact(tx)
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 |
Composing operations
Operations compose as values: combine, sequence, and chain them so that multiple database actions run in a single transaction without manual connection handling. For the full set of combinators (.combineWith(), .then(), OperationRead.sequence(), OperationRead.ifEmpty(), and more), see Composing Operations.
.combineWith() combines two independent operations:
- Kotlin
- Java
- Scala
fun dashboard(): Dashboard =
tx.execute(countUsers.combineWith(recentOrders, ::Dashboard))
Dashboard dashboard() {
return tx.execute(countUsers.combineWith(recentOrders, Dashboard::new));
}
def dashboard(): Dashboard =
countUsers
.combineWith(recentOrders)(Dashboard.apply)
.transact(tx)
Query Analysis can walk an entire composed operation tree and verify every SQL statement in one call.