@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,271 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: db-con
|
|
3
|
+
title: "DbCon"
|
|
4
|
+
description: "Reference for DbCon, the implicit execution context in the sql module carrying a DbConnection, SqlDialect, and SqlLogger through SQL operations."
|
|
5
|
+
keywords:
|
|
6
|
+
- "DbCon implicit context"
|
|
7
|
+
- "Transactor connection scope"
|
|
8
|
+
- "DbTx transaction scope"
|
|
9
|
+
- "SqlDialect dialect selection"
|
|
10
|
+
- "SqlLogger query observability"
|
|
11
|
+
- "sql module context threading"
|
|
12
|
+
- "DbConnection JDBC abstraction"
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
`DbCon` is the implicit execution context that threads database connection state through every SQL operation in the `sql` module. It is a plain trait with three abstract members — `connection`, `dialect`, and `logger` — and carries no type parameters:
|
|
16
|
+
|
|
17
|
+
```scala
|
|
18
|
+
trait DbCon {
|
|
19
|
+
def connection: DbConnection
|
|
20
|
+
def dialect: SqlDialect
|
|
21
|
+
def logger: SqlLogger
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Application code never constructs a `DbCon` directly. Instead, `Transactor#connect` and `Transactor#transact` each create one internally and supply it as a Scala 3 context parameter (`?=>`) to the block they receive.
|
|
26
|
+
|
|
27
|
+
All `Frag` execution methods (`query`, `queryOne`, `queryLimit`, `update`, `updateReturningKeys`) and all `Repo` CRUD methods (`all`, `find`, `insert`, `update`, `delete`, and friends) require an implicit `DbCon` in scope — so any code that runs inside a `Transactor#connect` or `Transactor#transact` block automatically has everything it needs to execute SQL.
|
|
28
|
+
|
|
29
|
+
Key properties:
|
|
30
|
+
- **Implicit context type** — carries the active database session without explicit parameter threading.
|
|
31
|
+
- **Three concrete members** — `connection` (the active `DbConnection`), `dialect` (the `SqlDialect`), and `logger` (the `SqlLogger`).
|
|
32
|
+
- **Extended by `DbTx`** — `DbTx` is a subtype of `DbCon` that marks transactional scope; any method that accepts `DbCon` also accepts `DbTx`.
|
|
33
|
+
- **Lifetime managed by `Transactor`** — the connection is closed when the enclosing `connect` or `transact` block returns, whether it succeeds or throws.
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
The following example shows how `DbCon` is obtained from a `Transactor`, how the context is propagated into helper methods, and how the three members can be accessed when needed:
|
|
38
|
+
|
|
39
|
+
```scala
|
|
40
|
+
import zio.blocks.sql._
|
|
41
|
+
import zio.blocks.schema.Schema
|
|
42
|
+
|
|
43
|
+
case class User(id: Int, name: String)
|
|
44
|
+
object User {
|
|
45
|
+
implicit val schema: Schema[User] = Schema.derived
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
val tx: Transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
49
|
+
// tx: Transactor = zio.blocks.sql.JdbcTransactor@4c1ffa3c
|
|
50
|
+
|
|
51
|
+
// DbCon is supplied automatically by connect — no explicit argument needed
|
|
52
|
+
tx.connect {
|
|
53
|
+
// Frag execution picks up the given DbCon implicitly
|
|
54
|
+
Frag.literal("CREATE TABLE users (id INTEGER NOT NULL, name TEXT NOT NULL)").update
|
|
55
|
+
sql"INSERT INTO users (id, name) VALUES (${1}, ${"Alice"})".update
|
|
56
|
+
val users: List[User] = sql"SELECT id, name FROM users".query[User]
|
|
57
|
+
// Access the context members directly when needed
|
|
58
|
+
val dialectName: String = summon[DbCon].dialect.toString
|
|
59
|
+
(users, dialectName)
|
|
60
|
+
}
|
|
61
|
+
// res1: Tuple2[List[User], String] = (
|
|
62
|
+
// List(User(id = 1, name = "Alice")),
|
|
63
|
+
// "SQLite"
|
|
64
|
+
// )
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Entry Points
|
|
68
|
+
|
|
69
|
+
`DbCon` is never instantiated by application code. A `Transactor` creates and supplies the instance automatically. There are two entry points depending on whether transactional behaviour is needed.
|
|
70
|
+
|
|
71
|
+
### `Transactor#connect` — Non-transactional connection scope
|
|
72
|
+
|
|
73
|
+
`Transactor#connect` acquires a JDBC connection from the underlying source, wraps it in a `DbCon`, and calls the provided context function. The connection is closed when the block returns, whether it completes normally or throws. Auto-commit remains at the driver default (enabled for most JDBC drivers), so each statement issued inside the block commits independently.
|
|
74
|
+
|
|
75
|
+
The signature from `Transactor` is:
|
|
76
|
+
|
|
77
|
+
```scala
|
|
78
|
+
trait Transactor {
|
|
79
|
+
def connect[A](f: DbCon ?=> A): A
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The following example shows a non-transactional read that uses the supplied `DbCon` to execute a query:
|
|
84
|
+
|
|
85
|
+
```scala
|
|
86
|
+
import zio.blocks.sql._
|
|
87
|
+
|
|
88
|
+
val tx: Transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
89
|
+
|
|
90
|
+
// The `DbCon` binding is supplied by `connect` — no manual construction
|
|
91
|
+
val names: List[String] = tx.connect {
|
|
92
|
+
sql"SELECT name FROM users ORDER BY name".query[String]
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
:::caution
|
|
97
|
+
Do not capture the `DbCon` value or the `DbConnection` it contains and use them outside the `connect` block. The connection is closed when the block returns, and any statement prepared or executed on it afterward will throw.
|
|
98
|
+
:::
|
|
99
|
+
|
|
100
|
+
### `Transactor#transact` — Transactional connection scope
|
|
101
|
+
|
|
102
|
+
`Transactor#transact` acquires a connection, disables auto-commit, and supplies a `DbTx` context — a subtype of `DbCon` — to the block. On normal return the transaction commits; on any thrown exception it rolls back. The connection is closed after commit or rollback. Because `DbTx extends DbCon`, all `Frag` and `Repo` methods that accept `DbCon` work unchanged inside `transact`.
|
|
103
|
+
|
|
104
|
+
The signature from `Transactor` is:
|
|
105
|
+
|
|
106
|
+
```scala
|
|
107
|
+
trait Transactor {
|
|
108
|
+
def transact[A](f: DbTx ?=> A): A
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The following example shows a transactional write that inserts two rows atomically — if the second insert throws, the first is rolled back:
|
|
113
|
+
|
|
114
|
+
```scala
|
|
115
|
+
import zio.blocks.sql._
|
|
116
|
+
|
|
117
|
+
val tx: Transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
118
|
+
|
|
119
|
+
tx.transact {
|
|
120
|
+
// Both inserts commit together; an exception rolls back both
|
|
121
|
+
sql"INSERT INTO users (id, name) VALUES (${1}, ${"Alice"})".update
|
|
122
|
+
sql"INSERT INTO users (id, name) VALUES (${2}, ${"Bob"})".update
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Core Operations
|
|
127
|
+
|
|
128
|
+
`DbCon` exposes three read-only members. They are rarely accessed directly in application code — the `Transactor` configures them at construction time and `Frag` / `Repo` consume them implicitly — but they become useful when integrating with lower-level JDBC abstractions, logging frameworks, or custom dialect rendering.
|
|
129
|
+
|
|
130
|
+
### Context Access
|
|
131
|
+
|
|
132
|
+
The three context-access members return the `DbConnection`, `SqlDialect`, and `SqlLogger` held by the current `DbCon` instance.
|
|
133
|
+
|
|
134
|
+
#### `DbCon#connection` — Underlying JDBC-abstraction connection
|
|
135
|
+
|
|
136
|
+
`DbCon#connection` returns the `DbConnection` wrapping the active JDBC `java.sql.Connection`. `DbConnection` exposes `prepareStatement`, `prepareStatementReturningKeys`, transaction-control methods, and `close`. It is consumed internally by every `Frag` and `Repo` operation that executes a statement. Access it directly only when you need to drop below the `Frag` layer to a raw prepared statement.
|
|
137
|
+
|
|
138
|
+
```scala
|
|
139
|
+
trait DbCon {
|
|
140
|
+
def connection: DbConnection
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The following example shows how to retrieve the `DbConnection` and use it to execute a raw prepared statement — useful for operations that the `Frag` API does not cover directly:
|
|
145
|
+
|
|
146
|
+
```scala
|
|
147
|
+
import zio.blocks.sql._
|
|
148
|
+
|
|
149
|
+
val tx: Transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
150
|
+
|
|
151
|
+
tx.connect {
|
|
152
|
+
val conn: DbConnection = summon[DbCon].connection
|
|
153
|
+
val ps = conn.prepareStatement("SELECT COUNT(*) FROM users")
|
|
154
|
+
try {
|
|
155
|
+
val rs = ps.executeQuery()
|
|
156
|
+
try { rs.next(); rs.reader.getInt(1) }
|
|
157
|
+
finally rs.close()
|
|
158
|
+
} finally ps.close()
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
:::caution
|
|
163
|
+
Never call `connection.close()` manually. The `Transactor` closes the connection when the enclosing `connect` or `transact` block finishes, whether it returns normally or throws. Closing the connection early will cause all subsequent statements in the block to fail.
|
|
164
|
+
:::
|
|
165
|
+
|
|
166
|
+
#### `DbCon#dialect` — SQL dialect for fragment rendering
|
|
167
|
+
|
|
168
|
+
`DbCon#dialect` returns the `SqlDialect` used to render `Frag` values to parameterized SQL strings. Every `Frag` execution method calls `frag.sql(dialect)` internally to produce the driver-specific SQL before binding parameters. The dialect also governs how DDL type names are spelled when generating `CREATE TABLE` statements from `Table#createTable`.
|
|
169
|
+
|
|
170
|
+
```scala
|
|
171
|
+
trait DbCon {
|
|
172
|
+
def dialect: SqlDialect
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The following example shows reading the dialect from a context to render a `Frag` explicitly — for instance, to log the SQL before execution:
|
|
177
|
+
|
|
178
|
+
```scala
|
|
179
|
+
import zio.blocks.sql._
|
|
180
|
+
|
|
181
|
+
val tx: Transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
182
|
+
// tx: Transactor = zio.blocks.sql.JdbcTransactor@606f8c9a
|
|
183
|
+
|
|
184
|
+
tx.connect {
|
|
185
|
+
val frag = sql"SELECT id FROM users WHERE id = ${42}"
|
|
186
|
+
frag.sql(summon[DbCon].dialect)
|
|
187
|
+
}
|
|
188
|
+
// res6: String = "SELECT id FROM users WHERE id = ?"
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### `DbCon#logger` — Query execution logger
|
|
192
|
+
|
|
193
|
+
`DbCon#logger` returns the `SqlLogger` that the `Transactor` was configured with. After each successful statement `SqlLogger#onSuccess` receives the rendered SQL, the parameter list, the execution duration, and the affected-row count. After a failed statement `SqlLogger#onError` receives the same information plus the thrown exception. The default implementation — `SqlLogger.noop` — discards all events; replace it with a custom `SqlLogger` to integrate with your preferred logging framework.
|
|
194
|
+
|
|
195
|
+
```scala
|
|
196
|
+
trait DbCon {
|
|
197
|
+
def logger: SqlLogger
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The following example shows passing a logging `SqlLogger` to `JdbcTransactor` so that every query executed inside `connect` or `transact` is recorded:
|
|
202
|
+
|
|
203
|
+
```scala
|
|
204
|
+
import zio.blocks.sql._
|
|
205
|
+
|
|
206
|
+
val loggingLogger: SqlLogger = new SqlLogger {
|
|
207
|
+
def onSuccess(event: SqlLogger.SuccessEvent): Unit =
|
|
208
|
+
println(s"OK [${event.duration.toMillis} ms, ${event.rowCount} rows]: ${event.sql}")
|
|
209
|
+
def onError(event: SqlLogger.ErrorEvent): Unit =
|
|
210
|
+
println(s"ERR [${event.duration.toMillis} ms]: ${event.sql} — ${event.error.getMessage}")
|
|
211
|
+
}
|
|
212
|
+
// loggingLogger: SqlLogger = repl.MdocSession$MdocApp7$$anon$9@1d96b35a
|
|
213
|
+
|
|
214
|
+
val tx: Transactor =
|
|
215
|
+
new JdbcTransactor(
|
|
216
|
+
() => java.sql.DriverManager.getConnection("jdbc:sqlite::memory:"),
|
|
217
|
+
SqlDialect.SQLite,
|
|
218
|
+
loggingLogger
|
|
219
|
+
)
|
|
220
|
+
// tx: Transactor = zio.blocks.sql.JdbcTransactor@7abee0ec
|
|
221
|
+
|
|
222
|
+
tx.connect {
|
|
223
|
+
// Every Frag execution notifies loggingLogger automatically
|
|
224
|
+
Frag.literal("SELECT 1").query[Int]
|
|
225
|
+
}
|
|
226
|
+
// OK [0 ms, 1 rows]: SELECT 1
|
|
227
|
+
// res8: List[Int] = List(1)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Transactional Scope — `DbTx`
|
|
231
|
+
|
|
232
|
+
`DbTx` is the only subtype of `DbCon`. It is a marker trait — it adds no new members — whose sole purpose is to distinguish code that runs inside `Transactor#transact` from code that runs inside `Transactor#connect` at the type level:
|
|
233
|
+
|
|
234
|
+
```scala
|
|
235
|
+
trait DbTx extends DbCon
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Because `DbTx` has no additional members, it exists purely to make the transactional / non-transactional boundary visible in method signatures. A method that requires `DbTx` cannot be called from a `connect` block, but a method that requires `DbCon` can be called from either. This asymmetry is enforced by the compiler: `DbTx <: DbCon` so `DbTx` satisfies a `DbCon` requirement, but a plain `DbCon` does not satisfy a `DbTx` requirement.
|
|
239
|
+
|
|
240
|
+
The following example illustrates how to write a helper method that is restricted to transactional scope:
|
|
241
|
+
|
|
242
|
+
```scala
|
|
243
|
+
import zio.blocks.sql._
|
|
244
|
+
|
|
245
|
+
// This helper compiles only inside a `transact` block, never inside `connect`
|
|
246
|
+
def insertUser(id: Int, name: String)(using DbTx): Unit = {
|
|
247
|
+
sql"INSERT INTO users (id, name) VALUES ($id, $name)".update
|
|
248
|
+
()
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
val tx: Transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
252
|
+
|
|
253
|
+
tx.transact {
|
|
254
|
+
insertUser(1, "Alice") // OK — DbTx satisfies the `using DbTx` requirement
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// tx.connect {
|
|
258
|
+
// insertUser(1, "Alice") // Compile error — DbCon does not satisfy `using DbTx`
|
|
259
|
+
// }
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
When a helper only reads data and should work in both contexts, declare it with `using DbCon`:
|
|
263
|
+
|
|
264
|
+
```scala
|
|
265
|
+
import zio.blocks.sql._
|
|
266
|
+
import zio.blocks.maybe.Maybe
|
|
267
|
+
|
|
268
|
+
// Usable inside both `connect` and `transact` blocks
|
|
269
|
+
def findUser(id: Int)(using DbCon): Maybe[String] =
|
|
270
|
+
sql"SELECT name FROM users WHERE id = $id".queryOne[String]
|
|
271
|
+
```
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: db-connection
|
|
3
|
+
title: "DbConnection"
|
|
4
|
+
description: "The JDBC Connection abstraction in the sql module"
|
|
5
|
+
keywords:
|
|
6
|
+
- "DbConnection JDBC abstraction"
|
|
7
|
+
- "prepareStatement SQL"
|
|
8
|
+
- "DbPreparedStatement executeQuery"
|
|
9
|
+
- "DbResultSet result reading"
|
|
10
|
+
- "transaction control commit rollback"
|
|
11
|
+
- "AutoCloseable connection lifecycle"
|
|
12
|
+
- "Transactor connection management"
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
`DbConnection` is a `trait` that extends `AutoCloseable` and abstracts over a JDBC `java.sql.Connection`. It exposes the minimum surface needed to prepare statements, control transaction boundaries, and query connection state.
|
|
16
|
+
|
|
17
|
+
It is part of a three-tier abstraction:
|
|
18
|
+
- `DbConnection` manages the connection itself
|
|
19
|
+
- `DbPreparedStatement` executes statements with parameters
|
|
20
|
+
- `DbResultSet` reads result rows
|
|
21
|
+
|
|
22
|
+
## Core API
|
|
23
|
+
|
|
24
|
+
Here is the core API which `DbConnection` exposes:
|
|
25
|
+
|
|
26
|
+
```scala
|
|
27
|
+
trait DbConnection extends AutoCloseable {
|
|
28
|
+
// Statement preparation
|
|
29
|
+
def prepareStatement(sql: String): DbPreparedStatement
|
|
30
|
+
def prepareStatementReturningKeys(sql: String): DbPreparedStatement
|
|
31
|
+
|
|
32
|
+
// Transaction control
|
|
33
|
+
def setAutoCommit(autoCommit: Boolean): Unit
|
|
34
|
+
def getAutoCommit: Boolean
|
|
35
|
+
def commit(): Unit
|
|
36
|
+
def rollback(): Unit
|
|
37
|
+
|
|
38
|
+
// Connection state
|
|
39
|
+
def isClosed: Boolean
|
|
40
|
+
def close(): Unit
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Creating Instances
|
|
45
|
+
|
|
46
|
+
`DbConnection` has no public constructor. Application code never instantiates it. `Transactor#connect` and `Transactor#transact` create the concrete `JdbcConnection` internally and make it available through `DbCon#connection` inside the block.
|
|
47
|
+
|
|
48
|
+
For testing, implement the trait directly with a mock or use a lightweight in-memory JDBC driver such as SQLite's `:memory:` database.
|
|
49
|
+
|
|
50
|
+
:::caution
|
|
51
|
+
Never call `con.close()` inside a `connect` or `transact` block. The `Transactor` closes the connection after the block returns; closing it early will cause the rest of the block to fail with a "connection already closed" JDBC error.
|
|
52
|
+
:::
|
|
53
|
+
|
|
54
|
+
## Statement Preparation
|
|
55
|
+
|
|
56
|
+
### `prepareStatement` — Prepare a SQL statement for execution
|
|
57
|
+
|
|
58
|
+
`prepareStatement(sql: String): DbPreparedStatement` asks the JDBC driver to compile and cache the SQL string and returns a `DbPreparedStatement` that can have parameters bound to it before execution. The `sql` string must use `?` placeholders for parameters.
|
|
59
|
+
|
|
60
|
+
```scala
|
|
61
|
+
import zio.blocks.sql._
|
|
62
|
+
|
|
63
|
+
val transactor: Transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
64
|
+
|
|
65
|
+
// Inside a connect block:
|
|
66
|
+
transactor.connect {
|
|
67
|
+
val con = summon[DbCon].connection
|
|
68
|
+
val stmt = con.prepareStatement("INSERT INTO tags (name) VALUES (?)")
|
|
69
|
+
stmt.paramWriter.setString(1, "scala")
|
|
70
|
+
val rowCount = stmt.executeUpdate()
|
|
71
|
+
stmt.close()
|
|
72
|
+
rowCount
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `prepareStatementReturningKeys` — Prepare an insert that returns generated keys
|
|
77
|
+
|
|
78
|
+
`prepareStatementReturningKeys(sql: String): DbPreparedStatement` prepares a statement with the JDBC `RETURN_GENERATED_KEYS` hint, enabling `executeUpdateReturningKeys` to return an auto-generated primary key. Use this for `INSERT` statements on tables with a database-generated `SERIAL` or `AUTOINCREMENT` primary key.
|
|
79
|
+
|
|
80
|
+
```scala
|
|
81
|
+
import zio.blocks.sql._
|
|
82
|
+
|
|
83
|
+
val transactor: Transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
84
|
+
|
|
85
|
+
// Inside a connect block:
|
|
86
|
+
transactor.connect {
|
|
87
|
+
val con = summon[DbCon].connection
|
|
88
|
+
val stmt = con.prepareStatementReturningKeys("INSERT INTO orders (amount) VALUES (?)")
|
|
89
|
+
stmt.paramWriter.setBigDecimal(1, new java.math.BigDecimal("99.99"))
|
|
90
|
+
val keyRs = stmt.executeUpdateReturningKeys()
|
|
91
|
+
val genKey = if (keyRs.next()) keyRs.reader.getLong(1) else -1L
|
|
92
|
+
keyRs.close()
|
|
93
|
+
stmt.close()
|
|
94
|
+
genKey
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Transaction Control
|
|
99
|
+
|
|
100
|
+
`setAutoCommit`, `getAutoCommit`, `commit`, and `rollback` manage the connection's transactional state.
|
|
101
|
+
|
|
102
|
+
`setAutoCommit(autoCommit: Boolean)` switches the connection between auto-commit and manual-commit mode. `getAutoCommit: Boolean` queries the current mode. `commit()` commits the current transaction; `rollback()` rolls it back. These methods wrap the corresponding `java.sql.Connection` calls.
|
|
103
|
+
|
|
104
|
+
`Transactor#transact` calls `setAutoCommit(false)` before the block and calls `commit()` on success or `rollback()` on exception — so within a `transact` block, you should not call these methods yourself. Use them directly only in a `connect` block where you need fine-grained control over transaction boundaries.
|
|
105
|
+
|
|
106
|
+
```scala
|
|
107
|
+
import zio.blocks.sql._
|
|
108
|
+
|
|
109
|
+
val transactor: Transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
110
|
+
|
|
111
|
+
// Manual transaction management inside a connect block (uncommon)
|
|
112
|
+
transactor.connect {
|
|
113
|
+
val con = summon[DbCon].connection
|
|
114
|
+
con.setAutoCommit(false)
|
|
115
|
+
try {
|
|
116
|
+
// ... execute statements ...
|
|
117
|
+
con.commit()
|
|
118
|
+
} catch {
|
|
119
|
+
case t: Throwable =>
|
|
120
|
+
con.rollback()
|
|
121
|
+
throw t
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Connection State
|
|
127
|
+
|
|
128
|
+
### `isClosed` — Check whether the connection is still open
|
|
129
|
+
|
|
130
|
+
`isClosed: Boolean` returns `true` if the connection has already been closed. Inside a `connect` or `transact` block the connection is always open; this method is primarily useful in test code that verifies the `Transactor` closes connections correctly.
|
|
131
|
+
|
|
132
|
+
```scala
|
|
133
|
+
import zio.blocks.sql._
|
|
134
|
+
|
|
135
|
+
val transactor: Transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
136
|
+
// transactor: Transactor = zio.blocks.sql.JdbcTransactor@7d102738
|
|
137
|
+
|
|
138
|
+
var capturedCon: DbConnection = null
|
|
139
|
+
// capturedCon: DbConnection = null
|
|
140
|
+
|
|
141
|
+
transactor.connect {
|
|
142
|
+
capturedCon = summon[DbCon].connection
|
|
143
|
+
capturedCon.isClosed // still open inside the block
|
|
144
|
+
}
|
|
145
|
+
// res4: Boolean = false
|
|
146
|
+
|
|
147
|
+
capturedCon.isClosed // closed after the block returned
|
|
148
|
+
// res5: Boolean = true
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### `close` — Close the connection
|
|
152
|
+
|
|
153
|
+
`close(): Unit` closes the connection and releases the underlying JDBC resources. Normally the `Transactor` calls this automatically after the `connect` or `transact` block completes. Calling it manually inside a block will cause subsequent operations to fail with a "connection already closed" error.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: db-param-writer
|
|
3
|
+
title: "DbParamWriter"
|
|
4
|
+
description: "Reference for DbParamWriter, the interface for binding typed Scala values to SQL prepared-statement parameters."
|
|
5
|
+
keywords:
|
|
6
|
+
- "DbParamWriter Parameter Binding"
|
|
7
|
+
- "Typed SQL Parameters"
|
|
8
|
+
- "Prepared Statement Binding"
|
|
9
|
+
- "JDBC Parameter Binding"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
`DbParamWriter` is a trait for binding typed Scala values to the `?` placeholders in a SQL prepared statement. It provides methods to set parameters by their 1-based index (following JDBC convention).
|
|
13
|
+
|
|
14
|
+
Application code does not use `DbParamWriter` directly — the framework calls it internally when executing queries through `Frag` or `Repo`. It is the write-side counterpart to `DbResultReader`, which reads values from result sets.
|
|
15
|
+
|
|
16
|
+
## Core API
|
|
17
|
+
|
|
18
|
+
```scala
|
|
19
|
+
trait DbParamWriter {
|
|
20
|
+
// Numeric types
|
|
21
|
+
def setInt(index: Int, value: Int): Unit
|
|
22
|
+
def setLong(index: Int, value: Long): Unit
|
|
23
|
+
def setDouble(index: Int, value: Double): Unit
|
|
24
|
+
def setFloat(index: Int, value: Float): Unit
|
|
25
|
+
def setShort(index: Int, value: Short): Unit
|
|
26
|
+
def setByte(index: Int, value: Byte): Unit
|
|
27
|
+
def setBigDecimal(index: Int, value: java.math.BigDecimal): Unit
|
|
28
|
+
|
|
29
|
+
// Text and binary
|
|
30
|
+
def setString(index: Int, value: String): Unit
|
|
31
|
+
def setBytes(index: Int, value: Array[Byte]): Unit
|
|
32
|
+
|
|
33
|
+
// Date and time
|
|
34
|
+
def setLocalDate(index: Int, value: java.time.LocalDate): Unit
|
|
35
|
+
def setLocalDateTime(index: Int, value: java.time.LocalDateTime): Unit
|
|
36
|
+
def setLocalTime(index: Int, value: java.time.LocalTime): Unit
|
|
37
|
+
def setInstant(index: Int, value: java.time.Instant): Unit
|
|
38
|
+
def setDuration(index: Int, value: java.time.Duration): Unit
|
|
39
|
+
|
|
40
|
+
// Other types
|
|
41
|
+
def setBoolean(index: Int, value: Boolean): Unit
|
|
42
|
+
def setUUID(index: Int, value: java.util.UUID): Unit
|
|
43
|
+
def setArray(index: Int, elementType: String, elements: IndexedSeq[Any]): Unit
|
|
44
|
+
|
|
45
|
+
// Null handling
|
|
46
|
+
def setNull(index: Int, sqlType: Int): Unit
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
You access `DbParamWriter` through `DbPreparedStatement.paramWriter` when using raw prepared statements in a `connect` block:
|
|
53
|
+
|
|
54
|
+
```scala
|
|
55
|
+
import zio.blocks.sql._
|
|
56
|
+
|
|
57
|
+
val transactor: Transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
58
|
+
|
|
59
|
+
transactor.connect {
|
|
60
|
+
val con = summon[DbCon].connection
|
|
61
|
+
val stmt = con.prepareStatement("INSERT INTO users (name, age) VALUES (?, ?)")
|
|
62
|
+
|
|
63
|
+
stmt.paramWriter.setString(1, "Alice") // First ?
|
|
64
|
+
stmt.paramWriter.setInt(2, 30) // Second ?
|
|
65
|
+
|
|
66
|
+
stmt.executeUpdate()
|
|
67
|
+
stmt.close()
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Note that indices are 1-based, not 0-based.
|
|
72
|
+
|
|
73
|
+
## How It Works
|
|
74
|
+
|
|
75
|
+
When you use higher-level operations like `Frag.query` or `Repo.insert`, `DbCodec` internally calls `DbParamWriter` methods to bind your Scala values to SQL parameters. You see this trait only when dropping down to raw prepared statements for advanced use cases like stored procedures or batch operations.
|
|
76
|
+
|
|
77
|
+
See [DbConnection](./db-connection.md) for examples of raw prepared-statement usage, and [DbCodec](./db-codec.md) for how parameter binding integrates with the codec system.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: db-param
|
|
3
|
+
title: "DbParam"
|
|
4
|
+
description: "Reference for DbParam, the typeclass that converts Scala values to DbValue for the sql interpolator and bound SQL parameters in the sql module."
|
|
5
|
+
keywords:
|
|
6
|
+
- "DbParam typeclass"
|
|
7
|
+
- "sql interpolator parameters"
|
|
8
|
+
- "toDbValue conversion"
|
|
9
|
+
- "DbParam given instances"
|
|
10
|
+
- "fromDbCodec bridge"
|
|
11
|
+
- "Option Maybe nullable parameters"
|
|
12
|
+
- "Compile-time SQL parameters"
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
`DbParam[A]` is a single-method typeclass that converts a Scala value of type `A` to a `DbValue` for use as a bound SQL parameter. Its sole abstract method is `toDbValue(value: A): DbValue`. The `sql"..."` string interpolator summons a `DbParam[A]` instance at compile time for every interpolated expression and delegates to it during execution to produce the typed `DbValue` that is stored in `Frag#params`.
|
|
16
|
+
|
|
17
|
+
The structural shape of `DbParam` is:
|
|
18
|
+
|
|
19
|
+
```scala
|
|
20
|
+
trait DbParam[A] {
|
|
21
|
+
def toDbValue(value: A): DbValue
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
object DbParam {
|
|
25
|
+
def apply[A](implicit p: DbParam[A]): DbParam[A]
|
|
26
|
+
// given instances for:
|
|
27
|
+
// Int, Long, Double, Float, Boolean, String, Short, Byte,
|
|
28
|
+
// BigDecimal, Array[Byte], LocalDate, LocalDateTime, LocalTime,
|
|
29
|
+
// Instant, Duration, UUID, DbValue (identity),
|
|
30
|
+
// Option[A], Maybe[A], and fromDbCodec[A]
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
The following example shows the three common entry points for `DbParam`: the `sql"..."` interpolator (which calls `toDbValue` behind the scenes), direct instance summoning for inspection:
|
|
37
|
+
|
|
38
|
+
```scala
|
|
39
|
+
import zio.blocks.sql._
|
|
40
|
+
import java.time.Instant
|
|
41
|
+
import java.util.UUID
|
|
42
|
+
|
|
43
|
+
// 1. Implicitly used by the sql"..." interpolator — no explicit call needed
|
|
44
|
+
val userId = 42
|
|
45
|
+
// userId: Int = 42
|
|
46
|
+
val active = true
|
|
47
|
+
// active: Boolean = true
|
|
48
|
+
val query = sql"SELECT * FROM users WHERE id = $userId AND active = $active"
|
|
49
|
+
// query: Frag = Frag(
|
|
50
|
+
// parts = ArraySeq("SELECT * FROM users WHERE id = ", " AND active = ", ""),
|
|
51
|
+
// params = Vector(DbInt(42), DbBoolean(true))
|
|
52
|
+
// )
|
|
53
|
+
query.params
|
|
54
|
+
// res0: IndexedSeq[DbValue] = Vector(DbInt(42), DbBoolean(true))
|
|
55
|
+
|
|
56
|
+
// 2. Summon an instance explicitly with DbParam.apply to inspect conversion
|
|
57
|
+
val instant = Instant.parse("2024-01-15T10:00:00Z")
|
|
58
|
+
// instant: Instant = 2024-01-15T10:00:00Z
|
|
59
|
+
DbParam[Instant].toDbValue(instant)
|
|
60
|
+
// res1: DbValue = DbInstant(2024-01-15T10:00:00Z)
|
|
61
|
+
|
|
62
|
+
DbParam[Option[Int]].toDbValue(Some(7))
|
|
63
|
+
// res2: DbValue = DbInt(7)
|
|
64
|
+
DbParam[Option[Int]].toDbValue(None)
|
|
65
|
+
// res3: DbValue = DbNull
|
|
66
|
+
```
|