@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,341 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: index
|
|
3
|
+
title: "SQL Module"
|
|
4
|
+
description: "Reference index for the zio-blocks-sql module: schema-driven SQL fragments, bidirectional codecs, CRUD repositories, and transaction management."
|
|
5
|
+
keywords:
|
|
6
|
+
- "Schema-driven SQL Operations"
|
|
7
|
+
- "DbCodec Schema Derivation"
|
|
8
|
+
- "SQL Fragments"
|
|
9
|
+
- "Bidirectional Codecs"
|
|
10
|
+
- "CRUD Repositories"
|
|
11
|
+
- "Transactor Connection Management"
|
|
12
|
+
- "ZIO Integration"
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
The `zio-blocks-sql` module provides type-safe, schema-driven SQL query building and execution on relational databases. Its core types — `DbCodec`, `Frag`, `Table`, `Repo`, `Transactor`, `DbCon`, and `DbTx` — form a layered system: codecs map Scala types to database columns, fragments carry parameterized SQL built via string interpolation, repositories expose CRUD operations derived at compile time from a schema, and a transactor manages the connection lifecycle with automatic commit and rollback.
|
|
16
|
+
|
|
17
|
+
## Motivation
|
|
18
|
+
|
|
19
|
+
Relational database access in Scala typically involves one of three trade-offs:
|
|
20
|
+
|
|
21
|
+
- Raw JDBC requires manual row mapping and string concatenation (SQL injection risk, tedious boilerplate)
|
|
22
|
+
- Reflection-based ORMs hide SQL entirely (opaque at compile time, difficult to optimize)
|
|
23
|
+
- Lifted-embedding DSLs like Slick require learning a parallel query language on top of SQL.
|
|
24
|
+
|
|
25
|
+
The `zio-blocks-sql` module takes a different path: it uses ZIO Blocks' `Schema` system for compile-time column metadata and familiar string interpolation for SQL text, giving you type safety without losing SQL's expressiveness.
|
|
26
|
+
|
|
27
|
+
Key advantages of the `zio-blocks-sql` module are:
|
|
28
|
+
|
|
29
|
+
- **Schema-driven derivation.** `DbCodec`, `Table`, and `Repo` all derive from the same `Schema[A]`, keeping column names, SQL types, and nullability in sync with your Scala data model automatically.
|
|
30
|
+
- **No runtime reflection.** Derivation runs at compile time through the `Deriver[DbCodec]` framework, producing zero-overhead codecs with no reflective calls at runtime.
|
|
31
|
+
- **Compile-time SQL parameterization.** The `sql"..."` macro interpolator verifies that every interpolated value has a `DbParam[A]` instance at compile time, converts it to a `DbValue`, and binds it to a `?` placeholder — no string concatenation, no SQL injection.
|
|
32
|
+
- **Composable fragments.** `Frag` values compose via `Frag#++`, letting you build reusable WHERE clauses, ORDER BY expressions, and pagination helpers that render correctly for any `SqlDialect`.
|
|
33
|
+
- **Explicit transaction boundaries.** `Transactor#transact` disables auto-commit, commits on success, and rolls back on any exception — the connection is always closed whether the block succeeds or throws.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
The core SQL module and the ZIO integration module publish separately. Add the artifacts you need to your build:
|
|
38
|
+
|
|
39
|
+
```scala
|
|
40
|
+
// Core SQL module (cross-built for JVM and Scala.js; the JDBC-backed
|
|
41
|
+
// JdbcTransactor implementation itself is JVM-only)
|
|
42
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-sql" % "0.0.51"
|
|
43
|
+
|
|
44
|
+
// ZIO integration (lifts JDBC into ZIO effects; JVM-only)
|
|
45
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-sql-zio" % "0.0.51"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Both modules require a JDBC driver on the classpath (for example, `org.xerial:sqlite-jdbc` for SQLite or `org.postgresql:postgresql` for PostgreSQL). Swap the JDBC URL and `SqlDialect` constant to switch databases.
|
|
49
|
+
|
|
50
|
+
## Overview
|
|
51
|
+
|
|
52
|
+
The module is organized into three groups: core types you interact with directly in application code, supporting types that implement the protocol layer, and a ZIO integration layer that lifts synchronous operations into the effect system.
|
|
53
|
+
|
|
54
|
+
### Core Types
|
|
55
|
+
|
|
56
|
+
These seven types form the public API for everyday SQL work:
|
|
57
|
+
|
|
58
|
+
- **[`DbCodec`](./db-codec.md)** — Converts between Scala values and database columns. Read values from result sets by column label or position; write values as bound parameters. Derived automatically from `Schema[A]`.
|
|
59
|
+
- **[`Frag`](./frag.md)** — An immutable SQL fragment built with the `sql"..."` interpolator. Holds literal SQL text and typed parameters separately, preventing SQL injection. Executed via `query`, `queryOne`, `update`, and other extension methods.
|
|
60
|
+
- **[`Table`](./table.md)** — Binds a Scala type to a database table: table name, codec, and column metadata. Derived from a `Schema` using naming conventions. Provides DDL generation via `createTable` and `dropTable`.
|
|
61
|
+
- **[`Repo`](./repo.md)** — Type-safe CRUD repository providing `all`, `find`, `insert`, `update`, `delete`, and other standard operations. All SQL is pre-built at construction time.
|
|
62
|
+
- **[`Transactor`](./transactor.md)** — Entry point for executing SQL. `connect` opens a connection; `transact` adds automatic commit/rollback. `JdbcTransactor` is the JDBC implementation.
|
|
63
|
+
- **[`DbCon`](./db-con.md)** — Implicit context available inside a `Transactor` block. Carries the connection, dialect, and logger. Threaded through all SQL operations automatically.
|
|
64
|
+
- **[`DbTx`](./db-tx.md)** — A `DbCon` subtype marking transactional scope (inside `Transactor#transact`). Extends `DbCon` so transactional and non-transactional operations compose freely.
|
|
65
|
+
|
|
66
|
+
### Supporting Types
|
|
67
|
+
|
|
68
|
+
These types implement the protocol and derivation layers and are rarely used directly in application code:
|
|
69
|
+
|
|
70
|
+
- **[`DbValue`](./db-value.md)** — Sealed ADT representing typed database values. Used internally by `DbCodec` and `Frag` to hold parameters before binding.
|
|
71
|
+
- **[`DbParam`](./db-param.md)** — Typeclass converting Scala values to `DbValue` for the `sql"..."` interpolator. Instances provided for all common types, `Option[A]`, and `Maybe[A]`.
|
|
72
|
+
- **[`SqlDialect`](./sql-dialect.md)** — Encodes database-specific SQL: type names for DDL and parameter placeholder tokens. `PostgreSQL` and `SQLite` are built-in.
|
|
73
|
+
- **[`SqlLogger`](./sql-logger.md)** — Hook for observing query execution. Receives SQL, parameters, duration, and row count on success or error.
|
|
74
|
+
- **[`SqlNameMapper`](./sql-name-mapper.md)** — Maps Scala field names to SQL column names. `SnakeCase` (default converts `camelCase` to `snake_case`), `Identity`, or `Custom` function.
|
|
75
|
+
- **[`TableMetadata`](./table-metadata.md)** — Derives column metadata from a `Schema`: names, types, and nullability. Used by `Table.derived` to populate table structure.
|
|
76
|
+
- **[`Ddl`](./ddl.md)** — Generates `CREATE TABLE IF NOT EXISTS` and `DROP TABLE IF EXISTS` fragments from column definitions.
|
|
77
|
+
- **[`DbConnection`](./db-connection.md)** — Abstraction over JDBC `Connection`. Prepares statements, controls transactions, and manages lifecycle.
|
|
78
|
+
- **[`DbResultReader`](./db-result-reader.md)** — Reads column values from result sets by label or 1-based position. Supports null detection via `wasNull`. Used by `DbCodec`.
|
|
79
|
+
- **[`DbParamWriter`](./db-param-writer.md)** — Binds parameter values to prepared statements. Covers all primitive, temporal, and UUID types. Used by `DbCodec`.
|
|
80
|
+
- **[`DbCodecDeriver`](./db-codec-deriver.md)** — Schema-driven codec derivation engine. Converts `Schema[A]` to `DbCodec[A]`, handling primitives, records, options, and JSONB types.
|
|
81
|
+
- **[`TransactorZIO`](./transactor-zio.md)** — ZIO integration for `Transactor`. Runs SQL effects on the blocking thread pool with proper resource cleanup and interrupt handling. Includes `ZLayer` support.
|
|
82
|
+
|
|
83
|
+
## How They Work Together
|
|
84
|
+
|
|
85
|
+
Five interconnected flows define how the module's types cooperate: codec derivation, SQL assembly, execution context, entity mapping, and transaction lifecycle. Understanding these flows shows why each type exists and how to compose them.
|
|
86
|
+
|
|
87
|
+
The typical workflow proceeds in this order:
|
|
88
|
+
|
|
89
|
+
1. Define a `Schema[A]` for your domain type (usually with `Schema.derived`).
|
|
90
|
+
2. Call `Repo.derived` (or `Table.derived` + `Repo.apply`) to produce a `Repo[E, ID]` — all SQL is pre-built here.
|
|
91
|
+
3. Create a `JdbcTransactor` from a JDBC URL or `DataSource`.
|
|
92
|
+
4. Open a connection with `Transactor#connect` (for reads) or `Transactor#transact` (for writes), which supplies a `DbCon` or `DbTx` context.
|
|
93
|
+
5. Inside the context block, call `Repo` methods or execute `sql"..."` fragments directly.
|
|
94
|
+
|
|
95
|
+
The following diagram shows the data-flow relationships between types:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
Schema[A]
|
|
99
|
+
│
|
|
100
|
+
┌────────────────┼──────────────────┐
|
|
101
|
+
│ │ │
|
|
102
|
+
▼ ▼ ▼
|
|
103
|
+
DbCodecDeriver TableMetadata deriveTableName
|
|
104
|
+
│ .columnsFor() (TableNamingPolicy
|
|
105
|
+
▼ │ + SqlNameMapper)
|
|
106
|
+
DbCodec[A] ◄──────────┘ │
|
|
107
|
+
│ │
|
|
108
|
+
└───────────────────────────────────┘
|
|
109
|
+
│
|
|
110
|
+
Table[A](name, codec, columnsMeta)
|
|
111
|
+
│
|
|
112
|
+
Repo[E, ID] sql"..." → Frag
|
|
113
|
+
─ all / find ─ frag.query[A]
|
|
114
|
+
─ insert / update / delete ─ frag.update
|
|
115
|
+
│ │
|
|
116
|
+
┌───────┴─────────────────────────────┘
|
|
117
|
+
│
|
|
118
|
+
▼
|
|
119
|
+
Transactor
|
|
120
|
+
├─ .connect { ... } (non-transactional, DbCon in scope)
|
|
121
|
+
└─ .transact { ... } (commit / rollback, DbTx in scope)
|
|
122
|
+
│
|
|
123
|
+
DbCon / DbTx
|
|
124
|
+
┌────────────┼────────────┐
|
|
125
|
+
▼ ▼ ▼
|
|
126
|
+
DbConnection SqlDialect SqlLogger
|
|
127
|
+
(JDBC wrapper) (PostgreSQL (observability)
|
|
128
|
+
| SQLite)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The `sql"..."` interpolator is defined as an extension on `StringContext`. At compile time the macro verifies that every interpolated expression has a `DbParam[A]` instance, converts it to a `DbValue`, and assembles a `Frag` with literal SQL in `parts` and the typed values in `params`. The `Frag` carries no dialect-specific rendering until `Frag#sql(dialect)` is called, so the same fragment works with PostgreSQL and SQLite unchanged.
|
|
132
|
+
|
|
133
|
+
A complete end-to-end example showing schema definition, table setup, repository construction, and mixed repository/fragment queries follows:
|
|
134
|
+
|
|
135
|
+
```scala
|
|
136
|
+
import zio.blocks.sql._
|
|
137
|
+
import zio.blocks.schema.Schema
|
|
138
|
+
import zio.blocks.maybe.Maybe
|
|
139
|
+
|
|
140
|
+
case class User(id: Int, name: String, email: String)
|
|
141
|
+
object User {
|
|
142
|
+
implicit val schema: Schema[User] = Schema.derived
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 1. Create transactor and repository
|
|
146
|
+
val tx = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
147
|
+
// tx: JdbcTransactor = zio.blocks.sql.JdbcTransactor@3fe7357a
|
|
148
|
+
val repo = Repo.derived[User, Int]("users", "id", _.id)
|
|
149
|
+
// repo: Repo[User, Int] = zio.blocks.sql.Repo$DerivedRepo@4206125b
|
|
150
|
+
|
|
151
|
+
// 2. Set up schema and run a transactional workflow
|
|
152
|
+
tx.transact {
|
|
153
|
+
// Create table using derived DDL
|
|
154
|
+
repo.table.createTable(summon[DbTx].dialect).update
|
|
155
|
+
|
|
156
|
+
// Insert via repository (uses pre-built INSERT fragment)
|
|
157
|
+
repo.insert(User(1, "Alice", "alice@example.com"))
|
|
158
|
+
repo.insert(User(2, "Bob", "bob@example.com"))
|
|
159
|
+
|
|
160
|
+
// Read via repository
|
|
161
|
+
val alice: Maybe[User] = repo.find(1)
|
|
162
|
+
|
|
163
|
+
// Read via raw SQL fragment — composes freely with repo operations
|
|
164
|
+
val aUsers: List[User] =
|
|
165
|
+
sql"SELECT id, name, email FROM users WHERE name LIKE ${"A%"}".query[User]
|
|
166
|
+
|
|
167
|
+
// Update and delete
|
|
168
|
+
repo.update(User(1, "Alice Smith", "alice.smith@example.com"))
|
|
169
|
+
repo.delete(2)
|
|
170
|
+
|
|
171
|
+
(alice, aUsers)
|
|
172
|
+
}
|
|
173
|
+
// res1: Tuple2[Maybe[User], List[User]] = (
|
|
174
|
+
// User(id = 1, name = "Alice", email = "alice@example.com"),
|
|
175
|
+
// List(User(id = 1, name = "Alice", email = "alice@example.com"))
|
|
176
|
+
// )
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Inside `transact`, auto-commit is disabled. If any call throws, the entire block rolls back and the exception propagates. On normal return, the transaction commits and the connection closes.
|
|
180
|
+
|
|
181
|
+
## Common Patterns
|
|
182
|
+
|
|
183
|
+
The module is designed around a small set of patterns that appear throughout most application code. Recognizing these patterns makes it easy to choose the right approach for each situation.
|
|
184
|
+
|
|
185
|
+
### Schema-Driven Derivation
|
|
186
|
+
|
|
187
|
+
`DbCodec`, `Table`, and `Repo` all derive from a single `Schema[A]`. Derivation respects `@Modifier` annotations — `@Modifier.rename` overrides a column name, `@Modifier.transient` excludes a field from the codec, and `@Modifier.config("sql.table_name", "my_table")` overrides the table name. This means your Scala type definition is the single source of truth for column names, types, and nullability:
|
|
188
|
+
|
|
189
|
+
```scala
|
|
190
|
+
import zio.blocks.sql._
|
|
191
|
+
import zio.blocks.schema.{Schema, Modifier}
|
|
192
|
+
|
|
193
|
+
case class BlogPost(
|
|
194
|
+
@Modifier.rename("post_id") id: Int,
|
|
195
|
+
title: String,
|
|
196
|
+
@Modifier.transient authorHandle: String = "" // excluded from SQL
|
|
197
|
+
)
|
|
198
|
+
object BlogPost {
|
|
199
|
+
implicit val schema: Schema[BlogPost] = Schema.derived
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
val repo = Repo.derived[BlogPost, Int]("post_id", _.id)
|
|
203
|
+
// repo: Repo[BlogPost, Int] = zio.blocks.sql.Repo$DerivedRepo@243d4111
|
|
204
|
+
repo.table.name
|
|
205
|
+
// res3: String = "blog_post"
|
|
206
|
+
repo.table.codec.columns
|
|
207
|
+
// res4: IndexedSeq[String] = Vector("post_id", "title")
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Implicit Context Threading
|
|
211
|
+
|
|
212
|
+
Every SQL operation — `Frag` execution and `Repo` CRUD — requires an implicit `DbCon` (or `DbTx`) in scope. The context carries the connection, dialect, and logger, but you never pass it explicitly. Calling code inside `Transactor#connect` or `Transactor#transact` automatically has the context available, and helper methods can propagate it with a `using` parameter:
|
|
213
|
+
|
|
214
|
+
```scala
|
|
215
|
+
import zio.blocks.sql._
|
|
216
|
+
import zio.blocks.schema.Schema
|
|
217
|
+
|
|
218
|
+
case class Product(id: Int, name: String, price: BigDecimal)
|
|
219
|
+
object Product { implicit val schema: Schema[Product] = Schema.derived }
|
|
220
|
+
|
|
221
|
+
def cheapProducts(maxPrice: BigDecimal)(using DbCon): List[Product] =
|
|
222
|
+
sql"SELECT id, name, price FROM product WHERE price < $maxPrice".query[Product]
|
|
223
|
+
|
|
224
|
+
val tx = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
225
|
+
tx.connect {
|
|
226
|
+
// `cheapProducts` picks up the DbCon automatically
|
|
227
|
+
val items = cheapProducts(BigDecimal("9.99"))
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Multi-Column Codecs
|
|
232
|
+
|
|
233
|
+
A single `DbCodec[A]` can span multiple database columns. When you use a multi-column type directly in an `sql"..."` expression, the `fromDbCodec` `DbParam` instance throws at runtime because it cannot collapse multiple values into a single `?` placeholder. Instead, use `Frag.values` for multi-row inserts or write the columns explicitly in the fragment:
|
|
234
|
+
|
|
235
|
+
```scala
|
|
236
|
+
import zio.blocks.sql._
|
|
237
|
+
import zio.blocks.schema.Schema
|
|
238
|
+
|
|
239
|
+
case class Point(x: Double, y: Double)
|
|
240
|
+
object Point { implicit val schema: Schema[Point] = Schema.derived }
|
|
241
|
+
|
|
242
|
+
// Multi-row insert using Frag.values — one (?, ?) tuple per row
|
|
243
|
+
val points = List(Point(1.0, 2.0), Point(3.0, 4.0))
|
|
244
|
+
// points: List[Point] = List(Point(x = 1.0, y = 2.0), Point(x = 3.0, y = 4.0))
|
|
245
|
+
val frag = Frag.literal("INSERT INTO point (x, y) VALUES ") ++ Frag.values(points)
|
|
246
|
+
// frag: Frag = Frag(
|
|
247
|
+
// parts = Vector("INSERT INTO point (x, y) VALUES (", ", ", "), (", ", ", ")"),
|
|
248
|
+
// params = Vector(DbDouble(1.0), DbDouble(2.0), DbDouble(3.0), DbDouble(4.0))
|
|
249
|
+
// )
|
|
250
|
+
frag.sql(SqlDialect.SQLite)
|
|
251
|
+
// res7: String = "INSERT INTO point (x, y) VALUES (?, ?), (?, ?)"
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Type-Safe SQL Parameterization
|
|
255
|
+
|
|
256
|
+
The `sql"..."` interpolator accepts any Scala value for which a `DbParam[A]` exists. The macro checks this at compile time and binds the value to a `?` placeholder, preventing SQL injection regardless of the value's content. All standard scalar types have built-in instances, `Option[A]` binds to `NULL` or the inner value, and custom types with a `DbCodec[A]` automatically gain a `DbParam[A]`:
|
|
257
|
+
|
|
258
|
+
```scala
|
|
259
|
+
import zio.blocks.sql._
|
|
260
|
+
|
|
261
|
+
val userId: Int = 42
|
|
262
|
+
// userId: Int = 42
|
|
263
|
+
val namePattern: String = "%alice%"
|
|
264
|
+
// namePattern: String = "%alice%"
|
|
265
|
+
val active: Option[Boolean] = Some(true)
|
|
266
|
+
// active: Option[Boolean] = Some(true)
|
|
267
|
+
|
|
268
|
+
// All three are compile-time safe — no string concatenation
|
|
269
|
+
val frag =
|
|
270
|
+
sql"SELECT * FROM users WHERE id = $userId AND name LIKE $namePattern AND active = $active"
|
|
271
|
+
// frag: Frag = Frag(
|
|
272
|
+
// parts = ArraySeq(
|
|
273
|
+
// "SELECT * FROM users WHERE id = ",
|
|
274
|
+
// " AND name LIKE ",
|
|
275
|
+
// " AND active = ",
|
|
276
|
+
// ""
|
|
277
|
+
// ),
|
|
278
|
+
// params = Vector(DbInt(42), DbString("%alice%"), DbBoolean(true))
|
|
279
|
+
// )
|
|
280
|
+
frag.sql(SqlDialect.SQLite)
|
|
281
|
+
// res9: String = "SELECT * FROM users WHERE id = ? AND name LIKE ? AND active = ?"
|
|
282
|
+
frag.params
|
|
283
|
+
// res10: IndexedSeq[DbValue] = Vector(
|
|
284
|
+
// DbInt(42),
|
|
285
|
+
// DbString("%alice%"),
|
|
286
|
+
// DbBoolean(true)
|
|
287
|
+
// )
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### JSONB Serialization for Complex Types
|
|
291
|
+
|
|
292
|
+
When a field's type is not directly representable as a single column — such as `List[A]`, `Map[K, V]`, or a sealed trait with multiple variants — `DbCodecDeriver` automatically uses `DbCodec.jsonb[A]` to store and retrieve the value as a JSON-encoded `TEXT` or `JSONB` column. This keeps complex nested data in a single column without requiring a separate table:
|
|
293
|
+
|
|
294
|
+
```scala
|
|
295
|
+
import zio.blocks.sql._
|
|
296
|
+
|
|
297
|
+
case class Order(id: Int, tags: List[String], metadata: Map[String, String]) derives DbCodec
|
|
298
|
+
|
|
299
|
+
// `tags` and `metadata` are encoded via DbCodec.jsonb when read/written through Frag/Repo
|
|
300
|
+
val codec = DbCodec[Order]
|
|
301
|
+
// codec: DbCodec[Order] = zio.blocks.sql.DbCodecDeriver$$anon$20@10cf0d2d
|
|
302
|
+
codec.columns
|
|
303
|
+
// res12: IndexedSeq[String] = Vector("id", "tags", "metadata")
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Optional and Nullable Handling
|
|
307
|
+
|
|
308
|
+
`Option[A]` and `Maybe[A]` map to a single nullable column. Reading a `NULL` from the database produces `None` or `Maybe.absent`; writing `None` or `Maybe.absent` binds `NULL` to the parameter. For non-optional types, encountering an unexpected `NULL` throws `IllegalStateException` at read time, surfacing schema mismatches immediately rather than silently coercing `NULL` to a default:
|
|
309
|
+
|
|
310
|
+
```scala
|
|
311
|
+
import zio.blocks.sql._
|
|
312
|
+
import zio.blocks.schema.Schema
|
|
313
|
+
|
|
314
|
+
case class Profile(userId: Int, bio: Option[String], avatarUrl: Option[String])
|
|
315
|
+
object Profile { implicit val schema: Schema[Profile] = Schema.derived }
|
|
316
|
+
|
|
317
|
+
// bio and avatar_url become nullable TEXT columns in the generated DDL
|
|
318
|
+
val repo = Repo.derived[Profile, Int]("user_id", _.userId)
|
|
319
|
+
|
|
320
|
+
given DbCon = ???
|
|
321
|
+
// repo.insert(Profile(1, None, None)) binds NULL for both optional columns
|
|
322
|
+
repo.insert(Profile(1, None, None))
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Integration Points
|
|
326
|
+
|
|
327
|
+
The `zio-blocks-sql` module sits at the intersection of several other ZIO Blocks modules and integrates with the JVM JDBC ecosystem.
|
|
328
|
+
|
|
329
|
+
The primary dependency is **`zio-blocks-schema`**. `Schema[A]` (specifically its `Reflect` tree) is the source of all compile-time type metadata: field names, types, nullability, and annotations. `DbCodecDeriver` extends `Deriver[DbCodec]`, the same framework used by JSON, Avro, and other codec modules in the schema module's built-in codec suite. The `@Modifier.rename`, `@Modifier.transient`, and `@Modifier.config` annotations attach SQL-specific configuration to individual fields and types without coupling the domain model to the `zio-blocks-sql` module.
|
|
330
|
+
|
|
331
|
+
The module also depends on **`zio-blocks-maybe`** for `Maybe[A]` support. `Maybe[A]` has distinct absent and present semantics (unlike `Option` which cannot distinguish `Some(null)` from a genuinely absent value), and both `DbCodec` and `DbParam` provide `Maybe[A]` instances alongside `Option[A]`.
|
|
332
|
+
|
|
333
|
+
For transparent opaque-type support the module integrates with **`As[A, B]`** from `zio-blocks-schema`. If an opaque type has a `DbCodec` for its underlying type and an `As` conversion, `DbCodec` derives an instance for the opaque type automatically via `dbCodecFromAs` without requiring a hand-written codec.
|
|
334
|
+
|
|
335
|
+
The JDBC layer is isolated behind three abstractions — `DbConnection`, `DbResultReader`, and `DbParamWriter` — so the `JdbcTransactor` can be replaced by an alternate backend (for example, a WebSQL or node-postgres adapter on Scala.js) without changing any application code that depends only on the shared `zio-blocks-sql` module.
|
|
336
|
+
|
|
337
|
+
The **`zio-blocks-sql-zio` module** provides `TransactorZIO`, a ZIO-aware wrapper around `JdbcTransactor`. It exposes two execution models: blocking wrappers (`TransactorZIO#connect`, `TransactorZIO#transact`) that run synchronous bodies with `ZIO.attemptBlocking`, and effect-aware methods (`TransactorZIO#connectZIO`, `TransactorZIO#transactZIO`) that bracket connections via `ZIO.acquireRelease` so connections are always released even on fiber interruption. A `ZLayer` constructor (`TransactorZIO.layer`) integrates with ZIO's dependency injection system.
|
|
338
|
+
|
|
339
|
+
## See Also
|
|
340
|
+
|
|
341
|
+
- **[SQL-ZIO Integration Reference](../sql-zio.md)** — Reference page for `TransactorZIO` and the ZIO `ZLayer` integration.
|