@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,687 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: db-codec
|
|
3
|
+
title: "DbCodec"
|
|
4
|
+
description: "Reference for DbCodec[A], the foundational bidirectional codec between Scala values and database columns in the sql module."
|
|
5
|
+
keywords:
|
|
6
|
+
- "DbCodec Schema derivation"
|
|
7
|
+
- "Bidirectional Column Codec"
|
|
8
|
+
- "JDBC Row Mapping"
|
|
9
|
+
- "DbResultReader DbParamWriter"
|
|
10
|
+
- "JSONB Column Encoding"
|
|
11
|
+
- "Opaque Type Codec"
|
|
12
|
+
- "SQL Module Codec"
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
`DbCodec[A]` is a bidirectional codec between a Scala value of type `A` and one or more database columns. Every read-side operation — fetching rows from a result set — and every write-side operation — binding parameters to a prepared statement — flows through a `DbCodec`. It is the foundational type in the `sql` module: [`Frag`](./frag.md) uses it to decode query results, [`Table`](./table.md) carries it as column metadata, and [`Repo`](./repo.md) relies on it to map entity rows to and from the database.
|
|
16
|
+
|
|
17
|
+
Key properties:
|
|
18
|
+
- **Bidirectional** — the same type handles both encoding (write) and decoding (read), keeping the two directions in sync.
|
|
19
|
+
- **Multi-column** — a single codec spans any number of database columns; a case class codec produces one column per field.
|
|
20
|
+
- **Label-based and positional** — `readValue` supports order-independent decoding by column label or fast 1-based positional access per the JDBC convention.
|
|
21
|
+
- **Schema-driven** — `DbCodec.derived` and `DbCodecDeriver` produce codecs automatically from a `Schema[A]` at compile time, with no runtime reflection.
|
|
22
|
+
- **Null-safe** — `Option[A]` and `Maybe[A]` codecs handle SQL `NULL` transparently; non-optional types throw `IllegalStateException` on unexpected `NULL`, surfacing schema mismatches immediately rather than silently coercing.
|
|
23
|
+
|
|
24
|
+
## Core API
|
|
25
|
+
|
|
26
|
+
```scala
|
|
27
|
+
import zio.blocks.sql.{DbResultReader, DbParamWriter, DbValue}
|
|
28
|
+
import zio.blocks.schema.derive.DerivationBuilder
|
|
29
|
+
|
|
30
|
+
trait DbCodec[A] {
|
|
31
|
+
// Column inspection
|
|
32
|
+
def columns: IndexedSeq[String]
|
|
33
|
+
def columnCount: Int
|
|
34
|
+
|
|
35
|
+
// Read operations
|
|
36
|
+
def readValue(reader: DbResultReader, startIndex: Int): A
|
|
37
|
+
def readValue(reader: DbResultReader, columnLabels: IndexedSeq[String]): A
|
|
38
|
+
|
|
39
|
+
// Write operations
|
|
40
|
+
def writeValue(writer: DbParamWriter, startIndex: Int, value: A): Unit
|
|
41
|
+
def toDbValues(value: A): IndexedSeq[DbValue]
|
|
42
|
+
|
|
43
|
+
// Transformation
|
|
44
|
+
def transform[B](read: A => B)(write: B => A): DbCodec[B]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
object DbCodec {
|
|
48
|
+
// Automatic derivation from Schema[A]
|
|
49
|
+
inline given derived[A]: DbCodec[A]
|
|
50
|
+
inline given derivedOpaque[A]: DbCodec[A]
|
|
51
|
+
|
|
52
|
+
// Customize derivation
|
|
53
|
+
inline def builder[A]: DerivationBuilder[DbCodec, A]
|
|
54
|
+
inline def derivedWith[A](
|
|
55
|
+
configure: DerivationBuilder[DbCodec, A] => DerivationBuilder[DbCodec, A]
|
|
56
|
+
): DbCodec[A]
|
|
57
|
+
|
|
58
|
+
// Retrieval
|
|
59
|
+
def apply[A](implicit codec: DbCodec[A]): DbCodec[A]
|
|
60
|
+
|
|
61
|
+
// Built-in instances
|
|
62
|
+
// given instances for: Int, Long, String, Boolean, Double, Float, Short, Byte,
|
|
63
|
+
// BigDecimal, Instant, UUID, ...
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Codecs for JSON/JSONB columns, type conversions, and specialized encoding strategies are also available through additional companion object methods and instances.
|
|
68
|
+
|
|
69
|
+
## Usage
|
|
70
|
+
|
|
71
|
+
The following example shows the core lifecycle of a `DbCodec`: deriving one automatically, inspecting its column metadata, encoding a value, handling nullable columns, and adapting the codec to a newtype:
|
|
72
|
+
|
|
73
|
+
```scala
|
|
74
|
+
import zio.blocks.sql._
|
|
75
|
+
import zio.blocks.schema.Schema
|
|
76
|
+
|
|
77
|
+
// Derive a codec automatically — field names map to snake_case columns by default
|
|
78
|
+
case class User(id: Int, name: String, email: Option[String]) derives DbCodec
|
|
79
|
+
|
|
80
|
+
val codec = DbCodec[User]
|
|
81
|
+
// codec: DbCodec[User] = zio.blocks.sql.DbCodecDeriver$$anon$20@55869d8
|
|
82
|
+
|
|
83
|
+
codec.columns
|
|
84
|
+
// res1: IndexedSeq[String] = Vector("id", "name", "email")
|
|
85
|
+
codec.columnCount
|
|
86
|
+
// res2: Int = 3
|
|
87
|
+
|
|
88
|
+
// Encode a value for use as SQL parameters
|
|
89
|
+
val params = codec.toDbValues(User(1, "Alice", Some("alice@example.com")))
|
|
90
|
+
// params: IndexedSeq[DbValue] = Vector(
|
|
91
|
+
// DbInt(1),
|
|
92
|
+
// DbString("Alice"),
|
|
93
|
+
// DbString("alice@example.com")
|
|
94
|
+
// )
|
|
95
|
+
|
|
96
|
+
// None encodes as SQL NULL
|
|
97
|
+
val nullParams = codec.toDbValues(User(2, "Bob", None))
|
|
98
|
+
// nullParams: IndexedSeq[DbValue] = Vector(DbInt(2), DbString("Bob"), DbNull)
|
|
99
|
+
|
|
100
|
+
// Adapt any codec to a newtype with transform — no full Schema needed
|
|
101
|
+
case class UserId(value: Int)
|
|
102
|
+
val userIdCodec: DbCodec[UserId] = DbCodec[Int].transform(UserId(_))(_.value)
|
|
103
|
+
// userIdCodec: DbCodec[UserId] = zio.blocks.sql.DbCodec$$anon$1@2f13d28a
|
|
104
|
+
|
|
105
|
+
userIdCodec.columns
|
|
106
|
+
// res3: IndexedSeq[String] = Vector("value")
|
|
107
|
+
userIdCodec.toDbValues(UserId(42))
|
|
108
|
+
// res4: IndexedSeq[DbValue] = Vector(DbInt(42))
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Construction / Creating Instances
|
|
112
|
+
|
|
113
|
+
We can obtain a `DbCodec[A]` in several ways: automatic schema derivation (the most common path), structured derivation with field-level overrides, JSONB wrapping for complex types, opaque-type support, and manual composition.
|
|
114
|
+
|
|
115
|
+
### `DbCodec.derived` — Automatic schema-driven derivation
|
|
116
|
+
|
|
117
|
+
`DbCodec.derived` is a Scala 3 `inline given` that produces a `DbCodec[A]` by internally deriving `Schema[A]` and running it through `DbCodecDeriver`. Because it derives `Schema` itself, no explicit `Schema` needs to be in scope. It also enables the Scala 3 `derives` clause.
|
|
118
|
+
|
|
119
|
+
```scala
|
|
120
|
+
object DbCodec {
|
|
121
|
+
inline given derived[A]: DbCodec[A]
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The two most common spellings are the `derives` clause on the case class and explicit summoning:
|
|
126
|
+
|
|
127
|
+
```scala
|
|
128
|
+
import zio.blocks.sql._
|
|
129
|
+
|
|
130
|
+
// Option 1: derives clause
|
|
131
|
+
case class Product(sku: String, price: BigDecimal, inStock: Boolean) derives DbCodec
|
|
132
|
+
|
|
133
|
+
// Option 2: explicit given
|
|
134
|
+
case class Category(id: Int, name: String)
|
|
135
|
+
given DbCodec[Category] = DbCodec.derived
|
|
136
|
+
|
|
137
|
+
// Columns follow the default SqlNameMapper (SnakeCase):
|
|
138
|
+
// Product → "sku", "price", "in_stock"
|
|
139
|
+
// Category → "id", "name"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`DbCodec.derived` delegates to `DbCodecDeriver`, which handles primitives, case classes, enums, sealed traits, `Option`/`Maybe` fields, and JSONB-encoded complex fields. Enum and sealed-trait variants serialize to their name as a `String` column unless annotated with `@Modifier.rename`.
|
|
143
|
+
|
|
144
|
+
:::tip
|
|
145
|
+
For case classes with fields that need custom codecs or name overrides, prefer `DbCodec.derivedWith` so you can supply those overrides through the `DerivationBuilder` API.
|
|
146
|
+
:::
|
|
147
|
+
|
|
148
|
+
### `DbCodec.derivedWith` — Derivation with field-level overrides
|
|
149
|
+
|
|
150
|
+
`DbCodec.derivedWith` derives a `DbCodec[A]` and applies caller-supplied overrides before finalizing the codec. This is the right choice when a specific field needs a custom codec — for example, a JSON-encoded value type or a field stored in a non-default format.
|
|
151
|
+
|
|
152
|
+
```scala
|
|
153
|
+
object DbCodec {
|
|
154
|
+
inline def derivedWith[A](
|
|
155
|
+
configure: DerivationBuilder[DbCodec, A] => DerivationBuilder[DbCodec, A]
|
|
156
|
+
): DbCodec[A]
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The configure function receives a `DerivationBuilder[DbCodec, A]` and returns a modified one. We call `DerivationBuilder#instance` to attach a custom `DbCodec` for a specific field, identified by the enclosing type's `TypeId` and the field name:
|
|
161
|
+
|
|
162
|
+
```scala
|
|
163
|
+
import zio.blocks.sql._
|
|
164
|
+
import zio.blocks.schema.Schema
|
|
165
|
+
import zio.blocks.typeid.TypeId
|
|
166
|
+
|
|
167
|
+
case class Tags(values: List[String])
|
|
168
|
+
object Tags { implicit val schema: Schema[Tags] = Schema.derived }
|
|
169
|
+
|
|
170
|
+
case class Product(id: Int, tags: Tags)
|
|
171
|
+
object Product { implicit val schema: Schema[Product] = Schema.derived }
|
|
172
|
+
|
|
173
|
+
// tags is stored as a JSON string in the "tags" column
|
|
174
|
+
val tagsCodec: DbCodec[Tags] =
|
|
175
|
+
DbCodec[String].transform(json => Tags(json.split(",").toList))(_.values.mkString(","))
|
|
176
|
+
// tagsCodec: DbCodec[Tags] = zio.blocks.sql.DbCodec$$anon$1@3592645d
|
|
177
|
+
|
|
178
|
+
val productCodec: DbCodec[Product] =
|
|
179
|
+
DbCodec.derivedWith[Product](
|
|
180
|
+
_.instance(TypeId.of[Product], "tags", tagsCodec)
|
|
181
|
+
)
|
|
182
|
+
// productCodec: DbCodec[Product] = zio.blocks.sql.DbCodecDeriver$$anon$20@73120731
|
|
183
|
+
|
|
184
|
+
productCodec.columns
|
|
185
|
+
// res7: IndexedSeq[String] = Vector("id", "tags")
|
|
186
|
+
productCodec.columnCount
|
|
187
|
+
// res8: Int = 2
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### `DbCodec.jsonb` — JSONB column codec
|
|
191
|
+
|
|
192
|
+
`DbCodec.jsonb` creates a `DbCodec[A]` that stores and retrieves a value of type `A` as a JSON string in a single database column. Two overloads are available: one using an implicit `JsonSchemaCodec[A]` for the encode/decode pair, and one accepting explicit functions.
|
|
193
|
+
|
|
194
|
+
```scala
|
|
195
|
+
object DbCodec {
|
|
196
|
+
def jsonb[A](using jsonCodec: JsonSchemaCodec[A]): DbCodec[A]
|
|
197
|
+
def jsonb[A](encode: A => String, decode: String => A): DbCodec[A]
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The first overload requires a `JsonSchemaCodec[A]` (aliased from `zio.blocks.schema.json.JsonCodec`) in implicit scope:
|
|
202
|
+
|
|
203
|
+
```scala
|
|
204
|
+
import zio.blocks.sql._
|
|
205
|
+
import zio.blocks.schema.Schema
|
|
206
|
+
import zio.blocks.schema.json.{JsonCodec => JsonSchemaCodec, JsonCodecDeriver}
|
|
207
|
+
|
|
208
|
+
case class Address(street: String, city: String)
|
|
209
|
+
object Address {
|
|
210
|
+
implicit val schema: Schema[Address] = Schema.derived
|
|
211
|
+
implicit val jsonCodec: JsonSchemaCodec[Address] = schema.deriving(JsonCodecDeriver).derive
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Address is stored as a JSON string in a single TEXT/JSONB column
|
|
215
|
+
val codec: DbCodec[Address] = DbCodec.jsonb[Address]
|
|
216
|
+
// codec: DbCodec[Address] = zio.blocks.sql.DbCodec$$anon$1@68d986d1
|
|
217
|
+
|
|
218
|
+
codec.columns
|
|
219
|
+
// res10: IndexedSeq[String] = Vector("value")
|
|
220
|
+
codec.toDbValues(Address("Main St", "NYC"))
|
|
221
|
+
// res11: IndexedSeq[DbValue] = Vector(
|
|
222
|
+
// DbString("{\"street\":\"Main St\",\"city\":\"NYC\"}")
|
|
223
|
+
// )
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Use the two-argument overload when you supply custom encode/decode logic instead of relying on `JsonSchemaCodec`:
|
|
227
|
+
|
|
228
|
+
```scala
|
|
229
|
+
import zio.blocks.sql._
|
|
230
|
+
|
|
231
|
+
case class Point(x: Double, y: Double)
|
|
232
|
+
|
|
233
|
+
// Custom JSON encoding using a hand-rolled format
|
|
234
|
+
val pointCodec: DbCodec[Point] = DbCodec.jsonb[Point](
|
|
235
|
+
p => s"${p.x},${p.y}",
|
|
236
|
+
s => { val parts = s.split(","); Point(parts(0).toDouble, parts(1).toDouble) }
|
|
237
|
+
)
|
|
238
|
+
// pointCodec: DbCodec[Point] = zio.blocks.sql.DbCodec$$anon$1@1144ae3c
|
|
239
|
+
|
|
240
|
+
pointCodec.toDbValues(Point(1.0, 2.0))
|
|
241
|
+
// res12: IndexedSeq[DbValue] = Vector(DbString("1.0,2.0"))
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### `DbCodec.jsonbOption` — Nullable JSONB column codec
|
|
245
|
+
|
|
246
|
+
`DbCodec.jsonbOption` creates a `DbCodec[Option[A]]` that stores `Some(a)` as a JSON string and `None` as SQL `NULL`. Like `jsonb`, it has an implicit `JsonSchemaCodec[A]` overload and a two-argument overload:
|
|
247
|
+
|
|
248
|
+
```scala
|
|
249
|
+
object DbCodec {
|
|
250
|
+
def jsonbOption[A](using jsonCodec: JsonSchemaCodec[A]): DbCodec[Option[A]]
|
|
251
|
+
def jsonbOption[A](encode: A => String, decode: String => A): DbCodec[Option[A]]
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
The codec delegates to `DbCodec[Option[String]]` and applies the JSON encode/decode on the inner `String`, so `NULL` detection uses the underlying `Option[String]` codec's standard null handling:
|
|
256
|
+
|
|
257
|
+
```scala
|
|
258
|
+
import zio.blocks.sql._
|
|
259
|
+
import zio.blocks.schema.json.{JsonCodec => JsonSchemaCodec}
|
|
260
|
+
|
|
261
|
+
// Assume JsonSchemaCodec[Address] is in scope from the previous example
|
|
262
|
+
val nullableCodec: DbCodec[Option[Address]] = DbCodec.jsonbOption[Address]
|
|
263
|
+
// nullableCodec: DbCodec[Option[Address]] = zio.blocks.sql.DbCodec$$anon$1@4c7b464f
|
|
264
|
+
|
|
265
|
+
nullableCodec.toDbValues(Some(Address("Elm St", "LA")))
|
|
266
|
+
// res13: IndexedSeq[DbValue] = Vector(
|
|
267
|
+
// DbString("{\"street\":\"Elm St\",\"city\":\"LA\"}")
|
|
268
|
+
// )
|
|
269
|
+
|
|
270
|
+
nullableCodec.toDbValues(None)
|
|
271
|
+
// res14: IndexedSeq[DbValue] = Vector(DbNull)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### `DbCodec.derivedOpaque` — Opaque type derivation
|
|
275
|
+
|
|
276
|
+
`DbCodec.derivedOpaque` is a lower-priority `inline given` that produces a `DbCodec[A]` for Scala 3 opaque types by reusing the codec of the underlying type. The compiler selects it automatically when `A` is an opaque type and no explicit `DbCodec[A]` is in scope.
|
|
277
|
+
|
|
278
|
+
```scala
|
|
279
|
+
object DbCodec {
|
|
280
|
+
inline given derivedOpaque[A]: DbCodec[A]
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
For the decode direction the opaque type's companion `apply` is called. For the encode direction, if the opaque type is declared as a subtype of its underlying type (`opaque type T <: U = U`), the value is used directly; otherwise the companion must expose an `unwrap` method:
|
|
285
|
+
|
|
286
|
+
```scala
|
|
287
|
+
import zio.blocks.sql._
|
|
288
|
+
|
|
289
|
+
opaque type ProductId <: String = String
|
|
290
|
+
object ProductId {
|
|
291
|
+
def apply(value: String): ProductId = value
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// DbCodec[ProductId] is resolved automatically — no explicit given needed
|
|
295
|
+
val codec = DbCodec[ProductId]
|
|
296
|
+
// codec: DbCodec[ProductId] = zio.blocks.sql.DbCodec$$anon$6@3261707e
|
|
297
|
+
codec.columns
|
|
298
|
+
// res16: IndexedSeq[String] = Vector("value")
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
:::caution
|
|
302
|
+
`DbCodec.derivedOpaque` is a Scala 3-only macro. The `sql` module requires Scala 3.
|
|
303
|
+
:::
|
|
304
|
+
|
|
305
|
+
### `DbCodec.dbCodecFromAs` — Codec derivation via `As` conversion
|
|
306
|
+
|
|
307
|
+
`DbCodec.dbCodecFromAs` is a `given` that derives `DbCodec[B]` from `DbCodec[A]` and an `As[A, B]` conversion. It enables opaque types and newtype wrappers to receive a `DbCodec` automatically when their underlying type already has one and an `As[A, B]` instance is provided:
|
|
308
|
+
|
|
309
|
+
```scala
|
|
310
|
+
object DbCodec {
|
|
311
|
+
given dbCodecFromAs[A, B](using conv: As[A, B], base: DbCodec[A]): DbCodec[B]
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
`As[A, B]` (from `zio.blocks.schema`) represents a validated conversion from `A` to `B` and from `B` back to `A`. The derived codec applies `As#into` on decode and `As#from` on encode; if either conversion returns a `Left`, an `IllegalStateException` is thrown at runtime:
|
|
316
|
+
|
|
317
|
+
```scala
|
|
318
|
+
import zio.blocks.sql._
|
|
319
|
+
import zio.blocks.schema.As
|
|
320
|
+
|
|
321
|
+
// Suppose As[String, EmailAddress] is defined and EmailAddress wraps String
|
|
322
|
+
// DbCodec[EmailAddress] is then resolved automatically — no explicit given needed
|
|
323
|
+
// val emailCodec = DbCodec[EmailAddress]
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
For types without an `As` instance, use `DbCodec[A].transform` instead.
|
|
327
|
+
|
|
328
|
+
### `DbCodec.apply` — Summoning an instance
|
|
329
|
+
|
|
330
|
+
`DbCodec.apply` summons an implicitly available `DbCodec[A]` from the current scope. It is the standard way to access a codec without writing `implicitly` or `summon`:
|
|
331
|
+
|
|
332
|
+
```scala
|
|
333
|
+
object DbCodec {
|
|
334
|
+
def apply[A](implicit codec: DbCodec[A]): DbCodec[A]
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
We use `DbCodec.apply` whenever we need a codec value without knowing its derivation path:
|
|
339
|
+
|
|
340
|
+
```scala
|
|
341
|
+
import zio.blocks.sql._
|
|
342
|
+
|
|
343
|
+
case class Order(id: Long, status: String) derives DbCodec
|
|
344
|
+
|
|
345
|
+
// Summon the derived codec
|
|
346
|
+
val codec: DbCodec[Order] = DbCodec[Order]
|
|
347
|
+
// codec: DbCodec[Order] = zio.blocks.sql.DbCodecDeriver$$anon$20@678700e1
|
|
348
|
+
codec.columns
|
|
349
|
+
// res19: IndexedSeq[String] = Vector("id", "status")
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### `DbCodec.builder` — Derivation builder
|
|
353
|
+
|
|
354
|
+
`DbCodec.builder[A]` returns a `DerivationBuilder[DbCodec, A]` pre-seeded with the derived schema for `A`. Use it when you need to attach multiple field-level overrides before calling `.derive` to finalize the codec, giving you full control over the build process:
|
|
355
|
+
|
|
356
|
+
```scala
|
|
357
|
+
object DbCodec {
|
|
358
|
+
inline def builder[A]: DerivationBuilder[DbCodec, A]
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
`DerivationBuilder` exposes `instance` to attach custom codecs for individual fields and `derive` to produce the final codec. `DbCodec.derivedWith` is a one-liner wrapper around `builder`:
|
|
363
|
+
|
|
364
|
+
```scala
|
|
365
|
+
import zio.blocks.sql._
|
|
366
|
+
import zio.blocks.typeid.TypeId
|
|
367
|
+
|
|
368
|
+
case class Order(id: Long, tags: List[String], metadata: Map[String, String])
|
|
369
|
+
|
|
370
|
+
// Build manually — equivalent to derivedWith but explicit
|
|
371
|
+
val codec: DbCodec[Order] =
|
|
372
|
+
DbCodec
|
|
373
|
+
.builder[Order]
|
|
374
|
+
.instance(TypeId.of[Order], "tags", DbCodec[String].transform(_.split(",").toList)(_.mkString(",")))
|
|
375
|
+
.derive
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## Predefined Instances
|
|
379
|
+
|
|
380
|
+
`DbCodec` provides `given` instances for all primitive and common JVM types. Each occupies a single column named `"value"`:
|
|
381
|
+
|
|
382
|
+
| Scala Type | Given Name | `DbValue` variant | Notes |
|
|
383
|
+
|------------------------|--------------------|------------------------------|----------------------------------------------------|
|
|
384
|
+
| `Int` | `intCodec` | `DbValue.DbInt` | |
|
|
385
|
+
| `Long` | `longCodec` | `DbValue.DbLong` | |
|
|
386
|
+
| `String` | `stringCodec` | `DbValue.DbString` | |
|
|
387
|
+
| `Boolean` | `booleanCodec` | `DbValue.DbBoolean` | |
|
|
388
|
+
| `Double` | `doubleCodec` | `DbValue.DbDouble` | |
|
|
389
|
+
| `Float` | `floatCodec` | `DbValue.DbFloat` | |
|
|
390
|
+
| `Short` | `shortCodec` | `DbValue.DbShort` | |
|
|
391
|
+
| `Byte` | `byteCodec` | `DbValue.DbByte` | |
|
|
392
|
+
| `BigDecimal` | `bigDecimalCodec` | `DbValue.DbBigDecimal` | Throws on SQL `NULL`; use `Option[BigDecimal]` for nullable columns. |
|
|
393
|
+
| `java.time.Instant` | `instantCodec` | `DbValue.DbInstant` | |
|
|
394
|
+
| `Option[A]` | `optionCodec` | inner or `DbValue.DbNull` | Requires a `DbCodec[A]`; single-column inner only. |
|
|
395
|
+
| `Maybe[A]` | `maybeCodec` | inner or `DbValue.DbNull` | Requires a `DbCodec[A]`; single-column inner only. |
|
|
396
|
+
|
|
397
|
+
All primitive codecs set their single column name to `"value"`. When a primitive codec is used as part of a record derivation, `DbCodecDeriver` replaces the column name with the field name (after applying the `SqlNameMapper`).
|
|
398
|
+
|
|
399
|
+
## Core Operations
|
|
400
|
+
|
|
401
|
+
The five abstract and one final method on `DbCodec` divide into four operational groups: column metadata inspection, decoding from a result set, encoding to prepared-statement parameters, and transformation.
|
|
402
|
+
|
|
403
|
+
### Column Metadata
|
|
404
|
+
|
|
405
|
+
The column metadata methods expose the names and count of columns a codec spans. They are used by `Table`, `Repo`, and `Frag` to build SQL `SELECT`, `INSERT`, and `UPDATE` clauses without any per-call string assembly.
|
|
406
|
+
|
|
407
|
+
#### `columns` — Ordered column names
|
|
408
|
+
|
|
409
|
+
`DbCodec#columns` returns the ordered `IndexedSeq[String]` of database column names for this codec. The sequence matches the order in which `readValue` and `writeValue` consume and produce values.
|
|
410
|
+
|
|
411
|
+
```scala
|
|
412
|
+
trait DbCodec[A] {
|
|
413
|
+
def columns: IndexedSeq[String]
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
For a case class codec produced by `DbCodec.derived`, each field maps to one column name after the `SqlNameMapper` (default: `SnakeCase`). Annotating a field with `@Modifier.rename("custom_name")` overrides the mapped name:
|
|
418
|
+
|
|
419
|
+
```scala
|
|
420
|
+
import zio.blocks.sql._
|
|
421
|
+
import zio.blocks.schema.{Schema, Modifier}
|
|
422
|
+
|
|
423
|
+
case class BlogPost(
|
|
424
|
+
@Modifier.rename("post_id") id: Int,
|
|
425
|
+
authorName: String
|
|
426
|
+
) derives DbCodec
|
|
427
|
+
|
|
428
|
+
DbCodec[BlogPost].columns
|
|
429
|
+
// res22: IndexedSeq[String] = Vector("post_id", "author_name")
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
#### `columnCount` — Number of columns
|
|
433
|
+
|
|
434
|
+
`DbCodec#columnCount` returns the number of columns this codec spans. It is derived from `columns.size` and provided as a concrete method:
|
|
435
|
+
|
|
436
|
+
```scala
|
|
437
|
+
trait DbCodec[A] {
|
|
438
|
+
def columnCount: Int = columns.size
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
We use `columnCount` to validate multi-column usage and to calculate offsets when composing codecs. For all primitive codecs, `columnCount` is `1`. For a case class, it equals the number of non-transient fields (fields annotated with `@Modifier.transient()` are excluded):
|
|
443
|
+
|
|
444
|
+
```scala
|
|
445
|
+
import zio.blocks.sql._
|
|
446
|
+
import zio.blocks.schema.{Schema, Modifier}
|
|
447
|
+
|
|
448
|
+
case class Event(name: String, @Modifier.transient() internalFlag: Boolean = false) derives DbCodec
|
|
449
|
+
|
|
450
|
+
DbCodec[Event].columnCount // "internalFlag" is excluded
|
|
451
|
+
// res24: Int = 1
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Reading / Decoding
|
|
455
|
+
|
|
456
|
+
The two `readValue` overloads decode a Scala value from a `DbResultReader`, which abstracts over a JDBC `ResultSet`. Query execution in `Frag` prefers the label-based overload so that result column order can differ from codec column order.
|
|
457
|
+
|
|
458
|
+
#### `readValue` — Positional read
|
|
459
|
+
|
|
460
|
+
`DbCodec#readValue(reader, startIndex)` reads a value of type `A` from the result reader starting at the given 1-based column index. For a multi-column codec, it reads `columnCount` consecutive columns beginning at `startIndex`. Internally this overload delegates to the label-based overload by calling `DbResultReader#columnLabel` for each offset.
|
|
461
|
+
|
|
462
|
+
```scala
|
|
463
|
+
trait DbCodec[A] {
|
|
464
|
+
def readValue(reader: DbResultReader, startIndex: Int): A
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
The default implementation converts positional access to label-based access automatically, so implementing only the label-based overload is sufficient when writing a custom `DbCodec`:
|
|
469
|
+
|
|
470
|
+
```scala
|
|
471
|
+
import zio.blocks.sql._
|
|
472
|
+
|
|
473
|
+
// For illustration: a custom single-column String codec
|
|
474
|
+
val uppercaseCodec: DbCodec[String] = new DbCodec[String] {
|
|
475
|
+
val columns: IndexedSeq[String] = IndexedSeq("value")
|
|
476
|
+
def readValue(reader: DbResultReader, columnLabels: IndexedSeq[String]): String =
|
|
477
|
+
reader.getString(columnLabels.head).toUpperCase
|
|
478
|
+
def writeValue(writer: DbParamWriter, startIndex: Int, value: String): Unit =
|
|
479
|
+
writer.setString(startIndex, value)
|
|
480
|
+
def toDbValues(value: String): IndexedSeq[DbValue] =
|
|
481
|
+
IndexedSeq(DbValue.DbString(value))
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// positional overload works automatically
|
|
485
|
+
// uppercaseCodec.readValue(reader, 1) → delegates to label-based via columnLabel(1)
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
:::caution
|
|
489
|
+
`startIndex` is 1-based per the JDBC convention. Passing `0` will cause an out-of-bounds error in the underlying `ResultSet`.
|
|
490
|
+
:::
|
|
491
|
+
|
|
492
|
+
#### `readValue` — Label-based read
|
|
493
|
+
|
|
494
|
+
`DbCodec#readValue(reader, columnLabels)` reads a value of type `A` by looking up each column by label, allowing the result set's column order to differ from the codec's column order. `Frag#query` always calls this overload, passing the labels derived from the query's `SELECT` list.
|
|
495
|
+
|
|
496
|
+
```scala
|
|
497
|
+
trait DbCodec[A] {
|
|
498
|
+
def readValue(reader: DbResultReader, columnLabels: IndexedSeq[String]): A
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
The caller must supply exactly `columnCount` labels in the logical order matching the codec's `columns` sequence. In practice, `Frag` constructs this sequence automatically from the query result metadata:
|
|
503
|
+
|
|
504
|
+
```scala
|
|
505
|
+
import zio.blocks.sql._
|
|
506
|
+
|
|
507
|
+
case class User(id: Int, name: String) derives DbCodec
|
|
508
|
+
|
|
509
|
+
val codec = DbCodec[User]
|
|
510
|
+
|
|
511
|
+
// Calling with explicit labels — useful for custom result processing
|
|
512
|
+
// codec.readValue(reader, IndexedSeq("id", "name")) → User(...)
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### Writing / Encoding
|
|
516
|
+
|
|
517
|
+
The two encoding methods convert a Scala value into database parameters: `writeValue` binds values directly to a `DbParamWriter` (a prepared statement), while `toDbValues` converts them to the typed `DbValue` ADT for inspection, testing, and logging.
|
|
518
|
+
|
|
519
|
+
#### `writeValue` — Bind to a prepared statement
|
|
520
|
+
|
|
521
|
+
`DbCodec#writeValue` writes a value of type `A` to a `DbParamWriter` starting at the given 1-based parameter index. For a multi-column codec, it writes exactly `columnCount` consecutive parameters beginning at `startIndex`.
|
|
522
|
+
|
|
523
|
+
```scala
|
|
524
|
+
trait DbCodec[A] {
|
|
525
|
+
def writeValue(writer: DbParamWriter, startIndex: Int, value: A): Unit
|
|
526
|
+
}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
`Frag` and `Repo` call this method to bind parameters when executing `INSERT` and `UPDATE` statements. For `None` / `Maybe.absent`, the codec calls `DbParamWriter#setNull` with `java.sql.Types.NULL`:
|
|
530
|
+
|
|
531
|
+
```scala
|
|
532
|
+
import zio.blocks.sql._
|
|
533
|
+
|
|
534
|
+
case class Point(x: Double, y: Double) derives DbCodec
|
|
535
|
+
|
|
536
|
+
val codec = DbCodec[Point]
|
|
537
|
+
// codec.writeValue(writer, 1, Point(3.0, 4.0))
|
|
538
|
+
// → writer.setDouble(1, 3.0); writer.setDouble(2, 4.0)
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
:::caution
|
|
542
|
+
`startIndex` is 1-based. The codec writes exactly `columnCount` parameters, so if you compose two codecs at offsets `i` and `i + codec.columnCount`, the second start index must be adjusted accordingly.
|
|
543
|
+
:::
|
|
544
|
+
|
|
545
|
+
#### `toDbValues` — Convert to `DbValue` representation
|
|
546
|
+
|
|
547
|
+
`DbCodec#toDbValues` converts a value of type `A` into an `IndexedSeq[DbValue]`, one element per column. The result is parallel to `columns` — `toDbValues(v)(i)` corresponds to `columns(i)`.
|
|
548
|
+
|
|
549
|
+
```scala
|
|
550
|
+
trait DbCodec[A] {
|
|
551
|
+
def toDbValues(value: A): IndexedSeq[DbValue]
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
`toDbValues` is used by `Frag` and `Repo` to inspect or log parameters before binding, and in tests to assert encoding behavior without a real database connection:
|
|
556
|
+
|
|
557
|
+
```scala
|
|
558
|
+
import zio.blocks.sql._
|
|
559
|
+
|
|
560
|
+
case class Item(id: Int, name: String, price: Option[BigDecimal]) derives DbCodec
|
|
561
|
+
|
|
562
|
+
val codec = DbCodec[Item]
|
|
563
|
+
// codec: DbCodec[Item] = zio.blocks.sql.DbCodecDeriver$$anon$20@1ffebf02
|
|
564
|
+
|
|
565
|
+
codec.toDbValues(Item(1, "Widget", Some(BigDecimal("9.99"))))
|
|
566
|
+
// res29: IndexedSeq[DbValue] = Vector(
|
|
567
|
+
// DbInt(1),
|
|
568
|
+
// DbString("Widget"),
|
|
569
|
+
// DbBigDecimal(9.99)
|
|
570
|
+
// )
|
|
571
|
+
|
|
572
|
+
codec.toDbValues(Item(2, "Gadget", None))
|
|
573
|
+
// res30: IndexedSeq[DbValue] = Vector(DbInt(2), DbString("Gadget"), DbNull)
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### Transformations
|
|
577
|
+
|
|
578
|
+
#### `transform` — Map a codec to a new type
|
|
579
|
+
|
|
580
|
+
`DbCodec#transform` returns a new `DbCodec[B]` by mapping the read direction with `read: A => B` and the write direction with `write: B => A`. The resulting codec shares the same `columns` as the original and is the lightest way to create a codec for a newtype or value wrapper without defining a full `Schema`:
|
|
581
|
+
|
|
582
|
+
```scala
|
|
583
|
+
trait DbCodec[A] {
|
|
584
|
+
final def transform[B](read: A => B)(write: B => A): DbCodec[B]
|
|
585
|
+
}
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
Both `read` and `write` must be total functions; any exception they throw propagates to the caller. The transformed codec delegates all column metadata and read/write operations to the inner codec after applying the conversions:
|
|
589
|
+
|
|
590
|
+
```scala
|
|
591
|
+
import zio.blocks.sql._
|
|
592
|
+
|
|
593
|
+
case class ProductId(value: String)
|
|
594
|
+
|
|
595
|
+
// Adapt the String codec to ProductId without a Schema
|
|
596
|
+
val productIdCodec: DbCodec[ProductId] =
|
|
597
|
+
DbCodec[String].transform(ProductId(_))(_.value)
|
|
598
|
+
// productIdCodec: DbCodec[ProductId] = zio.blocks.sql.DbCodec$$anon$1@95d8a43
|
|
599
|
+
|
|
600
|
+
productIdCodec.columns
|
|
601
|
+
// res32: IndexedSeq[String] = Vector("value")
|
|
602
|
+
productIdCodec.toDbValues(ProductId("abc-1"))
|
|
603
|
+
// res33: IndexedSeq[DbValue] = Vector(DbString("abc-1"))
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
`DbCodec#transform` is also the engine behind `DbCodec.jsonb`, `DbCodec.jsonbOption`, and `DbCodec.dbCodecFromAs` — each of those constructors builds on top of an existing primitive or composite codec and applies `transform` to attach custom encode/decode logic.
|
|
607
|
+
|
|
608
|
+
## Supporting Types
|
|
609
|
+
|
|
610
|
+
The two interfaces that `DbCodec` depends on for its read and write operations are `DbResultReader` and `DbParamWriter`. Both abstract over the JDBC layer so the `sql` module's `shared` source compiles on Scala.js as well as the JVM, and so custom backends can substitute their own implementations without touching codec logic.
|
|
611
|
+
|
|
612
|
+
```
|
|
613
|
+
DbCodec[A]
|
|
614
|
+
│ reads via writes via
|
|
615
|
+
▼ ▼
|
|
616
|
+
DbResultReader DbParamWriter
|
|
617
|
+
│ │
|
|
618
|
+
JdbcResultSetReader JdbcParamWriter
|
|
619
|
+
│ │
|
|
620
|
+
java.sql.ResultSet java.sql.PreparedStatement
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### `DbResultReader` — Result set abstraction
|
|
624
|
+
|
|
625
|
+
`DbResultReader` is the interface through which `DbCodec#readValue` reads column values from a query result. It supports both label-based access (e.g., `DbResultReader#getString("name")`) and 1-based positional access (e.g., `DbResultReader#getInt(1)`), plus `DbResultReader#wasNull` to detect SQL `NULL` after any `get*` call:
|
|
626
|
+
|
|
627
|
+
```scala
|
|
628
|
+
trait DbResultReader {
|
|
629
|
+
def getInt(index: Int): Int
|
|
630
|
+
def getInt(label: String): Int
|
|
631
|
+
def getString(index: Int): String
|
|
632
|
+
def getString(label: String): String
|
|
633
|
+
def getBoolean(label: String): Boolean
|
|
634
|
+
def getBigDecimal(label: String): java.math.BigDecimal
|
|
635
|
+
def getInstant(label: String): java.time.Instant
|
|
636
|
+
// ... and all other column types
|
|
637
|
+
def columnLabel(index: Int): String
|
|
638
|
+
def hasColumn(label: String): Boolean
|
|
639
|
+
def wasNull: Boolean
|
|
640
|
+
}
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
`DbResultReader` is rarely used directly in application code. `Frag#query` wraps the JDBC `ResultSet` in a `JdbcResultSetReader` and passes it to the appropriate `DbCodec#readValue` call automatically.
|
|
644
|
+
|
|
645
|
+
### `DbParamWriter` — Prepared statement abstraction
|
|
646
|
+
|
|
647
|
+
`DbParamWriter` is the interface through which `DbCodec#writeValue` binds column values to a prepared statement. It follows the JDBC convention of 1-based parameter indexes and includes `setNull` for writing SQL `NULL`:
|
|
648
|
+
|
|
649
|
+
```scala
|
|
650
|
+
trait DbParamWriter {
|
|
651
|
+
def setInt(index: Int, value: Int): Unit
|
|
652
|
+
def setString(index: Int, value: String): Unit
|
|
653
|
+
def setBoolean(index: Int, value: Boolean): Unit
|
|
654
|
+
def setBigDecimal(index: Int, value: java.math.BigDecimal): Unit
|
|
655
|
+
def setInstant(index: Int, value: java.time.Instant): Unit
|
|
656
|
+
// ... and all other parameter types
|
|
657
|
+
def setNull(index: Int, sqlType: Int): Unit
|
|
658
|
+
}
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
Like `DbResultReader`, `DbParamWriter` is not used directly in application code. `Frag#update` and `Repo` CRUD methods create a `JdbcParamWriter` wrapping a `java.sql.PreparedStatement` and pass it to `DbCodec#writeValue` internally.
|
|
662
|
+
|
|
663
|
+
## Integration
|
|
664
|
+
|
|
665
|
+
`DbCodec` sits at the center of the `sql` module's layered architecture. The diagram below shows how it connects to its neighbours:
|
|
666
|
+
|
|
667
|
+
```
|
|
668
|
+
Schema[A]
|
|
669
|
+
│
|
|
670
|
+
▼ (DbCodecDeriver)
|
|
671
|
+
DbCodec[A] ◄────────────────────────────────┐
|
|
672
|
+
│ │
|
|
673
|
+
├──► Table[A] │
|
|
674
|
+
│ └──► Repo[E, ID] │
|
|
675
|
+
│ │ │
|
|
676
|
+
│ CRUD methods │
|
|
677
|
+
│ │ │
|
|
678
|
+
└──► Frag ──────────┘ │
|
|
679
|
+
(sql"..." interpolator) │
|
|
680
|
+
│ │
|
|
681
|
+
Transactor#connect/transact │
|
|
682
|
+
│ │
|
|
683
|
+
DbCon / DbTx │
|
|
684
|
+
/ \ │
|
|
685
|
+
DbResultReader DbParamWriter ────────────┘
|
|
686
|
+
(readValue) (writeValue)
|
|
687
|
+
```
|