0
# Database I/O Actions
1
2
Composable, asynchronous database actions with effect tracking and transaction support in Slick.
3
4
## Capabilities
5
6
### DBIOAction
7
8
The foundation of Slick's asynchronous database operations with composability and effect tracking.
9
10
```scala { .api }
11
/**
12
* Composable database action with result type R, streaming type S, and effect E
13
* @param R Result type of the action
14
* @param S Streaming capability (NoStream or Streaming[T])
15
* @param E Effect type (Read, Write, Transactional, etc.)
16
*/
17
sealed trait DBIOAction[+R, +S <: NoStream, -E <: Effect] {
18
/** Transform the result of this action */
19
def map[R2](f: R => R2): DBIOAction[R2, S, E]
20
21
/** Compose this action with another action (monadic bind) */
22
def flatMap[R2, S2 <: NoStream, E2 <: Effect](f: R => DBIOAction[R2, S2, E2]): DBIOAction[R2, S2, E with E2]
23
24
/** Run another action after this one, ignoring this action's result */
25
def andThen[R2, S2 <: NoStream, E2 <: Effect](a: DBIOAction[R2, S2, E2]): DBIOAction[R2, S2, E with E2]
26
27
/** Run this action and another action in parallel, combining results */
28
def zip[R2, S2 <: NoStream, E2 <: Effect](a: DBIOAction[R2, S2, E2]): DBIOAction[(R, R2), S with S2, E with E2]
29
30
/** Run a cleanup action after this action, regardless of success or failure */
31
def andFinally[S2 <: NoStream, E2 <: Effect](a: DBIOAction[_, S2, E2]): DBIOAction[R, S with S2, E with E2]
32
33
/** Handle failures in this action */
34
def asTry: DBIOAction[Try[R], S, E]
35
36
/** Recover from failures with an alternative result */
37
def recover[R2 >: R](pf: PartialFunction[Throwable, R2]): DBIOAction[R2, S, E]
38
39
/** Recover from failures with an alternative action */
40
def recoverWith[R2 >: R, S2 <: NoStream, E2 <: Effect](pf: PartialFunction[Throwable, DBIOAction[R2, S2, E2]]): DBIOAction[R2, S with S2, E with E2]
41
42
/** Run this action in a transaction */
43
def transactionally: DBIOAction[R, S, E with Transactional]
44
45
/** Apply a cleanup action when this action fails */
46
def cleanUp[S2 <: NoStream, E2 <: Effect](f: Option[Throwable] => DBIOAction[_, S2, E2]): DBIOAction[R, S with S2, E with E2]
47
48
/** Convert failed action to successful action with exception as result */
49
def failed: DBIOAction[Throwable, S, E]
50
51
/** Filter the result based on a predicate */
52
def filter(p: R => Boolean)(implicit executor: ExecutionContext): DBIOAction[R, NoStream, E]
53
54
/** Transform with partial function, fail if not defined */
55
def collect[R2](pf: PartialFunction[R, R2])(implicit executor: ExecutionContext): DBIOAction[R2, NoStream, E]
56
57
/** Replace result with unit value */
58
def void: DBIOAction[Unit, NoStream, E]
59
60
/** Replace result with given value */
61
def as[A](a: => A): DBIOAction[A, NoStream, E]
62
63
/** Use a pinned database session for this action */
64
def withPinnedSession: DBIOAction[R, S, E]
65
66
/** Add a name for logging purposes */
67
def named(name: String): DBIOAction[R, S, E]
68
}
69
70
/**
71
* Type aliases for common action types
72
*/
73
type DBIO[+R] = DBIOAction[R, NoStream, Effect.All]
74
type StreamingDBIO[+R, +T] = DBIOAction[R, Streaming[T], Effect.All]
75
type ReadAction[+R, +S <: NoStream, -E <: Effect] = DBIOAction[R, S, E with Read]
76
type WriteAction[+R, +S <: NoStream, -E <: Effect] = DBIOAction[R, S, E with Write]
77
```
78
79
**Usage Examples:**
80
81
```scala
82
// Basic action composition
83
val action1: DBIO[Int] = coffees.length.result
84
val action2: DBIO[Seq[Coffee]] = coffees.result
85
86
val combined = for {
87
count <- action1
88
allCoffees <- action2
89
} yield (count, allCoffees)
90
91
// Parallel execution
92
val parallelAction = action1.zip(action2)
93
94
// Error handling
95
val safeAction = coffees.result.asTry.map {
96
case Success(coffees) => s"Found ${coffees.length} coffees"
97
case Failure(ex) => s"Error: ${ex.getMessage}"
98
}
99
100
// Transaction
101
val transactionalAction = (for {
102
_ <- coffees += Coffee("New Coffee", 3.50)
103
_ <- coffees.filter(_.price > 5.0).delete
104
} yield ()).transactionally
105
```
106
107
### Effect System
108
109
Track the types of operations performed by database actions.
110
111
```scala { .api }
112
/**
113
* Base trait for database effects
114
*/
115
sealed trait Effect
116
117
/**
118
* Standard database effects
119
*/
120
object Effect {
121
/** Read operations that don't modify data */
122
sealed trait Read extends Effect
123
124
/** Write operations that modify data */
125
sealed trait Write extends Effect
126
127
/** Operations that require transaction support */
128
sealed trait Transactional extends Effect
129
130
/** Schema operations (DDL) */
131
sealed trait Schema extends Effect
132
133
/** All possible effects */
134
type All = Read with Write with Transactional with Schema
135
}
136
```
137
138
### Streaming
139
140
Handle large result sets with streaming capabilities.
141
142
```scala { .api }
143
/**
144
* Streaming capability marker
145
*/
146
sealed trait StreamingAction[+R, +T] extends DBIOAction[R, Streaming[T], Effect.All]
147
148
/**
149
* No streaming capability
150
*/
151
sealed trait NoStream
152
153
/**
154
* Streaming capability with element type T
155
*/
156
final class Streaming[+T] private[slick] ()
157
```
158
159
**Usage Examples:**
160
161
```scala
162
// Stream large result sets
163
val largeResultStream: StreamingDBIO[_, Coffee] = coffees.result
164
165
// Process streaming results with Akka Streams (example integration)
166
import akka.stream.scaladsl.Source
167
import akka.NotUsed
168
169
val source: Source[Coffee, NotUsed] =
170
Source.fromPublisher(db.stream(coffees.result))
171
172
// Streaming with processing
173
val processedStream = db.stream(coffees.result)
174
.map(coffee => coffee.copy(price = coffee.price * 1.1))
175
.take(1000)
176
```
177
178
### Action Combinators
179
180
Combine and sequence database actions in various ways.
181
182
```scala { .api }
183
object DBIO {
184
/** Create an action that returns the given value */
185
def successful[R](v: R): DBIO[R]
186
187
/** Create a failed action with the given exception */
188
def failed[R](t: Throwable): DBIO[R]
189
190
/** Run a sequence of actions sequentially, returning all results */
191
def sequence[R](actions: Seq[DBIO[R]]): DBIO[Seq[R]]
192
193
/** Run a sequence of actions sequentially, ignoring results */
194
def seq(actions: DBIO[_]*): DBIO[Unit]
195
196
/** Convert a Scala Future to a DBIO action */
197
def from[R](f: Future[R]): DBIO[R]
198
199
/** Create an action from a side-effecting function */
200
def fold[T, R](values: Seq[T], zero: R)(f: (R, T) => DBIO[R]): DBIO[R]
201
202
/** Traverse a sequence, applying an action to each element */
203
def traverse[T, R](values: Seq[T])(f: T => DBIO[R]): DBIO[Seq[R]]
204
}
205
```
206
207
**Usage Examples:**
208
209
```scala
210
// Sequential actions
211
val setupActions = DBIO.seq(
212
coffees.schema.create,
213
coffees += Coffee("Americano", 2.50),
214
coffees += Coffee("Latte", 3.00),
215
coffees += Coffee("Espresso", 2.00)
216
)
217
218
// Batch operations
219
val insertActions = Seq(
220
Coffee("Mocha", 3.50),
221
Coffee("Cappuccino", 3.00),
222
Coffee("Macchiato", 3.25)
223
).map(coffee => coffees += coffee)
224
225
val batchInsert = DBIO.sequence(insertActions)
226
227
// Conditional actions
228
def getCoffeeOrCreate(name: String): DBIO[Coffee] = {
229
coffees.filter(_.name === name).result.headOption.flatMap {
230
case Some(coffee) => DBIO.successful(coffee)
231
case None =>
232
val newCoffee = Coffee(name, 2.50)
233
(coffees += newCoffee).map(_ => newCoffee)
234
}
235
}
236
237
// Traverse pattern
238
val coffeeNames = Seq("Americano", "Latte", "Cappuccino")
239
val findOrCreateAll = DBIO.traverse(coffeeNames)(getCoffeeOrCreate)
240
```
241
242
### Query Execution
243
244
Execute queries and return results in various forms.
245
246
```scala { .api }
247
/**
248
* Query result execution methods
249
*/
250
trait QueryExecutionMethods[E, U, C[_]] {
251
/** Execute query and return all results */
252
def result: StreamingDBIO[C[U], U]
253
254
/** Execute query and return first result */
255
def head: DBIO[U]
256
257
/** Execute query and return optional first result */
258
def headOption: DBIO[Option[U]]
259
260
/** Execute query and return streaming results */
261
def stream: StreamingDBIO[C[U], U]
262
}
263
264
/**
265
* Update/Insert/Delete execution methods
266
*/
267
trait ModifyingExecutionMethods[E] {
268
/** Insert a single row */
269
def += (value: E): DBIO[Int]
270
271
/** Insert multiple rows */
272
def ++= (values: Iterable[E]): DBIO[Int]
273
274
/** Insert or update a row (upsert) */
275
def insertOrUpdate(value: E): DBIO[Int]
276
277
/** Update matching rows */
278
def update: DBIO[Int]
279
280
/** Delete matching rows */
281
def delete: DBIO[Int]
282
}
283
```
284
285
**Usage Examples:**
286
287
```scala
288
// Query execution
289
val allCoffees: DBIO[Seq[Coffee]] = coffees.result
290
val firstCoffee: DBIO[Coffee] = coffees.head
291
val maybeCoffee: DBIO[Option[Coffee]] = coffees.headOption
292
293
// Modifications
294
val insertAction: DBIO[Int] = coffees += Coffee("New Coffee", 3.00)
295
val batchInsertAction: DBIO[Int] = coffees ++= Seq(
296
Coffee("Coffee 1", 2.50),
297
Coffee("Coffee 2", 2.75)
298
)
299
300
val updateAction: DBIO[Int] = coffees
301
.filter(_.name === "Old Coffee")
302
.map(_.name)
303
.update("Updated Coffee")
304
305
val deleteAction: DBIO[Int] = coffees
306
.filter(_.price > 5.0)
307
.delete
308
309
// Upsert
310
val upsertAction: DBIO[Int] = coffees.insertOrUpdate(Coffee("Specialty", 4.50))
311
```
312
313
### Transactions
314
315
Manage database transactions for consistency and atomicity.
316
317
```scala { .api }
318
/**
319
* Transaction isolation levels
320
*/
321
object TransactionIsolation {
322
val ReadUncommitted: Int
323
val ReadCommitted: Int
324
val RepeatableRead: Int
325
val Serializable: Int
326
}
327
328
/**
329
* Transaction methods
330
*/
331
trait DatabaseTransaction {
332
/** Run action in a transaction */
333
def transactionally: DBIOAction[R, S, E with Transactional]
334
335
/** Run action in a transaction with specific isolation level */
336
def withTransactionIsolation(level: Int): DBIOAction[R, S, E with Transactional]
337
}
338
```
339
340
**Usage Examples:**
341
342
```scala
343
// Simple transaction
344
val transferMoney = (for {
345
_ <- accounts.filter(_.id === fromAccountId).map(_.balance).update(fromBalance - amount)
346
_ <- accounts.filter(_.id === toAccountId).map(_.balance).update(toBalance + amount)
347
_ <- transactions += Transaction(fromAccountId, toAccountId, amount)
348
} yield ()).transactionally
349
350
// Transaction with error handling
351
val safeTransfer = transferMoney.asTry.map {
352
case Success(_) => "Transfer completed successfully"
353
case Failure(ex) => s"Transfer failed: ${ex.getMessage}"
354
}
355
356
// Nested transactions (savepoints)
357
val complexTransaction = (for {
358
user <- users += User("Alice")
359
profile <- profiles += Profile(user.id, "Alice's Profile")
360
// Inner transaction that might fail
361
_ <- (orders += Order(user.id, "Failed Item")).transactionally.asTry
362
_ <- orders += Order(user.id, "Success Item")
363
} yield user).transactionally
364
365
// Custom isolation level
366
val highConsistencyAction = coffees.result
367
.withTransactionIsolation(TransactionIsolation.Serializable)
368
```
369
370
### Error Handling
371
372
Handle database errors and exceptions in actions.
373
374
```scala { .api }
375
/**
376
* Error handling methods
377
*/
378
trait ErrorHandling[R, S <: NoStream, E <: Effect] {
379
/** Convert to Try[R] to handle exceptions */
380
def asTry: DBIOAction[Try[R], S, E]
381
382
/** Recover from specific exceptions */
383
def recover[R2 >: R](pf: PartialFunction[Throwable, R2]): DBIOAction[R2, S, E]
384
385
/** Recover with alternative action */
386
def recoverWith[R2 >: R, S2 <: NoStream, E2 <: Effect](
387
pf: PartialFunction[Throwable, DBIOAction[R2, S2, E2]]
388
): DBIOAction[R2, S with S2, E with E2]
389
390
/** Handle both success and failure cases */
391
def andThen[U](pf: PartialFunction[Try[R], U]): DBIOAction[R, S, E]
392
}
393
```
394
395
**Usage Examples:**
396
397
```scala
398
// Try-based error handling
399
val safeQuery = coffees.result.asTry.map {
400
case Success(coffees) => Right(coffees)
401
case Failure(ex) => Left(s"Database error: ${ex.getMessage}")
402
}
403
404
// Recover from specific errors
405
val queryWithFallback = coffees.filter(_.name === "Nonexistent").result.recover {
406
case _: NoSuchElementException => Seq.empty[Coffee]
407
case ex: SQLException => throw new RuntimeException(s"Database error: ${ex.getMessage}")
408
}
409
410
// Recover with alternative action
411
val queryWithAlternative = coffees.filter(_.category === "special").result.recoverWith {
412
case _: SQLException => coffees.take(10).result
413
}
414
415
// Cleanup on error
416
val actionWithCleanup = (for {
417
tempId <- tempTable += TempData("processing")
418
result <- complexProcessing(tempId)
419
_ <- tempTable.filter(_.id === tempId).delete
420
} yield result).cleanUp { errorOpt =>
421
errorOpt.fold(DBIO.successful(()))(ex =>
422
DBIO.seq(
423
tempTable.filter(_.status === "processing").delete,
424
logError(ex.getMessage)
425
)
426
)
427
}
428
```
429
430
## Types
431
432
```scala { .api }
433
sealed trait DBIOAction[+R, +S <: NoStream, -E <: Effect]
434
type DBIO[+R] = DBIOAction[R, NoStream, Effect.All]
435
type StreamingDBIO[+R, +T] = DBIOAction[R, Streaming[T], Effect.All]
436
437
sealed trait Effect
438
object Effect {
439
trait Read extends Effect
440
trait Write extends Effect
441
trait Transactional extends Effect
442
trait Schema extends Effect
443
type All = Read with Write with Transactional with Schema
444
}
445
446
sealed trait NoStream
447
final class Streaming[+T] private[slick] ()
448
449
// Execution result types
450
type DatabasePublisher[T] // Reactive Streams Publisher
451
type StreamingInvoker[R, T] // For streaming execution
452
```