@zio.dev/zio-blocks 0.0.33 → 0.0.55
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/adr/2026-07-18-data-migration.md +123 -0
- package/guides/async-getting-started.md +687 -0
- package/guides/compile-time-resource-safety-with-scope.md +21 -16
- package/guides/getting-started-with-mux.md +1395 -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 +640 -165
- package/guides/sql-checked-interpolation.md +173 -0
- package/guides/sql-transactions.md +286 -0
- package/guides/telemetry-guide.md +1130 -0
- package/guides/zio-schema-migration.md +29 -22
- package/index.md +248 -389
- 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 +1499 -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/config-decoder.md +460 -0
- package/reference/config/config-source.md +489 -0
- package/reference/config/errors.md +278 -0
- package/reference/config/flags.md +369 -0
- package/reference/config/formats.md +314 -0
- package/reference/config/index.md +304 -0
- package/reference/config/rollout.md +336 -0
- package/reference/context.md +9 -52
- package/reference/data-migration.md +269 -0
- package/reference/datastar/attributes.md +302 -0
- package/reference/datastar/events.md +234 -0
- package/reference/datastar/index.md +256 -0
- package/reference/datastar/signals.md +230 -0
- package/reference/datastar/sse.md +295 -0
- package/reference/datastar.md +346 -0
- package/reference/docs.md +1461 -345
- package/reference/endpoint/auth-type.md +146 -0
- package/reference/endpoint/bulk-creation.md +96 -0
- package/reference/endpoint/endpoint.md +297 -0
- package/reference/endpoint/http-codec.md +249 -0
- package/reference/endpoint/index.md +745 -0
- package/reference/endpoint/path-codec.md +225 -0
- package/reference/endpoint/route-pattern.md +194 -0
- package/reference/endpoint/route-tree.md +111 -0
- package/reference/endpoint/segment-codec.md +199 -0
- package/reference/html.md +1424 -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 +807 -0
- package/reference/htmx/response-headers.md +240 -0
- package/reference/http-model/headers.md +735 -0
- package/reference/http-model/index.md +49 -0
- package/reference/http-model/model.md +1517 -0
- package/reference/http-model/schema-codecs.md +522 -0
- package/reference/http-model/schema.md +750 -0
- package/reference/http-model/server-sent-event.md +341 -0
- package/reference/jwt.md +195 -0
- package/reference/maybe.md +943 -0
- package/reference/media-type.md +2 -2
- package/reference/mux.md +254 -0
- package/reference/mux.mdx +828 -0
- package/reference/openapi.md +1351 -0
- package/reference/projection.md +654 -0
- package/reference/resource-management/defer-handle.md +1 -1
- package/reference/resource-management/resource.md +31 -98
- package/reference/resource-management/scope.md +28 -220
- package/reference/resource-management/wire.md +5 -55
- 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 +185 -0
- package/reference/ringbuffer/mpsc.mdx +164 -0
- package/reference/ringbuffer/spmc.mdx +108 -0
- package/reference/ringbuffer/spsc.mdx +416 -0
- package/reference/{allows.md → schema/allows.md} +4 -100
- package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
- package/reference/{binding.md → schema/binding.md} +3 -4
- package/reference/schema/built-in-codecs/avro.md +451 -0
- package/reference/schema/built-in-codecs/bson.md +510 -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} +11 -11
- package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +196 -5
- package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
- package/reference/schema/format.md +92 -0
- package/reference/schema/index.md +52 -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} +167 -72
- package/reference/schema/reflect-transformer.md +140 -0
- 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/schema-search.md +263 -0
- package/reference/{schema.md → schema/schema.md} +22 -2
- 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 +1032 -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 +148 -0
- package/reference/sql/db-tx.md +114 -0
- package/reference/sql/db-value.md +41 -0
- package/reference/sql/ddl.md +85 -0
- package/reference/sql/frag.md +288 -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 +363 -0
- package/reference/sql-zio.md +112 -0
- package/reference/streams/core/index.md +32 -0
- package/reference/streams/core/pipeline.md +854 -0
- package/reference/streams/core/sink.md +1404 -0
- package/reference/streams/core/stream.md +3236 -0
- package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
- package/reference/streams/execution-and-compatibility/index.md +35 -0
- package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
- package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
- package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
- package/reference/streams/index.md +726 -0
- package/reference/streams/primitives/index.md +30 -0
- package/reference/streams/primitives/reader.md +1992 -0
- package/reference/streams/primitives/writer.md +1201 -0
- package/reference/telemetry/common/any-value.md +90 -0
- package/reference/telemetry/common/attribute-key.md +87 -0
- package/reference/telemetry/common/attributes.md +118 -0
- package/reference/telemetry/common/index.md +39 -0
- package/reference/telemetry/common/instrumentation-scope.md +24 -0
- package/reference/telemetry/common/resource.md +34 -0
- package/reference/telemetry/index.md +311 -0
- package/reference/telemetry/logging/index.md +197 -0
- package/reference/telemetry/logging/log-enrichment.md +72 -0
- package/reference/telemetry/logging/log-formatter.md +100 -0
- package/reference/telemetry/logging/log-record-processor.md +56 -0
- package/reference/telemetry/logging/log-record.md +44 -0
- package/reference/telemetry/logging/log-writer.md +64 -0
- package/reference/telemetry/logging/logger-provider.md +142 -0
- package/reference/telemetry/logging/logger.md +83 -0
- package/reference/telemetry/logging/severity.md +62 -0
- package/reference/telemetry/metrics/index.md +150 -0
- package/reference/telemetry/metrics/instruments.md +183 -0
- package/reference/telemetry/metrics/labeled-instruments.md +74 -0
- package/reference/telemetry/metrics/meter-provider.md +76 -0
- package/reference/telemetry/metrics/meter.md +98 -0
- package/reference/telemetry/metrics/metric-data.md +57 -0
- package/reference/telemetry/otel/custom-exporter.md +216 -0
- package/reference/telemetry/otel/index.md +212 -0
- package/reference/telemetry/tracing/index.md +155 -0
- package/reference/telemetry/tracing/sampler.md +89 -0
- package/reference/telemetry/tracing/span-builder.md +57 -0
- package/reference/telemetry/tracing/span-context.md +39 -0
- package/reference/telemetry/tracing/span-data.md +32 -0
- package/reference/telemetry/tracing/span-kind.md +55 -0
- package/reference/telemetry/tracing/span-processor.md +53 -0
- package/reference/telemetry/tracing/span-status.md +47 -0
- package/reference/telemetry/tracing/span.md +117 -0
- package/reference/telemetry/tracing/tracer-provider.md +91 -0
- package/reference/telemetry/tracing/tracer.md +52 -0
- package/reference/typeid.md +5 -83
- package/sidebars.js +376 -43
- package/undocumented-report.md +528 -270
- 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,448 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: table
|
|
3
|
+
title: "Table"
|
|
4
|
+
description: "Reference page for Table[A], the entity-to-table metadata binding in the sql module for schema-driven JDBC mapping and DDL generation."
|
|
5
|
+
keywords:
|
|
6
|
+
- "Table Schema Derivation"
|
|
7
|
+
- "DDL Generation SQL"
|
|
8
|
+
- "TableNamingPolicy Column Mapping"
|
|
9
|
+
- "ColumnMeta Metadata"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
`Table[A]` is the metadata binding between a Scala type `A` and a specific database table in the `sql` module. It holds the table name, a `DbCodec[A]` for reading and writing rows, and an `IndexedSeq[ColumnMeta]` describing each column's name, SQL type representative, and nullability. `Table` provides both type-safe column access and dialect-aware DDL generation without any ORM runtime or session lifecycle.
|
|
13
|
+
|
|
14
|
+
The structural shape of `Table` is:
|
|
15
|
+
|
|
16
|
+
```scala
|
|
17
|
+
final case class Table[A](name: String, codec: DbCodec[A], columnsMeta: IndexedSeq[ColumnMeta]) {
|
|
18
|
+
def columns: IndexedSeq[String] = ???
|
|
19
|
+
|
|
20
|
+
def createTable(dialect: SqlDialect): Frag = ???
|
|
21
|
+
def dropTable: Frag = ???
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
object Table {
|
|
25
|
+
def derived[A](implicit schema: Schema[A]): Table[A] = ???
|
|
26
|
+
def derived[A](tableName: String)(implicit schema: Schema[A]): Table[A] = ???
|
|
27
|
+
def derived[A](namingPolicy: TableNamingPolicy)(implicit schema: Schema[A]): Table[A] = ???
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
The following example illustrates the core workflow: derive a table from a schema-equipped case class, inspect its column names, generate `CREATE TABLE` DDL, and finally generate `DROP TABLE` DDL:
|
|
34
|
+
|
|
35
|
+
```scala
|
|
36
|
+
import zio.blocks.sql._
|
|
37
|
+
import zio.blocks.schema.Schema
|
|
38
|
+
|
|
39
|
+
case class User(id: Int, name: String, email: String)
|
|
40
|
+
object User {
|
|
41
|
+
implicit val schema: Schema[User] = Schema.derived
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Derive the table binding — columns come from the schema; "user" is a
|
|
45
|
+
// reserved word in PostgreSQL, so the table name is overridden explicitly
|
|
46
|
+
val table = Table.derived[User]("users")
|
|
47
|
+
// table: Table[User] = Table(
|
|
48
|
+
// name = "users",
|
|
49
|
+
// codec = zio.blocks.sql.DbCodecDeriver$$anon$10@485654ba,
|
|
50
|
+
// columnsMeta = Vector(
|
|
51
|
+
// ColumnMeta(name = "id", dbValue = DbInt(0), nullable = false),
|
|
52
|
+
// ColumnMeta(name = "name", dbValue = DbString(""), nullable = false),
|
|
53
|
+
// ColumnMeta(name = "email", dbValue = DbString(""), nullable = false)
|
|
54
|
+
// )
|
|
55
|
+
// )
|
|
56
|
+
table.name
|
|
57
|
+
// res1: String = "users"
|
|
58
|
+
table.columns
|
|
59
|
+
// res2: IndexedSeq[String] = Vector("id", "name", "email")
|
|
60
|
+
|
|
61
|
+
// Emit dialect-aware DDL as Frag values
|
|
62
|
+
val createSql = table.createTable(SqlDialect.PostgreSQL).sql(SqlDialect.PostgreSQL)
|
|
63
|
+
// createSql: String = """CREATE TABLE IF NOT EXISTS users (
|
|
64
|
+
// id INTEGER NOT NULL,
|
|
65
|
+
// name TEXT NOT NULL,
|
|
66
|
+
// email TEXT NOT NULL
|
|
67
|
+
// )"""
|
|
68
|
+
val dropSql = table.dropTable.sql(SqlDialect.PostgreSQL)
|
|
69
|
+
// dropSql: String = "DROP TABLE IF EXISTS users"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Construction / Creating Instances
|
|
73
|
+
|
|
74
|
+
`Table` offers three `derived` factory methods on its companion object and one direct constructor via its primary constructor. All three `derived` overloads require a `Schema[A]` implicit.
|
|
75
|
+
|
|
76
|
+
### `Table.derived` — Derive using the default naming policy
|
|
77
|
+
|
|
78
|
+
`Table.derived[A]` inspects the `Schema[A]` implicit and applies `TableNamingPolicy.Singular` to produce the table name. This policy converts `CamelCase` Scala type names to `snake_case` SQL identifiers (for example, `UserProfile` becomes `user_profile`). The table name can be overridden by annotating the type with `@Modifier.config("sql.table_name", "my_table")`.
|
|
79
|
+
|
|
80
|
+
```scala
|
|
81
|
+
object Table {
|
|
82
|
+
def derived[A](implicit schema: Schema[A]): Table[A]
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The following example derives a table for a two-field case class and checks the resulting name and column list:
|
|
87
|
+
|
|
88
|
+
```scala
|
|
89
|
+
import zio.blocks.sql._
|
|
90
|
+
import zio.blocks.schema.Schema
|
|
91
|
+
|
|
92
|
+
case class BlogPost(title: String, body: String)
|
|
93
|
+
object BlogPost {
|
|
94
|
+
implicit val schema: Schema[BlogPost] = Schema.derived
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
val table = Table.derived[BlogPost]
|
|
98
|
+
// table: Table[BlogPost] = Table(
|
|
99
|
+
// name = "blog_post",
|
|
100
|
+
// codec = zio.blocks.sql.DbCodecDeriver$$anon$10@51cbb015,
|
|
101
|
+
// columnsMeta = Vector(
|
|
102
|
+
// ColumnMeta(name = "title", dbValue = DbString(""), nullable = false),
|
|
103
|
+
// ColumnMeta(name = "body", dbValue = DbString(""), nullable = false)
|
|
104
|
+
// )
|
|
105
|
+
// )
|
|
106
|
+
table.name
|
|
107
|
+
// res4: String = "blog_post"
|
|
108
|
+
table.columns
|
|
109
|
+
// res5: IndexedSeq[String] = Vector("title", "body")
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### `Table.derived` (with explicit table name) — Bypass naming policy and annotations
|
|
113
|
+
|
|
114
|
+
`Table.derived[A](tableName: String)` derives a table with the supplied name, ignoring both the `TableNamingPolicy` and any `@Modifier.config("sql.table_name", …)` annotation on the type. The column names and codec are still derived from the schema in the normal way. Use this overload when the desired SQL table name cannot be expressed by any naming policy.
|
|
115
|
+
|
|
116
|
+
```scala
|
|
117
|
+
object Table {
|
|
118
|
+
def derived[A](tableName: String)(implicit schema: Schema[A]): Table[A]
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The following example maps `UserProfile` to a table called `profiles` rather than the default `user_profile`:
|
|
123
|
+
|
|
124
|
+
```scala
|
|
125
|
+
import zio.blocks.sql._
|
|
126
|
+
import zio.blocks.schema.Schema
|
|
127
|
+
|
|
128
|
+
case class UserProfile(firstName: String, lastName: String)
|
|
129
|
+
object UserProfile {
|
|
130
|
+
implicit val schema: Schema[UserProfile] = Schema.derived
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
val table = Table.derived[UserProfile]("profiles")
|
|
134
|
+
// table: Table[UserProfile] = Table(
|
|
135
|
+
// name = "profiles",
|
|
136
|
+
// codec = zio.blocks.sql.DbCodecDeriver$$anon$10@1d451191,
|
|
137
|
+
// columnsMeta = Vector(
|
|
138
|
+
// ColumnMeta(name = "first_name", dbValue = DbString(""), nullable = false),
|
|
139
|
+
// ColumnMeta(name = "last_name", dbValue = DbString(""), nullable = false)
|
|
140
|
+
// )
|
|
141
|
+
// )
|
|
142
|
+
table.name
|
|
143
|
+
// res7: String = "profiles"
|
|
144
|
+
table.columns
|
|
145
|
+
// res8: IndexedSeq[String] = Vector("first_name", "last_name")
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
:::caution
|
|
149
|
+
The table name is validated as a SQL identifier immediately at construction time. Spaces, hyphens, or any character outside `[A-Za-z0-9_]` (with a letter or underscore as the first character) will cause `Table.derived` to throw `IllegalArgumentException`. For example, `Table.derived[UserProfile]("user profile")` throws with the message `Invalid SQL table identifier 'user profile'. Only ASCII letters, digits, and underscores are supported, and the first character must be a letter or underscore.`
|
|
150
|
+
:::
|
|
151
|
+
|
|
152
|
+
### `Table.derived` (with naming policy) — Control table name derivation
|
|
153
|
+
|
|
154
|
+
`Table.derived[A](namingPolicy: TableNamingPolicy)` derives a table and applies the supplied `TableNamingPolicy` to the type name when computing the table name. Use `TableNamingPolicy.Plural` for pluralized names, `TableNamingPolicy.Singular` (the default) for singular names, or `TableNamingPolicy.Custom(f)` for arbitrary transformations.
|
|
155
|
+
|
|
156
|
+
```scala
|
|
157
|
+
object Table {
|
|
158
|
+
def derived[A](namingPolicy: TableNamingPolicy)(implicit schema: Schema[A]): Table[A]
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The following example uses `TableNamingPolicy.Plural` so that `Category` maps to the table `categories`:
|
|
163
|
+
|
|
164
|
+
```scala
|
|
165
|
+
import zio.blocks.sql._
|
|
166
|
+
import zio.blocks.schema.Schema
|
|
167
|
+
|
|
168
|
+
case class Category(name: String)
|
|
169
|
+
object Category {
|
|
170
|
+
implicit val schema: Schema[Category] = Schema.derived
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
val singular = Table.derived[Category](TableNamingPolicy.Singular)
|
|
174
|
+
// singular: Table[Category] = Table(
|
|
175
|
+
// name = "category",
|
|
176
|
+
// codec = zio.blocks.sql.DbCodecDeriver$$anon$10@2e5dff9,
|
|
177
|
+
// columnsMeta = Vector(
|
|
178
|
+
// ColumnMeta(name = "name", dbValue = DbString(""), nullable = false)
|
|
179
|
+
// )
|
|
180
|
+
// )
|
|
181
|
+
singular.name
|
|
182
|
+
// res10: String = "category"
|
|
183
|
+
|
|
184
|
+
val plural = Table.derived[Category](TableNamingPolicy.Plural)
|
|
185
|
+
// plural: Table[Category] = Table(
|
|
186
|
+
// name = "categories",
|
|
187
|
+
// codec = zio.blocks.sql.DbCodecDeriver$$anon$10@54d4530a,
|
|
188
|
+
// columnsMeta = Vector(
|
|
189
|
+
// ColumnMeta(name = "name", dbValue = DbString(""), nullable = false)
|
|
190
|
+
// )
|
|
191
|
+
// )
|
|
192
|
+
plural.name
|
|
193
|
+
// res11: String = "categories"
|
|
194
|
+
|
|
195
|
+
val custom = Table.derived[Category](TableNamingPolicy.Custom(n => s"tbl_$n"))
|
|
196
|
+
// custom: Table[Category] = Table(
|
|
197
|
+
// name = "tbl_Category",
|
|
198
|
+
// codec = zio.blocks.sql.DbCodecDeriver$$anon$10@325f2633,
|
|
199
|
+
// columnsMeta = Vector(
|
|
200
|
+
// ColumnMeta(name = "name", dbValue = DbString(""), nullable = false)
|
|
201
|
+
// )
|
|
202
|
+
// )
|
|
203
|
+
custom.name
|
|
204
|
+
// res12: String = "tbl_Category"
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### `Table.apply` — Construct directly from codec and column metadata
|
|
208
|
+
|
|
209
|
+
The primary constructor accepts the table name, a `DbCodec[A]`, and an `IndexedSeq[ColumnMeta]` explicitly. SQL identifier validation runs for the table name and every column name at construction time. Use this constructor when you have a hand-written or externally produced codec rather than a schema-derived one.
|
|
210
|
+
|
|
211
|
+
```scala
|
|
212
|
+
final case class Table[A](name: String, codec: DbCodec[A], columnsMeta: IndexedSeq[ColumnMeta])
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
The following example builds a `Table` manually, supplying a pre-existing `DbCodec` and explicit column metadata:
|
|
216
|
+
|
|
217
|
+
```scala
|
|
218
|
+
import zio.blocks.sql._
|
|
219
|
+
|
|
220
|
+
case class Tag(id: Int, label: String) derives DbCodec
|
|
221
|
+
|
|
222
|
+
// columnsMeta must describe the same columns, in the same order, as the codec;
|
|
223
|
+
// nullable must match the field's actual optionality (Tag.label is non-optional)
|
|
224
|
+
val meta = IndexedSeq(
|
|
225
|
+
ColumnMeta("id", DbValue.DbInt(0), nullable = false),
|
|
226
|
+
ColumnMeta("label", DbValue.DbString(""), nullable = false)
|
|
227
|
+
)
|
|
228
|
+
// meta: IndexedSeq[ColumnMeta] = Vector(
|
|
229
|
+
// ColumnMeta(name = "id", dbValue = DbInt(0), nullable = false),
|
|
230
|
+
// ColumnMeta(name = "label", dbValue = DbString(""), nullable = false)
|
|
231
|
+
// )
|
|
232
|
+
val table = Table[Tag]("tag", DbCodec[Tag], meta)
|
|
233
|
+
// table: Table[Tag] = Table(
|
|
234
|
+
// name = "tag",
|
|
235
|
+
// codec = zio.blocks.sql.DbCodecDeriver$$anon$10@1a14f5c3,
|
|
236
|
+
// columnsMeta = Vector(
|
|
237
|
+
// ColumnMeta(name = "id", dbValue = DbInt(0), nullable = false),
|
|
238
|
+
// ColumnMeta(name = "label", dbValue = DbString(""), nullable = false)
|
|
239
|
+
// )
|
|
240
|
+
// )
|
|
241
|
+
table.name
|
|
242
|
+
// res14: String = "tag"
|
|
243
|
+
table.columns
|
|
244
|
+
// res15: IndexedSeq[String] = Vector("id", "label")
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
:::note
|
|
248
|
+
When the column `nullable` flag is `true`, the generated `CREATE TABLE` statement omits the `NOT NULL` constraint for that column, allowing the database to store `NULL` in that position. `Table.derived` sets this flag automatically based on whether the corresponding schema field is `Option[A]` or `Maybe[A]`.
|
|
249
|
+
:::
|
|
250
|
+
|
|
251
|
+
## Core Operations
|
|
252
|
+
|
|
253
|
+
### Element Access
|
|
254
|
+
|
|
255
|
+
The Element Access category exposes `columns`, which returns the SQL column names carried by the table in codec order.
|
|
256
|
+
|
|
257
|
+
#### `columns` — Column names in codec order
|
|
258
|
+
|
|
259
|
+
`Table#columns` returns an `IndexedSeq[String]` of the SQL column names for this table, in the same order as the underlying `DbCodec[A]`. The names are drawn from the validated `columnsMeta` and have already been checked to be legal SQL identifiers at construction time. Access is O(1) since the sequence is built once during construction.
|
|
260
|
+
|
|
261
|
+
```scala
|
|
262
|
+
final case class Table[A](...) {
|
|
263
|
+
def columns: IndexedSeq[String]
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The following example shows `columns` reflecting the snake_case field names derived from the schema:
|
|
268
|
+
|
|
269
|
+
```scala
|
|
270
|
+
import zio.blocks.sql._
|
|
271
|
+
import zio.blocks.schema.Schema
|
|
272
|
+
|
|
273
|
+
case class OrderLine(productId: Int, quantity: Int, unitPrice: BigDecimal)
|
|
274
|
+
object OrderLine {
|
|
275
|
+
implicit val schema: Schema[OrderLine] = Schema.derived
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
val table = Table.derived[OrderLine]
|
|
279
|
+
// table: Table[OrderLine] = Table(
|
|
280
|
+
// name = "order_line",
|
|
281
|
+
// codec = zio.blocks.sql.DbCodecDeriver$$anon$10@7196247a,
|
|
282
|
+
// columnsMeta = Vector(
|
|
283
|
+
// ColumnMeta(name = "product_id", dbValue = DbInt(0), nullable = false),
|
|
284
|
+
// ColumnMeta(name = "quantity", dbValue = DbInt(0), nullable = false),
|
|
285
|
+
// ColumnMeta(name = "unit_price", dbValue = DbBigDecimal(0), nullable = false)
|
|
286
|
+
// )
|
|
287
|
+
// )
|
|
288
|
+
table.columns
|
|
289
|
+
// res17: IndexedSeq[String] = Vector("product_id", "quantity", "unit_price")
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### DDL Generation
|
|
293
|
+
|
|
294
|
+
The DDL Generation category provides `createTable` and `dropTable`, which produce `Frag` values containing dialect-specific `CREATE TABLE IF NOT EXISTS` and `DROP TABLE IF EXISTS` statements. Both methods delegate to the `Ddl` helper, which constructs a `Frag` with no bound parameters — only literal SQL text.
|
|
295
|
+
|
|
296
|
+
#### `createTable` — Generate a CREATE TABLE statement
|
|
297
|
+
|
|
298
|
+
`Table#createTable` accepts a `SqlDialect` and returns a `Frag` whose SQL text is a `CREATE TABLE IF NOT EXISTS` statement. Each column definition uses the dialect's `typeName` method to convert the column's `DbValue` representative to the appropriate SQL type string (for example, `DbValue.DbString` becomes `TEXT` in PostgreSQL and `TEXT` in SQLite; `DbValue.DbInt` becomes `INTEGER` in both). Non-nullable columns carry a `NOT NULL` constraint; nullable columns do not.
|
|
299
|
+
|
|
300
|
+
```scala
|
|
301
|
+
final case class Table[A](...) {
|
|
302
|
+
def createTable(dialect: SqlDialect): Frag
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The following example demonstrates the DDL generated for a record with a mix of column types and an optional field:
|
|
307
|
+
|
|
308
|
+
```scala
|
|
309
|
+
import zio.blocks.sql._
|
|
310
|
+
import zio.blocks.schema.Schema
|
|
311
|
+
|
|
312
|
+
case class Product(sku: String, price: BigDecimal, stock: Option[Int])
|
|
313
|
+
object Product {
|
|
314
|
+
implicit val schema: Schema[Product] = Schema.derived
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
val table = Table.derived[Product]
|
|
318
|
+
// table: Table[Product] = Table(
|
|
319
|
+
// name = "product",
|
|
320
|
+
// codec = zio.blocks.sql.DbCodecDeriver$$anon$10@19a68e5b,
|
|
321
|
+
// columnsMeta = Vector(
|
|
322
|
+
// ColumnMeta(name = "sku", dbValue = DbString(""), nullable = false),
|
|
323
|
+
// ColumnMeta(name = "price", dbValue = DbBigDecimal(0), nullable = false),
|
|
324
|
+
// ColumnMeta(name = "stock", dbValue = DbInt(0), nullable = true)
|
|
325
|
+
// )
|
|
326
|
+
// )
|
|
327
|
+
val createPg = table.createTable(SqlDialect.PostgreSQL).sql(SqlDialect.PostgreSQL)
|
|
328
|
+
// createPg: String = """CREATE TABLE IF NOT EXISTS product (
|
|
329
|
+
// sku TEXT NOT NULL,
|
|
330
|
+
// price NUMERIC NOT NULL,
|
|
331
|
+
// stock INTEGER
|
|
332
|
+
// )"""
|
|
333
|
+
val createSq = table.createTable(SqlDialect.SQLite).sql(SqlDialect.SQLite)
|
|
334
|
+
// createSq: String = """CREATE TABLE IF NOT EXISTS product (
|
|
335
|
+
// sku TEXT NOT NULL,
|
|
336
|
+
// price TEXT NOT NULL,
|
|
337
|
+
// stock INTEGER
|
|
338
|
+
// )"""
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
:::caution
|
|
342
|
+
`Table#createTable` only supports column types whose `DbValue` representative maps to a primitive SQL type. Fields whose codec falls back to JSONB serialization (for example, `List[A]` or a sealed trait with multiple variants) will produce a `TEXT` or `JSONB` column — the DDL column type depends on the representative `DbValue` assigned during column metadata derivation, which in those cases is `DbValue.DbString`. Nested records that are flattened into multiple columns are fully supported.
|
|
343
|
+
:::
|
|
344
|
+
|
|
345
|
+
#### `dropTable` — Generate a DROP TABLE statement
|
|
346
|
+
|
|
347
|
+
`Table#dropTable` returns a `Frag` whose SQL text is `DROP TABLE IF EXISTS <name>`, with no parameters and no dialect argument. Because `DROP TABLE` syntax is uniform across the supported dialects, a single `Frag` is correct for any `SqlDialect`. Render the fragment with `Frag#sql` to obtain the final SQL string.
|
|
348
|
+
|
|
349
|
+
```scala
|
|
350
|
+
final case class Table[A](...) {
|
|
351
|
+
def dropTable: Frag
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
The following example shows the drop statement for a table derived from a simple case class:
|
|
356
|
+
|
|
357
|
+
```scala
|
|
358
|
+
import zio.blocks.sql._
|
|
359
|
+
import zio.blocks.schema.Schema
|
|
360
|
+
|
|
361
|
+
case class Session(token: String, userId: Int)
|
|
362
|
+
object Session {
|
|
363
|
+
implicit val schema: Schema[Session] = Schema.derived
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
val table = Table.derived[Session]
|
|
367
|
+
// table: Table[Session] = Table(
|
|
368
|
+
// name = "session",
|
|
369
|
+
// codec = zio.blocks.sql.DbCodecDeriver$$anon$10@7081afa2,
|
|
370
|
+
// columnsMeta = Vector(
|
|
371
|
+
// ColumnMeta(name = "token", dbValue = DbString(""), nullable = false),
|
|
372
|
+
// ColumnMeta(name = "user_id", dbValue = DbInt(0), nullable = false)
|
|
373
|
+
// )
|
|
374
|
+
// )
|
|
375
|
+
table.dropTable.sql(SqlDialect.PostgreSQL)
|
|
376
|
+
// res20: String = "DROP TABLE IF EXISTS session"
|
|
377
|
+
table.dropTable.sql(SqlDialect.SQLite)
|
|
378
|
+
// res21: String = "DROP TABLE IF EXISTS session"
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## Supporting Types
|
|
382
|
+
|
|
383
|
+
`Table` is a monomorphic final case class with no subtypes of its own, but it depends on two supporting types — `ColumnMeta` and `TableNamingPolicy` — that control how column metadata is captured and how table names are derived.
|
|
384
|
+
|
|
385
|
+
### `ColumnMeta`
|
|
386
|
+
|
|
387
|
+
`ColumnMeta` is a final case class that carries the per-column metadata consumed by `Table#createTable` for DDL generation. Each field in a schema-derived type produces exactly one `ColumnMeta` (nested records are flattened; optional fields set `nullable = true`).
|
|
388
|
+
|
|
389
|
+
```scala
|
|
390
|
+
final case class ColumnMeta(name: String, dbValue: DbValue, nullable: Boolean)
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
The three fields serve distinct roles. `name` is the SQL column name after `SqlNameMapper` has been applied and SQL identifier validation has been run. `dbValue` is a representative instance of the column's `DbValue` variant (for example, `DbValue.DbInt(0)` for an `Int` column) — it carries no runtime data and is used only to dispatch to `SqlDialect#typeName` during DDL generation. `nullable` reflects whether the Scala field is `Option[A]` or `Maybe[A]`.
|
|
394
|
+
|
|
395
|
+
### `TableNamingPolicy`
|
|
396
|
+
|
|
397
|
+
`TableNamingPolicy` is a sealed trait that controls how a Scala type name is translated into a SQL table name when using `Table.derived`. All three `derived` overloads use a naming policy either explicitly or implicitly.
|
|
398
|
+
|
|
399
|
+
```scala
|
|
400
|
+
sealed trait TableNamingPolicy {
|
|
401
|
+
def defaultName(typeName: String): String
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
object TableNamingPolicy {
|
|
405
|
+
case object Singular extends TableNamingPolicy
|
|
406
|
+
case object Plural extends TableNamingPolicy
|
|
407
|
+
final case class Custom(f: String => String) extends TableNamingPolicy
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
The three variants cover the most common conventions:
|
|
412
|
+
|
|
413
|
+
- **`Singular`** (the default) — converts the Scala type name to `snake_case` using `SqlNameMapper.SnakeCase`. `UserProfile` becomes `user_profile`, `Category` becomes `category`.
|
|
414
|
+
- **`Plural`** — applies the same `snake_case` conversion and then appends a simple English pluralization suffix. `Category` becomes `categories`, `User` becomes `users`, `Quiz` becomes `quizzes`.
|
|
415
|
+
- **`Custom(f)`** — applies the function `f` to the type name, giving full control over the mapping. The function receives the raw Scala type name (before any case conversion) and must return a valid SQL identifier.
|
|
416
|
+
|
|
417
|
+
The `Singular` policy is chosen because most databases treat table names as singular nouns by convention, but `Plural` is equally idiomatic in many teams. Pass the desired policy explicitly to `Table.derived[A](namingPolicy)` when the default does not match your project's convention.
|
|
418
|
+
|
|
419
|
+
## Comparison
|
|
420
|
+
|
|
421
|
+
### Slick and Doobie
|
|
422
|
+
|
|
423
|
+
`Table` takes a narrower scope than lifted-embedding ORMs like Slick and functional query builders like Doobie:
|
|
424
|
+
|
|
425
|
+
| Concern | `Table` (this module) | Slick | Doobie |
|
|
426
|
+
|--------------------------|--------------------------------------------------------------|-------------------------------------------------------------|------------------------------------------------------|
|
|
427
|
+
| Schema source of truth | `Schema[A]` (compile-time derivation) | `Table` class extending `TableQuery` (explicit column defs) | Hand-written `Get`/`Put` instances or Doobie macros |
|
|
428
|
+
| Query DSL | Plain SQL via `sql"..."` interpolator + `Frag` composition | Lifted Scala expressions compiled to SQL | Plain SQL via `sql"..."` interpolator |
|
|
429
|
+
| DDL generation | `Table#createTable` / `Table#dropTable` return `Frag` values | Via `schema.create` / `schema.drop` (requires lifted query) | Not built-in; usually handled by Flyway or Liquibase |
|
|
430
|
+
| Runtime overhead | Zero — derivation is compile-time; no reflection at runtime | JVM reflection + query compilation per session | Minimal; `Get`/`Put` are materialized type classes |
|
|
431
|
+
| Effect system dependency | None (the shared API abstracts over `DbConnection`; no JDBC dependency in shared code) | Slick's `DBIO` monad | Cats `IO` or `Sync[F]` |
|
|
432
|
+
|
|
433
|
+
`Table` does not model relationships, joins, or query projection — those concerns belong to hand-written `sql"..."` fragments and the `Repo` type. When you need rich relational queries, compose `Frag` values manually rather than using a lifted embedding.
|
|
434
|
+
|
|
435
|
+
### Hibernate JPA
|
|
436
|
+
|
|
437
|
+
`Table` and Hibernate address the same problem from opposite directions:
|
|
438
|
+
|
|
439
|
+
| Concern | `Table` (this module) | Hibernate / JPA |
|
|
440
|
+
|---------------------|------------------------------------------------------------------|-------------------------------------------------------------------------|
|
|
441
|
+
| Configuration style | Immutable value derived from `Schema[A]` at compile time | Annotations on mutable entity classes at runtime |
|
|
442
|
+
| Session lifecycle | None — connections managed explicitly by `Transactor` | `EntityManager`, `Session`, first-level cache, lazy proxies |
|
|
443
|
+
| Lazy loading | Not supported — all column values are loaded eagerly | Supported via proxy objects and byte-code instrumentation |
|
|
444
|
+
| SQL control | Full — every query is a `Frag` of literal SQL + typed parameters | Partial — JPQL / Criteria API abstracts SQL; native SQL as escape hatch |
|
|
445
|
+
| DDL generation | `Table#createTable` returns a `Frag`; you execute it explicitly | `hbm2ddl.auto` may run DDL automatically at startup |
|
|
446
|
+
| Scala compatibility | First-class; no mutable beans required | Requires JavaBean conventions (default constructor, mutable fields) |
|
|
447
|
+
|
|
448
|
+
`Table` never manages entity identity, caching, or lazy associations. It is a thin, transparent layer over JDBC — what you write in `sql"..."` is exactly what the database executes.
|