@zio.dev/zio-blocks 0.0.33 → 0.0.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/guides/compile-time-resource-safety-with-scope.md +16 -17
- package/guides/getting-started-with-mux.md +1507 -0
- package/guides/query-dsl-extending.md +161 -102
- package/guides/query-dsl-fluent-builder.md +217 -157
- package/guides/query-dsl-reified-optics.md +12 -10
- package/guides/query-dsl-sql.md +246 -165
- package/guides/telemetry-guide.md +1069 -0
- package/guides/zio-schema-migration.md +29 -22
- package/index.md +292 -50
- package/package.json +1 -1
- package/plans/config-follow-up-prs.md +188 -0
- package/plans/config-pr-assessment-roadmap.md +310 -0
- package/reference/MuxDataFlow.jsx +250 -0
- package/reference/async.md +651 -0
- package/reference/chunk.md +3533 -308
- package/reference/codegen/case-class.md +436 -0
- package/reference/codegen/emitter-config.md +383 -0
- package/reference/codegen/examples.md +664 -0
- package/reference/codegen/field.md +316 -0
- package/reference/codegen/index.md +317 -0
- package/reference/codegen/scala-emitter.md +392 -0
- package/reference/codegen/scala-file.md +276 -0
- package/reference/codegen/sealed-trait.md +408 -0
- package/reference/codegen/type-definition.md +340 -0
- package/reference/codegen/type-ref.md +201 -0
- package/reference/combinators.md +347 -117
- package/reference/config.md +158 -0
- package/reference/context.md +4 -4
- package/reference/datastar.md +346 -0
- package/reference/docs.md +1461 -345
- package/reference/endpoint/auth-type.md +146 -0
- package/reference/endpoint/endpoint.md +297 -0
- package/reference/endpoint/http-codec.md +249 -0
- package/reference/endpoint/index.md +825 -0
- package/reference/endpoint/path-codec.md +237 -0
- package/reference/endpoint/route-pattern.md +196 -0
- package/reference/endpoint/route-tree.md +111 -0
- package/reference/endpoint/segment-codec.md +212 -0
- package/reference/html.md +1120 -0
- package/reference/htmx/attribute-values.md +359 -0
- package/reference/htmx/hx-encoding.md +111 -0
- package/reference/htmx/hx-params.md +204 -0
- package/reference/htmx/hx-swap.md +276 -0
- package/reference/htmx/hx-sync.md +251 -0
- package/reference/htmx/hx-target.md +314 -0
- package/reference/htmx/hx-trigger.md +457 -0
- package/reference/htmx/hx-url-update.md +239 -0
- package/reference/htmx/index.md +855 -0
- package/reference/http-model/index.md +47 -0
- package/reference/http-model/model.md +1481 -0
- package/reference/http-model/schema.md +747 -0
- package/reference/maybe.md +826 -0
- package/reference/media-type.md +2 -2
- package/reference/mux.mdx +823 -0
- package/reference/openapi.md +1351 -0
- package/reference/resource-management/defer-handle.md +1 -1
- package/reference/resource-management/resource.md +31 -2
- package/reference/resource-management/scope.md +28 -12
- package/reference/resource-management/wire.md +3 -7
- package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
- package/reference/ringbuffer/MpscDiagram.jsx +618 -0
- package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
- package/reference/ringbuffer/SpscDiagram.jsx +677 -0
- package/reference/ringbuffer/advanced.mdx +109 -0
- package/reference/ringbuffer/index.mdx +145 -0
- package/reference/ringbuffer/mpmc.mdx +151 -0
- package/reference/ringbuffer/mpsc.mdx +132 -0
- package/reference/ringbuffer/spmc.mdx +108 -0
- package/reference/ringbuffer/spsc.mdx +344 -0
- package/reference/{allows.md → schema/allows.md} +4 -4
- package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
- package/reference/{binding.md → schema/binding.md} +2 -3
- package/reference/schema/built-in-codecs/avro.md +451 -0
- package/reference/schema/built-in-codecs/bson.md +480 -0
- package/reference/schema/built-in-codecs/csv.md +564 -0
- package/reference/schema/built-in-codecs/index.md +77 -0
- package/reference/schema/built-in-codecs/json/index.md +295 -0
- package/reference/schema/built-in-codecs/json/json-config.md +217 -0
- package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
- package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
- package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
- package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
- package/reference/schema/built-in-codecs/messagepack.md +508 -0
- package/reference/schema/built-in-codecs/thrift.md +433 -0
- package/reference/schema/built-in-codecs/toon.md +1078 -0
- package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
- package/reference/schema/built-in-codecs/yaml.md +552 -0
- package/reference/{codec.md → schema/codec.md} +10 -10
- package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
- package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
- package/reference/schema/format.md +92 -0
- package/reference/schema/index.md +50 -0
- package/reference/schema/migration.md +297 -0
- package/reference/{modifier.md → schema/modifier.md} +58 -7
- package/reference/{optics.md → schema/optics.md} +2 -2
- package/reference/{patch.md → schema/patch.md} +1 -1
- package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
- package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
- package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
- package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
- package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
- package/reference/{schema.md → schema/schema.md} +12 -0
- package/reference/{structural-types.md → schema/structural-types.md} +1 -1
- package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
- package/reference/smithy.md +533 -0
- package/reference/sql/db-codec-deriver.md +71 -0
- package/reference/sql/db-codec.md +687 -0
- package/reference/sql/db-con.md +271 -0
- package/reference/sql/db-connection.md +153 -0
- package/reference/sql/db-param-writer.md +77 -0
- package/reference/sql/db-param.md +66 -0
- package/reference/sql/db-result-reader.md +146 -0
- package/reference/sql/db-tx.md +82 -0
- package/reference/sql/db-value.md +41 -0
- package/reference/sql/ddl.md +85 -0
- package/reference/sql/frag.md +254 -0
- package/reference/sql/index.md +341 -0
- package/reference/sql/repo.md +600 -0
- package/reference/sql/sql-dialect.md +73 -0
- package/reference/sql/sql-logger.md +62 -0
- package/reference/sql/sql-name-mapper.md +70 -0
- package/reference/sql/table-metadata.md +134 -0
- package/reference/sql/table.md +448 -0
- package/reference/sql/transactor-zio.md +399 -0
- package/reference/sql/transactor.md +353 -0
- package/reference/sql-zio.md +112 -0
- package/reference/streams/concurrent-operators.md +106 -0
- package/reference/streams/index.md +653 -0
- package/reference/streams/pipeline.md +718 -0
- package/reference/streams/reader.md +1284 -0
- package/reference/streams/scala-2-compatibility.md +55 -0
- package/reference/streams/sink.md +1426 -0
- package/reference/streams/stream.md +2526 -0
- package/reference/streams/writer.md +1045 -0
- package/reference/streams/zero-boxing.md +275 -0
- package/reference/telemetry.md +693 -0
- package/reference/typeid.md +5 -19
- package/sidebars.js +238 -43
- package/reference/formats.md +0 -694
- package/reference/http-model.md +0 -1716
- package/reference/streams.md +0 -989
- package/ringbuffer.md +0 -249
- /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
- /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
- /package/reference/{lazy.md → schema/lazy.md} +0 -0
- /package/reference/{reflect.md → schema/reflect.md} +0 -0
- /package/reference/{registers.md → schema/registers.md} +0 -0
- /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
- /package/reference/{syntax.md → schema/syntax.md} +0 -0
- /package/reference/{validation.md → schema/validation.md} +0 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: transactor
|
|
3
|
+
title: "Transactor"
|
|
4
|
+
description: "Reference for Transactor and JdbcTransactor: the sql module's entry point for connection lifecycle and transaction management."
|
|
5
|
+
keywords:
|
|
6
|
+
- "JdbcTransactor connection lifecycle"
|
|
7
|
+
- "Transactor transaction management"
|
|
8
|
+
- "DbCon connection context"
|
|
9
|
+
- "DbTx transaction scope"
|
|
10
|
+
- "JDBC connection pooling"
|
|
11
|
+
- "SQL commit rollback"
|
|
12
|
+
- "TransactorZIO ZIO integration"
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
`Transactor` is the entry point for all SQL execution in the `sql` module. It declares two methods that acquire a JDBC connection, execute a provided body, and guarantee the connection is closed on return — whether the body succeeds or throws:
|
|
16
|
+
|
|
17
|
+
- **`Transactor#connect`** — supplies a `DbCon` implicit context for non-transactional queries; auto-commit remains at its default state.
|
|
18
|
+
- **`Transactor#transact`** — supplies a `DbTx` implicit context with auto-commit disabled; commits on success and rolls back on any exception.
|
|
19
|
+
|
|
20
|
+
`DbTx` extends `DbCon`, so every `Frag` execution method and every `Repo` CRUD operation that requires `DbCon` also works inside `transact`. The module's other core types — `Frag`, `Table`, and `Repo` — all depend on the context provided by `Transactor` to reach the database.
|
|
21
|
+
|
|
22
|
+
The structural shape of the trait is:
|
|
23
|
+
|
|
24
|
+
```scala
|
|
25
|
+
trait Transactor {
|
|
26
|
+
def connect[A](f: DbCon ?=> A): A
|
|
27
|
+
def transact[A](f: DbTx ?=> A): A
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`JdbcTransactor` is the concrete JDBC-backed implementation. Its companion object provides factory methods for all common connection strategies:
|
|
32
|
+
|
|
33
|
+
```scala
|
|
34
|
+
class JdbcTransactor(
|
|
35
|
+
connectionFactory: () => java.sql.Connection,
|
|
36
|
+
val dialect: SqlDialect,
|
|
37
|
+
val sqlLogger: SqlLogger
|
|
38
|
+
) extends Transactor
|
|
39
|
+
|
|
40
|
+
object JdbcTransactor {
|
|
41
|
+
def fromDataSource(dataSource: javax.sql.DataSource, dialect: SqlDialect): JdbcTransactor
|
|
42
|
+
def fromUrl(url: String, dialect: SqlDialect): JdbcTransactor
|
|
43
|
+
def fromUrl(url: String, user: String, password: String, dialect: SqlDialect): JdbcTransactor
|
|
44
|
+
def postgres(dataSource: javax.sql.DataSource): JdbcTransactor
|
|
45
|
+
def sqlite(dataSource: javax.sql.DataSource): JdbcTransactor
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
The following block demonstrates the complete workflow: create a transactor, open a transactional scope, and execute both `Repo` operations and raw `sql"..."` fragments inside it:
|
|
52
|
+
|
|
53
|
+
```scala
|
|
54
|
+
import zio.blocks.sql._
|
|
55
|
+
import zio.blocks.schema.Schema
|
|
56
|
+
|
|
57
|
+
case class User(id: Int, name: String, email: String)
|
|
58
|
+
object User { implicit val schema: Schema[User] = Schema.derived }
|
|
59
|
+
|
|
60
|
+
implicit val userCodec: DbCodec[User] = User.schema.deriving(DbCodecDeriver).derive
|
|
61
|
+
|
|
62
|
+
val repo = Repo.derived[User, Int]("users", "id", _.id)
|
|
63
|
+
val transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
64
|
+
|
|
65
|
+
// transact: auto-commit disabled; commits on success, rolls back on exception
|
|
66
|
+
transactor.transact {
|
|
67
|
+
repo.table.createTable(summon[DbTx].dialect).update
|
|
68
|
+
repo.insert(User(1, "Alice", "alice@example.com"))
|
|
69
|
+
repo.insert(User(2, "Bob", "bob@example.com"))
|
|
70
|
+
|
|
71
|
+
// Raw fragment composes freely with Repo operations inside the same scope
|
|
72
|
+
val active: List[User] =
|
|
73
|
+
sql"SELECT id, name, email FROM users WHERE id > ${0}".query[User]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// connect: auto-commit unchanged; no transaction overhead for pure reads
|
|
77
|
+
val users: List[User] = transactor.connect {
|
|
78
|
+
repo.all
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Construction / Creating Instances
|
|
83
|
+
|
|
84
|
+
We can create a `JdbcTransactor` from a `DataSource`, from a JDBC URL, or using database-specific convenience factories. In every case the third constructor parameter `sqlLogger` defaults to `SqlLogger.noop`, so it can be omitted unless query logging is required.
|
|
85
|
+
|
|
86
|
+
### `JdbcTransactor.fromDataSource` — Create from a DataSource
|
|
87
|
+
|
|
88
|
+
`JdbcTransactor.fromDataSource` is the recommended factory for production use. It calls `dataSource.getConnection` once per `connect` or `transact` invocation, so connection pooling is fully controlled by the `DataSource` implementation (for example, HikariCP or c3p0):
|
|
89
|
+
|
|
90
|
+
```scala
|
|
91
|
+
object JdbcTransactor {
|
|
92
|
+
def fromDataSource(dataSource: javax.sql.DataSource, dialect: SqlDialect): JdbcTransactor
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Pass the `DataSource` and the `SqlDialect` constant that matches your database. The following shows a typical setup where the data source is provided externally:
|
|
97
|
+
|
|
98
|
+
```scala
|
|
99
|
+
import zio.blocks.sql._
|
|
100
|
+
|
|
101
|
+
val dataSource: javax.sql.DataSource = ???
|
|
102
|
+
val transactor: JdbcTransactor =
|
|
103
|
+
JdbcTransactor.fromDataSource(dataSource, SqlDialect.PostgreSQL)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### `JdbcTransactor.fromUrl` — Create from a JDBC URL
|
|
107
|
+
|
|
108
|
+
`JdbcTransactor.fromUrl` creates a transactor that opens each connection via `DriverManager.getConnection`. Two overloads are available: one without credentials and one that accepts a username and password:
|
|
109
|
+
|
|
110
|
+
```scala
|
|
111
|
+
object JdbcTransactor {
|
|
112
|
+
def fromUrl(url: String, dialect: SqlDialect): JdbcTransactor
|
|
113
|
+
def fromUrl(url: String, user: String, password: String, dialect: SqlDialect): JdbcTransactor
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Use the credential-free overload for databases that embed authentication in the URL (for example, SQLite in-memory) or when the JDBC driver handles authentication separately:
|
|
118
|
+
|
|
119
|
+
```scala
|
|
120
|
+
import zio.blocks.sql._
|
|
121
|
+
|
|
122
|
+
// Without credentials — useful for SQLite or URL-embedded auth
|
|
123
|
+
val sqlite: JdbcTransactor =
|
|
124
|
+
JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
125
|
+
// sqlite: JdbcTransactor = zio.blocks.sql.JdbcTransactor@6bc883b
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Use the three-argument overload when the database requires a username and password supplied separately:
|
|
129
|
+
|
|
130
|
+
```scala
|
|
131
|
+
import zio.blocks.sql._
|
|
132
|
+
|
|
133
|
+
// With credentials — typical for PostgreSQL or MySQL
|
|
134
|
+
val postgres: JdbcTransactor =
|
|
135
|
+
JdbcTransactor.fromUrl(
|
|
136
|
+
"jdbc:postgresql://localhost/mydb",
|
|
137
|
+
"alice",
|
|
138
|
+
"secret",
|
|
139
|
+
SqlDialect.PostgreSQL
|
|
140
|
+
)
|
|
141
|
+
// postgres: JdbcTransactor = zio.blocks.sql.JdbcTransactor@40cda9e
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
:::caution
|
|
145
|
+
`fromUrl` opens a new physical connection for every `connect` or `transact` call and does no pooling. For applications with concurrent workloads, prefer `fromDataSource` backed by a pooling `DataSource`.
|
|
146
|
+
:::
|
|
147
|
+
|
|
148
|
+
### `JdbcTransactor.postgres` — PostgreSQL convenience factory
|
|
149
|
+
|
|
150
|
+
`JdbcTransactor.postgres` is shorthand for `fromDataSource(dataSource, SqlDialect.PostgreSQL)`. Use it to reduce boilerplate when working exclusively with PostgreSQL:
|
|
151
|
+
|
|
152
|
+
```scala
|
|
153
|
+
object JdbcTransactor {
|
|
154
|
+
def postgres(dataSource: javax.sql.DataSource): JdbcTransactor
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The factory fixes the dialect to `SqlDialect.PostgreSQL` so DDL type names, parameter placeholders, and dialect-specific SQL all render correctly for PostgreSQL:
|
|
159
|
+
|
|
160
|
+
```scala
|
|
161
|
+
import zio.blocks.sql._
|
|
162
|
+
|
|
163
|
+
val pgDataSource: javax.sql.DataSource = ???
|
|
164
|
+
val transactor: JdbcTransactor = JdbcTransactor.postgres(pgDataSource)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### `JdbcTransactor.sqlite` — SQLite convenience factory
|
|
168
|
+
|
|
169
|
+
`JdbcTransactor.sqlite` is shorthand for `fromDataSource(dataSource, SqlDialect.SQLite)`. Use it for SQLite databases where the `DataSource` is already available:
|
|
170
|
+
|
|
171
|
+
```scala
|
|
172
|
+
object JdbcTransactor {
|
|
173
|
+
def sqlite(dataSource: javax.sql.DataSource): JdbcTransactor
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
This factory is useful when you wire the `DataSource` through a dependency-injection layer and want to keep dialect selection close to the data source definition rather than at each call site:
|
|
178
|
+
|
|
179
|
+
```scala
|
|
180
|
+
import zio.blocks.sql._
|
|
181
|
+
|
|
182
|
+
val sqDataSource: javax.sql.DataSource = ???
|
|
183
|
+
val transactor: JdbcTransactor = JdbcTransactor.sqlite(sqDataSource)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Core Operations
|
|
187
|
+
|
|
188
|
+
`Transactor` exposes exactly two public methods. Together they cover the two modes of database access: non-transactional connections for reads and transactional connections for writes.
|
|
189
|
+
|
|
190
|
+
### Connection Management
|
|
191
|
+
|
|
192
|
+
`Transactor#connect` is the lightweight entry point for database access without a transaction. It provides a `DbCon` context, which is the implicit argument required by every `Frag` extension method and every `Repo` CRUD operation.
|
|
193
|
+
|
|
194
|
+
It acquires a connection from the underlying factory, wraps it in a `JdbcConnection`, synthesizes a `DbCon` given value, and executes the provided body. The connection is closed in a `finally` block, so it is released whether the body returns normally or throws:
|
|
195
|
+
|
|
196
|
+
```scala
|
|
197
|
+
trait Transactor {
|
|
198
|
+
def connect[A](f: DbCon ?=> A): A
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Because `DbTx extends DbCon`, a block that compiles under `connect` will also compile under `transact` — so we can promote non-transactional code to transactional scope without changing the body. The following example runs a read query inside `connect` without incurring transaction overhead:
|
|
203
|
+
|
|
204
|
+
```scala
|
|
205
|
+
import zio.blocks.sql._
|
|
206
|
+
import zio.blocks.schema.Schema
|
|
207
|
+
|
|
208
|
+
case class User(id: Int, name: String, email: String)
|
|
209
|
+
object User { implicit val schema: Schema[User] = Schema.derived }
|
|
210
|
+
|
|
211
|
+
implicit val userCodec: DbCodec[User] = User.schema.deriving(DbCodecDeriver).derive
|
|
212
|
+
|
|
213
|
+
val repo = Repo.derived[User, Int]("users", "id", _.id)
|
|
214
|
+
val transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
215
|
+
|
|
216
|
+
// The body receives DbCon as a given — no explicit passing required
|
|
217
|
+
val users: List[User] = transactor.connect {
|
|
218
|
+
repo.all
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
:::caution
|
|
223
|
+
Inside `connect`, auto-commit is left at its default state (typically `true` for JDBC). Any DML statement executed here is committed immediately. If you need atomic multi-statement writes, use `transact` instead.
|
|
224
|
+
:::
|
|
225
|
+
|
|
226
|
+
### Transaction Management
|
|
227
|
+
|
|
228
|
+
`Transactor#transact` builds on `connect` by adding full transaction semantics: it disables auto-commit before executing the body, commits when the body returns normally, and rolls back when the body throws. The connection is always closed after commit or rollback.
|
|
229
|
+
|
|
230
|
+
It acquires a connection, sets `autoCommit = false`, synthesizes a `DbTx` given value, and runs the body. On success it calls `conn.commit()`. On any exception it calls `conn.rollback()`, adds any rollback failure as a suppressed exception, and rethrows the original. The connection is closed in the `finally` block regardless of outcome:
|
|
231
|
+
|
|
232
|
+
```scala
|
|
233
|
+
trait Transactor {
|
|
234
|
+
def transact[A](f: DbTx ?=> A): A
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Because `DbTx extends DbCon`, the `DbTx` given satisfies any `DbCon ?=>` requirement inside the block. We can mix `Repo` CRUD calls with raw `sql"..."` fragments freely:
|
|
239
|
+
|
|
240
|
+
```scala
|
|
241
|
+
import zio.blocks.sql._
|
|
242
|
+
import zio.blocks.schema.Schema
|
|
243
|
+
import zio.blocks.maybe.Maybe
|
|
244
|
+
|
|
245
|
+
case class User(id: Int, name: String, email: String)
|
|
246
|
+
object User { implicit val schema: Schema[User] = Schema.derived }
|
|
247
|
+
|
|
248
|
+
implicit val userCodec: DbCodec[User] = User.schema.deriving(DbCodecDeriver).derive
|
|
249
|
+
// userCodec: DbCodec[User] = zio.blocks.sql.DbCodecDeriver$$anon$20@72550983
|
|
250
|
+
|
|
251
|
+
val repo = Repo.derived[User, Int]("users", "id", _.id)
|
|
252
|
+
// repo: Repo[User, Int] = zio.blocks.sql.Repo$DerivedRepo@144fc885
|
|
253
|
+
val transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
254
|
+
// transactor: JdbcTransactor = zio.blocks.sql.JdbcTransactor@12877e41
|
|
255
|
+
|
|
256
|
+
transactor.transact {
|
|
257
|
+
repo.table.createTable(summon[DbTx].dialect).update
|
|
258
|
+
repo.insert(User(1, "Alice", "alice@example.com"))
|
|
259
|
+
|
|
260
|
+
// If this throws, the INSERT above is rolled back
|
|
261
|
+
val existing: Maybe[User] = repo.find(1)
|
|
262
|
+
existing
|
|
263
|
+
}
|
|
264
|
+
// res7: Maybe[User] = User(
|
|
265
|
+
// id = 1,
|
|
266
|
+
// name = "Alice",
|
|
267
|
+
// email = "alice@example.com"
|
|
268
|
+
// )
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
:::caution
|
|
272
|
+
If `commit` itself throws after a successful body, the transaction is rolled back and the commit exception propagates. The body's return value is discarded in that case.
|
|
273
|
+
:::
|
|
274
|
+
|
|
275
|
+
## JdbcTransactor
|
|
276
|
+
|
|
277
|
+
`JdbcTransactor` is the only concrete implementation of `Transactor` in the `sql` module. It holds three constructor parameters: `connectionFactory`, `dialect`, and `sqlLogger`. The factory methods on its companion object cover the most common configurations; the primary constructor is available for custom setups such as test harnesses that inject a pre-existing connection:
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
Transactor (trait — shared, cross-platform)
|
|
281
|
+
└── JdbcTransactor (class — JVM only, JDBC-backed)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
You can subclass `JdbcTransactor` to override `connect` or `transact` — for example, to reuse a single shared connection across multiple calls in a test suite without closing it between calls, as the `TransactorSpec` test helper does internally.
|
|
285
|
+
|
|
286
|
+
## Transactor vs TransactorZIO
|
|
287
|
+
|
|
288
|
+
`Transactor` and `JdbcTransactor` are synchronous and suitable for code that does not use the ZIO effect system. `TransactorZIO`, provided by the separate `zio-blocks-sql-zio` artifact, wraps a `JdbcTransactor` and exposes two execution models for ZIO applications:
|
|
289
|
+
|
|
290
|
+
| Aspect | `Transactor` / `JdbcTransactor` | `TransactorZIO` (sql-zio module) |
|
|
291
|
+
|--------------------------|------------------------------------------------|---------------------------------------------------------------------------------------------------|
|
|
292
|
+
| **Return type** | `A` (synchronous, blocking) | `Task[A]` (or `ZIO[R, E \| Throwable, A]` for ZIO bodies) |
|
|
293
|
+
| **Thread model** | Blocks the calling thread | `connect`/`transact` run on the blocking thread pool via `ZIO.attemptBlocking` |
|
|
294
|
+
| **Interruption safety** | None — the body runs to completion | `connectZIO`/`transactZIO` use `ZIO.acquireRelease`; connection closes even on fiber interruption |
|
|
295
|
+
| **ZIO dependency** | None — zero ZIO dependency | Requires ZIO runtime |
|
|
296
|
+
| **Dependency injection** | Manual construction | `TransactorZIO.layer` provides a `ZLayer` |
|
|
297
|
+
| **When to use** | Synchronous imperative code, scripts, or tests | ZIO-based applications where effects compose across the entire call stack |
|
|
298
|
+
|
|
299
|
+
The following diagram shows how the two types relate:
|
|
300
|
+
|
|
301
|
+
```
|
|
302
|
+
JdbcTransactor ──────────── wraps ───────────► TransactorZIO
|
|
303
|
+
│ │
|
|
304
|
+
│ connect { DbCon ?=> A }: A │ connect { DbCon ?=> A }: Task[A]
|
|
305
|
+
│ transact { DbTx ?=> A }: A │ transact { DbTx ?=> A }: Task[A]
|
|
306
|
+
│ │ connectZIO { DbCon ?=> ZIO[R,E,A] }
|
|
307
|
+
│ │ transactZIO { DbTx ?=> ZIO[R,E,A] }
|
|
308
|
+
│ │
|
|
309
|
+
└── (no ZIO dep) ────────────────────────── (ZIO runtime required) ──┘
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Choose `JdbcTransactor` when the codebase is synchronous or when ZIO is not on the classpath. Choose `TransactorZIO` when you want connections bracketed by ZIO's `acquireRelease` so they survive fiber interruption, or when you need `ZLayer`-based dependency injection.
|
|
313
|
+
|
|
314
|
+
## Advanced Usage: Composing the Context
|
|
315
|
+
|
|
316
|
+
`DbCon` carries three members — `connection`, `dialect`, and `logger` — and is passed implicitly through every SQL operation. This means application-level helper methods can declare `(using DbCon)` and they are automatically satisfied anywhere inside `connect` or `transact` with no explicit argument passing. The same pattern composes across multiple layers:
|
|
317
|
+
|
|
318
|
+
```scala
|
|
319
|
+
import zio.blocks.sql._
|
|
320
|
+
import zio.blocks.schema.Schema
|
|
321
|
+
|
|
322
|
+
case class User(id: Int, name: String, email: String)
|
|
323
|
+
object User { implicit val schema: Schema[User] = Schema.derived }
|
|
324
|
+
|
|
325
|
+
implicit val userCodec: DbCodec[User] = User.schema.deriving(DbCodecDeriver).derive
|
|
326
|
+
// userCodec: DbCodec[User] = zio.blocks.sql.DbCodecDeriver$$anon$20@460334f6
|
|
327
|
+
|
|
328
|
+
val repo = Repo.derived[User, Int]("users", "id", _.id)
|
|
329
|
+
// repo: Repo[User, Int] = zio.blocks.sql.Repo$DerivedRepo@25787e5
|
|
330
|
+
val transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
331
|
+
// transactor: JdbcTransactor = zio.blocks.sql.JdbcTransactor@7fefa5d1
|
|
332
|
+
|
|
333
|
+
// Helper that composes two Repo calls — requires only DbCon, not Transactor
|
|
334
|
+
def upsertUser(user: User)(using DbCon): Unit = {
|
|
335
|
+
if (repo.exists(user.id)) repo.update(user)
|
|
336
|
+
else repo.insert(user)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// The Transactor provides the DbCon; upsertUser picks it up automatically
|
|
340
|
+
transactor.transact {
|
|
341
|
+
repo.table.createTable(summon[DbTx].dialect).update
|
|
342
|
+
upsertUser(User(1, "Alice", "alice@example.com"))
|
|
343
|
+
upsertUser(User(1, "Alice Smith", "alice.smith@example.com"))
|
|
344
|
+
repo.find(1)
|
|
345
|
+
}
|
|
346
|
+
// res9: Maybe[User] = User(
|
|
347
|
+
// id = 1,
|
|
348
|
+
// name = "Alice Smith",
|
|
349
|
+
// email = "alice.smith@example.com"
|
|
350
|
+
// )
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Because `DbTx extends DbCon`, `upsertUser` works unchanged inside both `connect` and `transact`. This lets us design helper functions against the minimal context they need and promote them to transactional scope at the call site without modifying their signatures.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: sql-zio
|
|
3
|
+
title: "SQL — ZIO Integration"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`zio-blocks-sql-zio` is the ZIO adapter for `zio-blocks-sql`. It wraps the
|
|
7
|
+
core JDBC transactor so ZIO applications can use the same SQL layer without
|
|
8
|
+
changing the underlying database API.
|
|
9
|
+
|
|
10
|
+
This guide covers two integration styles: `ZLayer`-based dependency injection
|
|
11
|
+
for the plain synchronous `Transactor` (via `JdbcTransactor.postgresLayer` /
|
|
12
|
+
`sqliteLayer`), and the `TransactorZIO` wrapper class for ZIO-native blocking
|
|
13
|
+
and effect-aware methods. For a full method-by-method reference on
|
|
14
|
+
`TransactorZIO`, see [TransactorZIO](./sql/transactor-zio.md).
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```scala
|
|
19
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-sql-zio" % "0.0.51"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
Create a `Transactor` layer from a JDBC `DataSource` and pick the dialect-specific helper that matches your database:
|
|
25
|
+
|
|
26
|
+
```scala
|
|
27
|
+
import javax.sql.DataSource
|
|
28
|
+
import zio.*
|
|
29
|
+
import zio.blocks.maybe.Maybe
|
|
30
|
+
import zio.blocks.sql.*
|
|
31
|
+
import zio.blocks.sql.zio.*
|
|
32
|
+
|
|
33
|
+
val dataSource: DataSource = ???
|
|
34
|
+
val transactorLayer: ZLayer[Any, Nothing, Transactor] =
|
|
35
|
+
ZLayer.succeed(dataSource) >>> JdbcTransactor.postgresLayer
|
|
36
|
+
|
|
37
|
+
val program: ZIO[Transactor, Throwable, Maybe[Int]] =
|
|
38
|
+
ZIO.serviceWith[Transactor] { transactor =>
|
|
39
|
+
transactor.connect {
|
|
40
|
+
sql"SELECT 1".queryOne[Int]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Use `JdbcTransactor.sqliteLayer` for SQLite databases.
|
|
46
|
+
|
|
47
|
+
## TransactorZIO
|
|
48
|
+
|
|
49
|
+
`TransactorZIO` wraps the synchronous transactor and exposes ZIO-friendly
|
|
50
|
+
operations.
|
|
51
|
+
|
|
52
|
+
```scala
|
|
53
|
+
import zio._
|
|
54
|
+
import zio.blocks.sql._
|
|
55
|
+
import zio.blocks.sql.zio._
|
|
56
|
+
|
|
57
|
+
val transactor = TransactorZIO.fromUrl(
|
|
58
|
+
"jdbc:postgresql://localhost/mydb",
|
|
59
|
+
"alice", "secret",
|
|
60
|
+
SqlDialect.PostgreSQL
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
For production use, prefer `TransactorZIO.fromDataSource(...)` so connection
|
|
65
|
+
pooling is handled outside the library.
|
|
66
|
+
|
|
67
|
+
You can also create a transactor from a `DataSource` when you already have one:
|
|
68
|
+
|
|
69
|
+
```scala
|
|
70
|
+
val transactor = TransactorZIO.fromDataSource(dataSource, SqlDialect.PostgreSQL)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Blocking Wrappers
|
|
74
|
+
|
|
75
|
+
`connect` and `transact` run synchronous code on ZIO's blocking thread pool and
|
|
76
|
+
return `Task`.
|
|
77
|
+
|
|
78
|
+
```scala
|
|
79
|
+
val users: Task[List[User]] = transactor.connect:
|
|
80
|
+
userRepo.all
|
|
81
|
+
|
|
82
|
+
val result: Task[User] = transactor.transact:
|
|
83
|
+
userRepo.insertReturning(newUser)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Effect-Aware Methods
|
|
87
|
+
|
|
88
|
+
`connectZIO` and `transactZIO` let the body return a `ZIO` directly.
|
|
89
|
+
|
|
90
|
+
```scala
|
|
91
|
+
val program: ZIO[Any, Throwable, User] =
|
|
92
|
+
transactor.transactZIO:
|
|
93
|
+
for
|
|
94
|
+
_ <- ZIO.attemptBlocking(userRepo.insert(newUser))
|
|
95
|
+
user <- ZIO.attemptBlocking(userRepo.find(newUser.id))
|
|
96
|
+
yield user.get
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## ZLayer
|
|
100
|
+
|
|
101
|
+
Use `ZLayer` when you want to provide `TransactorZIO` through dependency
|
|
102
|
+
injection:
|
|
103
|
+
|
|
104
|
+
```scala
|
|
105
|
+
val transactorLayer: ZLayer[Any, Nothing, TransactorZIO] =
|
|
106
|
+
TransactorZIO.layer("jdbc:postgresql://localhost/mydb", SqlDialect.PostgreSQL)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Thread Safety
|
|
110
|
+
|
|
111
|
+
`TransactorZIO` is safe to share. It creates a new connection per
|
|
112
|
+
`connect` / `transact` invocation.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: concurrent-operators
|
|
3
|
+
title: "Concurrent Operators"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
ZIO Blocks Streams ships **three** concurrent operators that fan work across virtual threads while preserving the typed-error, synchronous, pull-based programming model. The calling thread still receives `Either[E, Z]` — no effect system is required.
|
|
7
|
+
|
|
8
|
+
| Operator | Purpose |
|
|
9
|
+
|---|---|
|
|
10
|
+
| `Stream#mapPar(n)(f)` | Apply `f` to each element on up to `n` worker threads. Output is **unordered** (arrival order, not input order). |
|
|
11
|
+
| `Stream.mergeAll(n)(streams)` | Drain up to `n` inner streams concurrently; interleave their elements as they arrive. |
|
|
12
|
+
| `Stream#flatMapPar(n)(f)` | Per element, produce a sub-stream via `f`; drain up to `n` sub-streams concurrently. |
|
|
13
|
+
|
|
14
|
+
All three operators are **JVM-only**. On Scala.js they degrade to sequential equivalents (`map`, `flatten`, `flatMap`).
|
|
15
|
+
|
|
16
|
+
## Semantics
|
|
17
|
+
|
|
18
|
+
**Output order.** Concurrent output is **unordered** with respect to input position. Elements arrive as workers complete, not in input order. If you need input order, use sequential `map` / `flatMap`.
|
|
19
|
+
|
|
20
|
+
**Error propagation.** The first typed error from any worker or inner stream terminates all concurrent work and surfaces as `Left(e)` from the terminal operation. Defects (unexpected exceptions) propagate as thrown exceptions, same as sequential operators.
|
|
21
|
+
|
|
22
|
+
**Resource safety.** All worker threads and ring-buffer queues are cleaned up deterministically when the consumer closes the reader, the stream errors, or the scope finalizes.
|
|
23
|
+
|
|
24
|
+
**Primitive specialization.** Readers produced by concurrent operators preserve primitive specialization — `Int`, `Long`, `Float`, and `Double` streams use specialized lock-free queues internally, avoiding boxing in the concurrent handoff between threads.
|
|
25
|
+
|
|
26
|
+
## Buffer sizing
|
|
27
|
+
|
|
28
|
+
Concurrent operators use internal ring-buffer queues (default size **64**). Override with `Stream.bufferSize(n) { ... }` where `n` is a positive power of two:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
Stream.bufferSize(256) {
|
|
32
|
+
Stream.range(0, 1_000_000).mapPar(8)(heavyComputation)
|
|
33
|
+
}.runCollect
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Larger buffers help when producers are bursty; smaller buffers reduce memory when many concurrent streams are active. The default is fine for most workloads.
|
|
37
|
+
|
|
38
|
+
`Pipeline.buffer(n)` inserts a buffer of `n` elements between upstream and downstream (async handoff on JVM, sync on JS).
|
|
39
|
+
|
|
40
|
+
## Examples
|
|
41
|
+
|
|
42
|
+
### `mapPar`
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
// Apply an expensive function using 8 virtual threads.
|
|
46
|
+
// Output order varies between runs.
|
|
47
|
+
val result = Stream.range(0, 1000)
|
|
48
|
+
.mapPar(8)(n => { Thread.sleep(1); n * 2 })
|
|
49
|
+
.runCollect
|
|
50
|
+
// result: Right(Chunk(...)) -- all 1000 elements, but not in 0,2,4,... order
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### `mergeAll`
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
// Drain 10 streams concurrently, up to 4 at a time.
|
|
57
|
+
val streams = Stream.fromIterable(
|
|
58
|
+
(0 until 10).map(i => Stream.range(i * 100, (i + 1) * 100))
|
|
59
|
+
)
|
|
60
|
+
val merged = Stream.mergeAll(4)(streams).runFold(0L)(_ + _)
|
|
61
|
+
// merged: Right(499500) -- all elements consumed, order interleaved
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### `flatMapPar`
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
// Each element spawns a sub-stream; up to 8 drained concurrently.
|
|
68
|
+
val flat = Stream.range(0, 50)
|
|
69
|
+
.flatMapPar(8)(i => Stream.range(i * 20, (i + 1) * 20))
|
|
70
|
+
.runFold(0L)(_ + _)
|
|
71
|
+
// flat: Right(499500)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Error behaviour
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
// Typed error in a worker terminates all workers
|
|
78
|
+
val err1 = Stream.range(0, 1000)
|
|
79
|
+
.flatMap(n => if (n == 500) Stream.fail("bad element") else Stream.succeed(n))
|
|
80
|
+
.mapPar(4)(identity)
|
|
81
|
+
.runCollect
|
|
82
|
+
// err1: Left("bad element")
|
|
83
|
+
|
|
84
|
+
// Error in one inner stream terminates mergeAll
|
|
85
|
+
val err2 = Stream.mergeAll(4)(Stream.fromIterable(
|
|
86
|
+
List(Stream.range(0, 100), Stream.fail("inner error"), Stream.range(200, 300))
|
|
87
|
+
)).runCollect
|
|
88
|
+
// err2: Left("inner error")
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Guidelines
|
|
92
|
+
|
|
93
|
+
- **Use `mapPar(n)(f)` for expensive per-element work** — network calls, CPU-bound computation, blocking I/O. Do not use it for trivially cheap functions (e.g. `_ + 1`); the thread-handoff overhead exceeds the parallelism benefit.
|
|
94
|
+
- **Use `mergeAll(n)(streams)` for concurrent fan-in** — draining multiple independent sources (files, connections, partitions) simultaneously. Use `flatMapPar(n)(f)` when each input element produces a sub-stream to drain concurrently.
|
|
95
|
+
- **Concurrent output is unordered.** If you need sorted results, apply `.runCollect.map(_.sorted)` or accumulate into a structure that handles ordering. If you need input-order preservation, use sequential `map` / `flatMap`.
|
|
96
|
+
- **`mapPar`, `mergeAll`, and `flatMapPar` are JVM-only.** On JS they degrade to sequential equivalents.
|
|
97
|
+
|
|
98
|
+
## Comparison with other libraries
|
|
99
|
+
|
|
100
|
+
| Feature | ZB Streams | fs2 | Kyo | Ox | Pekko |
|
|
101
|
+
|---|---|---|---|---|---|
|
|
102
|
+
| Concurrent operators | `mapPar`, `mergeAll`, `flatMapPar` | `parEvalMap` | `mapParUnordered`* | `mapPar` | `mapAsync`, `flatMapMerge` |
|
|
103
|
+
| Effect system required | No | Yes (cats-effect) | Yes (Kyo) | No (virtual threads) | Yes (Akka) |
|
|
104
|
+
| Typed errors | `Either[E, Z]` | ApplicativeError | Kyo effects | Exceptions | No |
|
|
105
|
+
|
|
106
|
+
\* Kyo's `mapParUnordered` forks a fiber per element (very slow for large streams). Kyo's `collectAll` merges streams but does not parallelize pure computation within them.
|