@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.
Files changed (215) hide show
  1. package/adr/2026-07-18-data-migration.md +123 -0
  2. package/guides/async-getting-started.md +687 -0
  3. package/guides/compile-time-resource-safety-with-scope.md +21 -16
  4. package/guides/getting-started-with-mux.md +1395 -0
  5. package/guides/query-dsl-extending.md +161 -102
  6. package/guides/query-dsl-fluent-builder.md +217 -157
  7. package/guides/query-dsl-reified-optics.md +12 -10
  8. package/guides/query-dsl-sql.md +640 -165
  9. package/guides/sql-checked-interpolation.md +173 -0
  10. package/guides/sql-transactions.md +286 -0
  11. package/guides/telemetry-guide.md +1130 -0
  12. package/guides/zio-schema-migration.md +29 -22
  13. package/index.md +248 -389
  14. package/package.json +1 -1
  15. package/plans/config-follow-up-prs.md +188 -0
  16. package/plans/config-pr-assessment-roadmap.md +310 -0
  17. package/reference/MuxDataFlow.jsx +250 -0
  18. package/reference/async.md +1499 -0
  19. package/reference/chunk.md +3533 -308
  20. package/reference/codegen/case-class.md +436 -0
  21. package/reference/codegen/emitter-config.md +383 -0
  22. package/reference/codegen/examples.md +664 -0
  23. package/reference/codegen/field.md +316 -0
  24. package/reference/codegen/index.md +317 -0
  25. package/reference/codegen/scala-emitter.md +392 -0
  26. package/reference/codegen/scala-file.md +276 -0
  27. package/reference/codegen/sealed-trait.md +408 -0
  28. package/reference/codegen/type-definition.md +340 -0
  29. package/reference/codegen/type-ref.md +201 -0
  30. package/reference/combinators.md +347 -117
  31. package/reference/config/config-decoder.md +460 -0
  32. package/reference/config/config-source.md +489 -0
  33. package/reference/config/errors.md +278 -0
  34. package/reference/config/flags.md +369 -0
  35. package/reference/config/formats.md +314 -0
  36. package/reference/config/index.md +304 -0
  37. package/reference/config/rollout.md +336 -0
  38. package/reference/context.md +9 -52
  39. package/reference/data-migration.md +269 -0
  40. package/reference/datastar/attributes.md +302 -0
  41. package/reference/datastar/events.md +234 -0
  42. package/reference/datastar/index.md +256 -0
  43. package/reference/datastar/signals.md +230 -0
  44. package/reference/datastar/sse.md +295 -0
  45. package/reference/datastar.md +346 -0
  46. package/reference/docs.md +1461 -345
  47. package/reference/endpoint/auth-type.md +146 -0
  48. package/reference/endpoint/bulk-creation.md +96 -0
  49. package/reference/endpoint/endpoint.md +297 -0
  50. package/reference/endpoint/http-codec.md +249 -0
  51. package/reference/endpoint/index.md +745 -0
  52. package/reference/endpoint/path-codec.md +225 -0
  53. package/reference/endpoint/route-pattern.md +194 -0
  54. package/reference/endpoint/route-tree.md +111 -0
  55. package/reference/endpoint/segment-codec.md +199 -0
  56. package/reference/html.md +1424 -0
  57. package/reference/htmx/attribute-values.md +359 -0
  58. package/reference/htmx/hx-encoding.md +111 -0
  59. package/reference/htmx/hx-params.md +204 -0
  60. package/reference/htmx/hx-swap.md +276 -0
  61. package/reference/htmx/hx-sync.md +251 -0
  62. package/reference/htmx/hx-target.md +314 -0
  63. package/reference/htmx/hx-trigger.md +457 -0
  64. package/reference/htmx/hx-url-update.md +239 -0
  65. package/reference/htmx/index.md +807 -0
  66. package/reference/htmx/response-headers.md +240 -0
  67. package/reference/http-model/headers.md +735 -0
  68. package/reference/http-model/index.md +49 -0
  69. package/reference/http-model/model.md +1517 -0
  70. package/reference/http-model/schema-codecs.md +522 -0
  71. package/reference/http-model/schema.md +750 -0
  72. package/reference/http-model/server-sent-event.md +341 -0
  73. package/reference/jwt.md +195 -0
  74. package/reference/maybe.md +943 -0
  75. package/reference/media-type.md +2 -2
  76. package/reference/mux.md +254 -0
  77. package/reference/mux.mdx +828 -0
  78. package/reference/openapi.md +1351 -0
  79. package/reference/projection.md +654 -0
  80. package/reference/resource-management/defer-handle.md +1 -1
  81. package/reference/resource-management/resource.md +31 -98
  82. package/reference/resource-management/scope.md +28 -220
  83. package/reference/resource-management/wire.md +5 -55
  84. package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
  85. package/reference/ringbuffer/MpscDiagram.jsx +618 -0
  86. package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
  87. package/reference/ringbuffer/SpscDiagram.jsx +677 -0
  88. package/reference/ringbuffer/advanced.mdx +109 -0
  89. package/reference/ringbuffer/index.mdx +145 -0
  90. package/reference/ringbuffer/mpmc.mdx +185 -0
  91. package/reference/ringbuffer/mpsc.mdx +164 -0
  92. package/reference/ringbuffer/spmc.mdx +108 -0
  93. package/reference/ringbuffer/spsc.mdx +416 -0
  94. package/reference/{allows.md → schema/allows.md} +4 -100
  95. package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
  96. package/reference/{binding.md → schema/binding.md} +3 -4
  97. package/reference/schema/built-in-codecs/avro.md +451 -0
  98. package/reference/schema/built-in-codecs/bson.md +510 -0
  99. package/reference/schema/built-in-codecs/csv.md +564 -0
  100. package/reference/schema/built-in-codecs/index.md +77 -0
  101. package/reference/schema/built-in-codecs/json/index.md +295 -0
  102. package/reference/schema/built-in-codecs/json/json-config.md +217 -0
  103. package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
  104. package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
  105. package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
  106. package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
  107. package/reference/schema/built-in-codecs/messagepack.md +508 -0
  108. package/reference/schema/built-in-codecs/thrift.md +433 -0
  109. package/reference/schema/built-in-codecs/toon.md +1078 -0
  110. package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
  111. package/reference/schema/built-in-codecs/yaml.md +552 -0
  112. package/reference/{codec.md → schema/codec.md} +11 -11
  113. package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +196 -5
  114. package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
  115. package/reference/schema/format.md +92 -0
  116. package/reference/schema/index.md +52 -0
  117. package/reference/schema/migration.md +297 -0
  118. package/reference/{modifier.md → schema/modifier.md} +58 -7
  119. package/reference/{optics.md → schema/optics.md} +2 -2
  120. package/reference/{patch.md → schema/patch.md} +1 -1
  121. package/{path-interpolator.md → reference/schema/path-interpolator.md} +167 -72
  122. package/reference/schema/reflect-transformer.md +140 -0
  123. package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
  124. package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
  125. package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
  126. package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
  127. package/reference/schema/schema-search.md +263 -0
  128. package/reference/{schema.md → schema/schema.md} +22 -2
  129. package/reference/{structural-types.md → schema/structural-types.md} +1 -1
  130. package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
  131. package/reference/smithy.md +1032 -0
  132. package/reference/sql/db-codec-deriver.md +71 -0
  133. package/reference/sql/db-codec.md +687 -0
  134. package/reference/sql/db-con.md +271 -0
  135. package/reference/sql/db-connection.md +153 -0
  136. package/reference/sql/db-param-writer.md +77 -0
  137. package/reference/sql/db-param.md +66 -0
  138. package/reference/sql/db-result-reader.md +148 -0
  139. package/reference/sql/db-tx.md +114 -0
  140. package/reference/sql/db-value.md +41 -0
  141. package/reference/sql/ddl.md +85 -0
  142. package/reference/sql/frag.md +288 -0
  143. package/reference/sql/index.md +341 -0
  144. package/reference/sql/repo.md +600 -0
  145. package/reference/sql/sql-dialect.md +73 -0
  146. package/reference/sql/sql-logger.md +62 -0
  147. package/reference/sql/sql-name-mapper.md +70 -0
  148. package/reference/sql/table-metadata.md +134 -0
  149. package/reference/sql/table.md +448 -0
  150. package/reference/sql/transactor-zio.md +399 -0
  151. package/reference/sql/transactor.md +363 -0
  152. package/reference/sql-zio.md +112 -0
  153. package/reference/streams/core/index.md +32 -0
  154. package/reference/streams/core/pipeline.md +854 -0
  155. package/reference/streams/core/sink.md +1404 -0
  156. package/reference/streams/core/stream.md +3236 -0
  157. package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
  158. package/reference/streams/execution-and-compatibility/index.md +35 -0
  159. package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
  160. package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
  161. package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
  162. package/reference/streams/index.md +726 -0
  163. package/reference/streams/primitives/index.md +30 -0
  164. package/reference/streams/primitives/reader.md +1992 -0
  165. package/reference/streams/primitives/writer.md +1201 -0
  166. package/reference/telemetry/common/any-value.md +90 -0
  167. package/reference/telemetry/common/attribute-key.md +87 -0
  168. package/reference/telemetry/common/attributes.md +118 -0
  169. package/reference/telemetry/common/index.md +39 -0
  170. package/reference/telemetry/common/instrumentation-scope.md +24 -0
  171. package/reference/telemetry/common/resource.md +34 -0
  172. package/reference/telemetry/index.md +311 -0
  173. package/reference/telemetry/logging/index.md +197 -0
  174. package/reference/telemetry/logging/log-enrichment.md +72 -0
  175. package/reference/telemetry/logging/log-formatter.md +100 -0
  176. package/reference/telemetry/logging/log-record-processor.md +56 -0
  177. package/reference/telemetry/logging/log-record.md +44 -0
  178. package/reference/telemetry/logging/log-writer.md +64 -0
  179. package/reference/telemetry/logging/logger-provider.md +142 -0
  180. package/reference/telemetry/logging/logger.md +83 -0
  181. package/reference/telemetry/logging/severity.md +62 -0
  182. package/reference/telemetry/metrics/index.md +150 -0
  183. package/reference/telemetry/metrics/instruments.md +183 -0
  184. package/reference/telemetry/metrics/labeled-instruments.md +74 -0
  185. package/reference/telemetry/metrics/meter-provider.md +76 -0
  186. package/reference/telemetry/metrics/meter.md +98 -0
  187. package/reference/telemetry/metrics/metric-data.md +57 -0
  188. package/reference/telemetry/otel/custom-exporter.md +216 -0
  189. package/reference/telemetry/otel/index.md +212 -0
  190. package/reference/telemetry/tracing/index.md +155 -0
  191. package/reference/telemetry/tracing/sampler.md +89 -0
  192. package/reference/telemetry/tracing/span-builder.md +57 -0
  193. package/reference/telemetry/tracing/span-context.md +39 -0
  194. package/reference/telemetry/tracing/span-data.md +32 -0
  195. package/reference/telemetry/tracing/span-kind.md +55 -0
  196. package/reference/telemetry/tracing/span-processor.md +53 -0
  197. package/reference/telemetry/tracing/span-status.md +47 -0
  198. package/reference/telemetry/tracing/span.md +117 -0
  199. package/reference/telemetry/tracing/tracer-provider.md +91 -0
  200. package/reference/telemetry/tracing/tracer.md +52 -0
  201. package/reference/typeid.md +5 -83
  202. package/sidebars.js +376 -43
  203. package/undocumented-report.md +528 -270
  204. package/reference/formats.md +0 -694
  205. package/reference/http-model.md +0 -1716
  206. package/reference/streams.md +0 -989
  207. package/ringbuffer.md +0 -249
  208. /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
  209. /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
  210. /package/reference/{lazy.md → schema/lazy.md} +0 -0
  211. /package/reference/{reflect.md → schema/reflect.md} +0 -0
  212. /package/reference/{registers.md → schema/registers.md} +0 -0
  213. /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
  214. /package/reference/{syntax.md → schema/syntax.md} +0 -0
  215. /package/reference/{validation.md → schema/validation.md} +0 -0
@@ -0,0 +1,148 @@
1
+ ---
2
+ id: db-result-reader
3
+ title: "DbResultReader"
4
+ description: "Reference for DbResultReader, the interface for reading typed column values from a SQL result set."
5
+ keywords:
6
+ - "DbResultReader Column Access"
7
+ - "Result Set Abstraction"
8
+ ---
9
+
10
+ `DbResultReader` is a trait for reading typed column values from a database result set. It provides methods to access columns by either their 1-based index (following JDBC convention) or by column label.
11
+
12
+ Application code does not use `DbResultReader` directly — the framework creates it automatically when a `Frag` is executed. It is the read-side counterpart to `DbParamWriter`, which writes values to prepared statements.
13
+
14
+ ## Core API
15
+
16
+ The core API reads numeric, text, binary, boolean, temporal, and UUID values, plus column metadata and NULL status. `getArray` also exists but defaults to throwing `UnsupportedOperationException` in the shared trait; only backends that override it (the JVM implementation does) actually support SQL array access:
17
+
18
+ ```scala
19
+ trait DbResultReader {
20
+ // Numeric types
21
+ def getInt(index: Int): Int
22
+ def getInt(label: String): Int
23
+ def getLong(index: Int): Long
24
+ def getLong(label: String): Long
25
+ def getDouble(index: Int): Double
26
+ def getDouble(label: String): Double
27
+ def getFloat(index: Int): Float
28
+ def getFloat(label: String): Float
29
+ def getShort(index: Int): Short
30
+ def getShort(label: String): Short
31
+ def getByte(index: Int): Byte
32
+ def getByte(label: String): Byte
33
+ def getBigDecimal(index: Int): java.math.BigDecimal
34
+ def getBigDecimal(label: String): java.math.BigDecimal
35
+
36
+ // Text and binary
37
+ def getString(index: Int): String
38
+ def getString(label: String): String
39
+ def getBytes(index: Int): Array[Byte]
40
+ def getBytes(label: String): Array[Byte]
41
+
42
+ // Boolean
43
+ def getBoolean(index: Int): Boolean
44
+ def getBoolean(label: String): Boolean
45
+
46
+ // Date and time
47
+ def getLocalDate(index: Int): java.time.LocalDate
48
+ def getLocalDate(label: String): java.time.LocalDate
49
+ def getLocalDateTime(index: Int): java.time.LocalDateTime
50
+ def getLocalDateTime(label: String): java.time.LocalDateTime
51
+ def getLocalTime(index: Int): java.time.LocalTime
52
+ def getLocalTime(label: String): java.time.LocalTime
53
+ def getInstant(index: Int): java.time.Instant
54
+ def getInstant(label: String): java.time.Instant
55
+ def getDuration(index: Int): java.time.Duration
56
+ def getDuration(label: String): java.time.Duration
57
+
58
+ // Other types
59
+ def getUUID(index: Int): java.util.UUID
60
+ def getUUID(label: String): java.util.UUID
61
+ def getArray(index: Int): Array[String] // default: throws UnsupportedOperationException
62
+ def getArray(label: String): Array[String] // default: throws UnsupportedOperationException
63
+
64
+ // Metadata and NULL detection
65
+ def columnLabel(index: Int): String
66
+ def hasColumn(label: String): Boolean
67
+ def wasNull: Boolean
68
+ def isNull(index: Int): Boolean // default: false
69
+ def isNull(label: String): Boolean // default: false
70
+ }
71
+ ```
72
+
73
+ ## Usage
74
+
75
+ You access `DbResultReader` through `DbResultSet.reader` after calling `executeQuery` on a prepared statement:
76
+
77
+ ```scala
78
+ import zio.blocks.sql._
79
+
80
+ val transactor: Transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
81
+
82
+ transactor.connect {
83
+ val con = summon[DbCon].connection
84
+ val stmt = con.prepareStatement("SELECT id, name, age FROM users")
85
+ val rs = stmt.executeQuery()
86
+
87
+ if (rs.next()) {
88
+ // Read by column label
89
+ val id: Long = rs.reader.getLong("id")
90
+ val name: String = rs.reader.getString("name")
91
+ val age: Int = rs.reader.getInt("age")
92
+ }
93
+
94
+ rs.close()
95
+ stmt.close()
96
+ }
97
+ ```
98
+
99
+ ## NULL Handling
100
+
101
+ Numeric and boolean getters return zero-like defaults (`0`, `0L`, `false`) for SQL `NULL`. String, decimal, and temporal getters return `null`. After any `get*` call, check `wasNull` to detect `NULL`:
102
+
103
+ ```scala
104
+ import zio.blocks.sql._
105
+
106
+ val transactor: Transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
107
+
108
+ transactor.connect {
109
+ val con = summon[DbCon].connection
110
+ val stmt = con.prepareStatement("SELECT bio FROM users LIMIT 1")
111
+ val rs = stmt.executeQuery()
112
+
113
+ if (rs.next()) {
114
+ val bio: String = rs.reader.getString("bio")
115
+ val bioOption: Option[String] = if (rs.reader.wasNull) None else Some(bio)
116
+ }
117
+
118
+ rs.close()
119
+ stmt.close()
120
+ }
121
+ ```
122
+
123
+ :::caution
124
+ Always call `wasNull` immediately after the `get*` call whose NULL status you want to check. Do not call another `get*` method in between, or `wasNull` will reflect the wrong result.
125
+ :::
126
+
127
+ ## How It Works
128
+
129
+ When you use high-level operations like `Frag.query` or `Repo.find`, `DbCodec` internally calls `DbResultReader` methods to decode result rows. You only see this trait directly when dropping down to raw prepared statements for advanced use cases.
130
+
131
+ Label-based access (preferred) lets you ignore column order:
132
+
133
+ ```scala
134
+ import zio.blocks.sql._
135
+
136
+ val reader: DbResultReader = ???
137
+ val name = reader.getString("name") // Works regardless of SELECT order
138
+ ```
139
+
140
+ Index-based access (1-based per JDBC) is faster but requires stable column order:
141
+
142
+ ```scala
143
+ import zio.blocks.sql._
144
+
145
+ val reader: DbResultReader = ???
146
+ val name = reader.getString(2) // Assumes name is the 2nd column
147
+ ```
148
+
@@ -0,0 +1,114 @@
1
+ ---
2
+ id: db-tx
3
+ title: "DbTx"
4
+ description: "Reference for DbTx, the SQL module's transactional scope marker that extends DbCon with commit-on-success and rollback-on-failure semantics."
5
+ keywords:
6
+ - "DbTx Transaction Scope"
7
+ - "Transactor Transact Context"
8
+ - "DbCon Subtype Marker"
9
+ - "Auto-Commit Disabled Transaction"
10
+ - "Commit Rollback Semantics"
11
+ - "SQL Module Connection Context"
12
+ - "JDBC Transaction Lifecycle"
13
+ ---
14
+
15
+ `DbTx` is the transactional connection context supplied by `Transactor#transact`. It extends `DbCon` (so every `Frag`/`Repo` operation that needs `DbCon` also accepts `DbTx`) and adds savepoint-based nested transaction support. You never construct a `DbTx` directly; the `Transactor` creates one and supplies it as a given context to the block passed to `transact`. The connection is always closed when the outermost block exits, whether it commits, rolls back, or throws.
16
+
17
+ Key properties:
18
+ - **Transactional context** — A `DbTx` value in scope guarantees the underlying JDBC connection has auto-commit disabled.
19
+ - **Commit-on-success / rollback-on-failure** — The outermost `Transactor.transact` commits on normal return and rolls back on any uncaught exception (with suppressed rollback failures).
20
+ - **Savepoint-based nesting** — Inner blocks reuse the same connection via SQL savepoints (`SAVEPOINT` / `RELEASE SAVEPOINT` / `ROLLBACK TO SAVEPOINT`).
21
+ - **`transact(isolation, readOnly)`** — The two-arg `Transactor.transact` overload sets isolation level and `readOnly` before disabling auto-commit; `DbTx` nested blocks inherit those settings on the same connection.
22
+
23
+ The structural declaration of `DbTx` is:
24
+
25
+ ```scala
26
+ trait DbTx extends DbCon {
27
+ def savepoint(name: String): Unit
28
+ def release(name: String): Unit
29
+ def rollbackTo(name: String): Unit
30
+ def currentDepth: Int
31
+ private[sql] def currentDepth_=(depth: Int): Unit
32
+
33
+ // inherited from DbCon
34
+ def connection: DbConnection
35
+ def dialect: SqlDialect
36
+ def logger: SqlLogger
37
+ }
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ The following example opens a transaction via `Transactor#transact`, accesses all three context members, and combines a `Repo` CRUD operation with a hand-written `Frag` query — both of which accept `DbTx` transparently in place of `DbCon`:
43
+
44
+ ```scala
45
+ import zio.blocks.sql._
46
+ import zio.blocks.schema.Schema
47
+
48
+ case class User(id: Int, name: String, email: String)
49
+ object User {
50
+ implicit val schema: Schema[User] = Schema.derived
51
+ }
52
+
53
+ val repo = Repo.derived[User, Int]("users", "id", _.id)
54
+ // repo: Repo[User, Int] = zio.blocks.sql.Repo$DerivedRepo@783c5728
55
+ val tx = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
56
+ // tx: JdbcTransactor = zio.blocks.sql.JdbcTransactor@2ee9e31d
57
+
58
+ // On normal return: transaction commits and connection closes.
59
+ // On any exception: transaction rolls back, then the exception propagates.
60
+ tx.transact {
61
+ // All three context members are accessible via summon[DbTx]
62
+ val conn: DbConnection = summon[DbTx].connection // managed JDBC connection — do not close manually
63
+ val d: SqlDialect = summon[DbTx].dialect
64
+ val log: SqlLogger = summon[DbTx].logger
65
+
66
+ // Repo and Frag operations accept DbTx because DbTx extends DbCon
67
+ repo.table.createTable(summon[DbTx].dialect).update
68
+ repo.insert(User(1, "Alice", "alice@example.com"))
69
+ repo.insert(User(2, "Bob", "bob@example.com"))
70
+
71
+ val all: List[User] = repo.all
72
+ val custom: List[User] =
73
+ sql"SELECT id, name, email FROM users WHERE name LIKE ${"A%"}".query[User]
74
+
75
+ (all, custom)
76
+ }
77
+ // res1: Tuple2[List[User], List[User]] = (
78
+ // List(
79
+ // User(id = 1, name = "Alice", email = "alice@example.com"),
80
+ // User(id = 2, name = "Bob", email = "bob@example.com")
81
+ // ),
82
+ // List(User(id = 1, name = "Alice", email = "alice@example.com"))
83
+ // )
84
+ ```
85
+
86
+ ## Nested Transactions via Savepoints
87
+
88
+ Nested transactions reuse the same underlying JDBC connection via SQL savepoints. The `DbTx` given in scope exposes an extension `transact` and the `transactNested` helpers:
89
+
90
+ ```scala
91
+ import zio.blocks.sql._
92
+
93
+ val transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
94
+
95
+ // Savepoint-based nesting — same connection, isolated rollback
96
+ transactor.transact {
97
+ sql"INSERT INTO t VALUES (1)".update
98
+
99
+ // Inner block runs inside SAVEPOINT zib_tx_1
100
+ summon[DbTx].transact {
101
+ sql"INSERT INTO t VALUES (2)".update
102
+ }
103
+
104
+ // Equivalent using the `using` helper
105
+ // DbTx.transactNested { sql"INSERT INTO t VALUES (3)".update }
106
+ // transactNested { sql"INSERT INTO t VALUES (4)".update }
107
+ }
108
+ ```
109
+
110
+ Savepoint names are `zib_tx_1 .. zib_tx_N` where `N` is the nesting depth tracked in `currentDepth`. On success the savepoint is released via `RELEASE SAVEPOINT`; on failure it is rolled back via `ROLLBACK TO SAVEPOINT` and the exception is rethrown (with any rollback failure added as suppressed). Depth is decremented in `finally`, so sibling nested blocks reuse the same name sequence without leaking savepoints. `savepoint`/`release`/`rollbackTo` are also available directly for manual control and validate identifiers via `SqlIdentifier` to prevent injection.
111
+
112
+ :::caution
113
+ Only the outermost `Transactor.transact` issues a real `COMMIT`/`ROLLBACK`. Inner `summon[DbTx].transact` blocks are savepoint-scoped — outer commit still decides the final persistence of all work, including inner blocks that succeeded.
114
+ :::
@@ -0,0 +1,41 @@
1
+ ---
2
+ id: db-value
3
+ title: "DbValue"
4
+ description: "Reference for DbValue, the sealed ADT of typed SQL column values shared by Frag, DbCodec, and SqlDialect in the sql module."
5
+ keywords:
6
+ - "DbValue sealed ADT"
7
+ - "SQL column value types"
8
+ - "DbNull DbInt DbString"
9
+ - "typed database parameters"
10
+ - "SqlDialect typeName"
11
+ - "DbCodec toDbValues"
12
+ - "Frag params representation"
13
+ ---
14
+
15
+ `DbValue` is a sealed ADT representing every typed SQL column value the `sql` module handles, one `case class` per Scala type. `Frag` stores its bound parameters as `IndexedSeq[DbValue]`, `DbCodec#toDbValues` produces them, and `SqlDialect#typeName` maps a `DbValue` to its DDL type string — it is the common typed currency all three share, with no JDBC imports or dependencies of its own.
16
+
17
+ | Variant | Scala field type | PostgreSQL DDL | SQLite DDL |
18
+ |-------------------|----------------------------|---------------------|------------|
19
+ | `DbNull` | *(none)* | `NULL` | `NULL` |
20
+ | `DbInt` | `Int` | `INTEGER` | `INTEGER` |
21
+ | `DbLong` | `Long` | `BIGINT` | `INTEGER` |
22
+ | `DbDouble` | `Double` | `DOUBLE PRECISION` | `REAL` |
23
+ | `DbFloat` | `Float` | `REAL` | `REAL` |
24
+ | `DbBoolean` | `Boolean` | `BOOLEAN` | `INTEGER` |
25
+ | `DbString` | `String` | `TEXT` | `TEXT` |
26
+ | `DbBigDecimal` | `scala.BigDecimal` | `NUMERIC` | `TEXT` |
27
+ | `DbBytes` | `Array[Byte]` | `BYTEA` | `BLOB` |
28
+ | `DbShort` | `Short` | `SMALLINT` | `INTEGER` |
29
+ | `DbByte` | `Byte` | `SMALLINT` | `INTEGER` |
30
+ | `DbChar` | `Char` | `CHAR(1)` | `TEXT` |
31
+ | `DbLocalDate` | `java.time.LocalDate` | `DATE` | `TEXT` |
32
+ | `DbLocalDateTime` | `java.time.LocalDateTime` | `TIMESTAMP` | `TEXT` |
33
+ | `DbLocalTime` | `java.time.LocalTime` | `TIME` | `TEXT` |
34
+ | `DbInstant` | `java.time.Instant` | `TIMESTAMPTZ` | `TEXT` |
35
+ | `DbDuration` | `java.time.Duration` | `INTERVAL` | `TEXT` |
36
+ | `DbUUID` | `java.util.UUID` | `UUID` | `TEXT` |
37
+ | `DbArray` | `(String, IndexedSeq[Any])` | *(not in typeName)* | *(not in typeName)* |
38
+
39
+ `DbNull` is what `DbParam[Option[A]]` produces for `None` and `DbParam[Maybe[A]]` produces for `Maybe.absent`; `DbCodec` matches on it to decode nullable columns, throwing `IllegalStateException` if a non-optional codec hits it. `DbArray` is the one variant with two fields (`elementType`, `elements`) instead of `value`, used for collection-typed columns and not currently covered by `SqlDialect#typeName`.
40
+
41
+ See [Frag](./frag.md), [DbCodec](./db-codec.md), and the [SQL module index](./index.md).
@@ -0,0 +1,85 @@
1
+ ---
2
+ id: ddl
3
+ title: "Ddl"
4
+ description: "Reference for Ddl, the helper singleton that generates CREATE TABLE and DROP TABLE SQL fragments."
5
+ keywords:
6
+ - "Ddl createTable"
7
+ - "Ddl dropTable"
8
+ - "ColumnDef SQL type"
9
+ - "CREATE TABLE IF NOT EXISTS"
10
+ - "DROP TABLE IF EXISTS"
11
+ ---
12
+
13
+ `Ddl` is a helper object that generates DDL (Data Definition Language) SQL fragments for creating and dropping tables. `ColumnDef` is a simple data class pairing a column name with a SQL type string and a nullability flag.
14
+
15
+ Normally you don't call `Ddl` directly — `Table#createTable` and `Table#dropTable` use it internally. You may need `Ddl` directly when building custom DDL tooling.
16
+
17
+ ## Core API
18
+
19
+ ```scala
20
+ object Ddl {
21
+ def createTable(tableName: String, columns: IndexedSeq[ColumnDef]): Frag
22
+ def dropTable(tableName: String): Frag
23
+ }
24
+
25
+ final case class ColumnDef(name: String, sqlType: String, nullable: Boolean)
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ Create a `ColumnDef` for each column, then pass them to `Ddl.createTable`:
31
+
32
+ ```scala
33
+ import zio.blocks.sql._
34
+
35
+ val transactor: Transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
36
+ // transactor: Transactor = zio.blocks.sql.JdbcTransactor@3200cfda
37
+
38
+ val columns = IndexedSeq(
39
+ ColumnDef("id", "INTEGER", nullable = false),
40
+ ColumnDef("name", "TEXT", nullable = false),
41
+ ColumnDef("created_at", "TEXT", nullable = true)
42
+ )
43
+ // columns: IndexedSeq[ColumnDef] = Vector(
44
+ // ColumnDef(name = "id", sqlType = "INTEGER", nullable = false),
45
+ // ColumnDef(name = "name", sqlType = "TEXT", nullable = false),
46
+ // ColumnDef(name = "created_at", sqlType = "TEXT", nullable = true)
47
+ // )
48
+
49
+ val createFrag = Ddl.createTable("users", columns)
50
+ // createFrag: Frag = Frag(
51
+ // parts = Vector(
52
+ // """CREATE TABLE IF NOT EXISTS users (
53
+ // id INTEGER NOT NULL,
54
+ // name TEXT NOT NULL,
55
+ // created_at TEXT
56
+ // )"""
57
+ // ),
58
+ // params = Vector()
59
+ // )
60
+ val dropFrag = Ddl.dropTable("users")
61
+ // dropFrag: Frag = Frag(
62
+ // parts = Vector("DROP TABLE IF EXISTS users"),
63
+ // params = Vector()
64
+ // )
65
+
66
+ // Execute the fragments
67
+ transactor.transact {
68
+ createFrag.update
69
+ // ... do work ...
70
+ dropFrag.update
71
+ }
72
+ // res1: Int = 0
73
+ ```
74
+
75
+ ## How It Works
76
+
77
+ `Ddl` is typically used by `Table#createTable`, which derives `ColumnDef` from schema metadata:
78
+
79
+ 1. `Table.derived[A]` builds a schema.
80
+ 2. `Table#createTable(dialect)` converts schema columns to `ColumnDef` using `dialect.typeName`.
81
+ 3. `Ddl.createTable` receives the `ColumnDef` list and generates the SQL fragment.
82
+
83
+ You call `Ddl` directly only when you need custom DDL that doesn't fit the `Table` abstraction.
84
+
85
+ See [Table](./table.md) for the high-level DDL API.
@@ -0,0 +1,288 @@
1
+ ---
2
+ id: frag
3
+ title: "Frag"
4
+ description: "Reference for Frag, the SQL fragment type with safe parameterization and JDBC execution."
5
+ keywords:
6
+ - "SQL Fragment"
7
+ - "Frag SQL Interpolator"
8
+ - "Type-safe SQL parameters"
9
+ - "SQL Injection Prevention"
10
+ ---
11
+
12
+ `Frag` is an immutable SQL fragment — a piece of SQL text with typed parameter values kept safely separate from the literal SQL. The `sql"..."` string interpolator builds fragments by checking at compile time that every interpolated expression can be bound as a parameter. Fragments compose with `++` and execute through methods like `query`, `update`, and `queryOne`, plus the chunked streaming methods `queryStream` and `queryChunked`.
13
+
14
+ `Frag` is safe from SQL injection because parameter values never appear in the SQL string — they are stored separately and bound to `?` placeholders at execution time.
15
+
16
+ ## Core API
17
+
18
+ ```scala
19
+ final case class Frag(parts: IndexedSeq[String], params: IndexedSeq[DbValue]) {
20
+ def ++(other: Frag): Frag
21
+ def sql(dialect: SqlDialect): String
22
+ def queryParams: IndexedSeq[DbValue]
23
+ def isEmpty: Boolean
24
+ }
25
+
26
+ object Frag {
27
+ val empty: Frag
28
+ def literal(sqlStr: String): Frag
29
+ def sequence(frags: Frag*): Frag
30
+ def values[A](rows: Seq[A])(using codec: DbCodec[A]): Frag
31
+
32
+ extension (frag: Frag) {
33
+ def query[A](using DbCon, DbCodec[A]): List[A]
34
+ def queryOne[A](using DbCon, DbCodec[A]): Maybe[A]
35
+ def queryLimit[A](limit: Int)(using DbCon, DbCodec[A]): List[A]
36
+ def queryStream[A](using DbCon, DbCodec[A]): Stream[Throwable, Chunk[A]]
37
+ def queryChunked[A](chunkSize: Int)(using DbCon, DbCodec[A]): Stream[Throwable, Chunk[A]]
38
+ def update(using DbCon): Int
39
+ def updateReturningKeys[A](using DbCon, DbCodec[A]): List[A]
40
+ }
41
+ }
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ Build fragments with the `sql"..."` interpolator and execute them inside a `Transactor` block:
47
+
48
+ ```scala
49
+ import zio.blocks.sql._
50
+ import zio.blocks.schema.Schema
51
+ import zio.blocks.maybe.Maybe
52
+
53
+ case class User(id: Int, name: String, email: String)
54
+ object User { implicit val schema: Schema[User] = Schema.derived }
55
+
56
+ val tx: Transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
57
+
58
+ val userId = 42
59
+ val active = true
60
+
61
+ tx.connect {
62
+ // Query — returns all matching rows
63
+ val users: List[User] =
64
+ sql"SELECT id, name, email FROM users WHERE id > $userId AND active = $active".query[User]
65
+
66
+ // QueryOne — returns at most one row
67
+ val one: Maybe[User] =
68
+ sql"SELECT id, name, email FROM users WHERE id = ${1}".queryOne[User]
69
+
70
+ // Update — returns affected row count
71
+ val deleted: Int =
72
+ sql"DELETE FROM users WHERE active = ${false}".update
73
+
74
+ // UpdateReturningKeys — returns generated keys after INSERT
75
+ val keys: List[Long] =
76
+ sql"INSERT INTO users (name, email) VALUES (${"Alice"}, ${"alice@example.com"})".updateReturningKeys[Long]
77
+ }
78
+ ```
79
+
80
+ ## Building Fragments
81
+
82
+ **`sql"..."` interpolator** — The primary way to build fragments. Compile-time checked: every interpolated value must have a `DbParam` instance (provided for all common Scala and Java types).
83
+
84
+ ```scala
85
+ import zio.blocks.sql._
86
+
87
+ val userId = 42
88
+ // userId: Int = 42
89
+ val frag = sql"SELECT * FROM users WHERE id = $userId"
90
+ // frag: Frag = Frag(
91
+ // parts = Vector("SELECT * FROM users WHERE id = ", ""),
92
+ // params = Vector(DbInt(42))
93
+ // )
94
+ frag.params
95
+ // res2: IndexedSeq[DbValue] = Vector(DbInt(42))
96
+ ```
97
+
98
+ **`Frag.literal`** — Wrap static SQL text (no parameters):
99
+
100
+ ```scala
101
+ val query = sql"SELECT * FROM users" ++ Frag.literal(" ORDER BY name")
102
+ // query: Frag = Frag(
103
+ // parts = Vector("SELECT * FROM users ORDER BY name"),
104
+ // params = Vector()
105
+ // )
106
+ query.sql(SqlDialect.SQLite)
107
+ // res3: String = "SELECT * FROM users ORDER BY name"
108
+ ```
109
+
110
+ **`Frag.values`** — Build a multi-row INSERT VALUES clause:
111
+
112
+ ```scala
113
+ import zio.blocks.sql._
114
+
115
+ case class Product(name: String, price: BigDecimal) derives DbCodec
116
+
117
+ val products = List(Product("Widget", BigDecimal("9.99")), Product("Gadget", BigDecimal("24.99")))
118
+ // products: List[Product] = List(
119
+ // Product(name = "Widget", price = 9.99),
120
+ // Product(name = "Gadget", price = 24.99)
121
+ // )
122
+ val insert = Frag.literal("INSERT INTO product (name, price) VALUES ") ++ Frag.values(products)
123
+ // insert: Frag = Frag(
124
+ // parts = Vector(
125
+ // "INSERT INTO product (name, price) VALUES (",
126
+ // ", ",
127
+ // "), (",
128
+ // ", ",
129
+ // ")"
130
+ // ),
131
+ // params = Vector(
132
+ // DbString("Widget"),
133
+ // DbBigDecimal(9.99),
134
+ // DbString("Gadget"),
135
+ // DbBigDecimal(24.99)
136
+ // )
137
+ // )
138
+ insert.sql(SqlDialect.SQLite)
139
+ // res5: String = "INSERT INTO product (name, price) VALUES (?, ?), (?, ?)"
140
+ ```
141
+
142
+ **`Frag.empty`** — The identity fragment for composition. Useful for optional clauses:
143
+
144
+ ```scala
145
+ import zio.blocks.sql._
146
+
147
+ val hasFilter = true
148
+ // hasFilter: Boolean = true
149
+ val where = if (hasFilter) sql" WHERE active = ${true}" else Frag.empty
150
+ // where: Frag = Frag(
151
+ // parts = Vector(" WHERE active = ", ""),
152
+ // params = Vector(DbBoolean(true))
153
+ // )
154
+ val query = sql"SELECT * FROM users" ++ where
155
+ // query: Frag = Frag(
156
+ // parts = Vector("SELECT * FROM users WHERE active = ", ""),
157
+ // params = Vector(DbBoolean(true))
158
+ // )
159
+ query.sql(SqlDialect.SQLite)
160
+ // res7: String = "SELECT * FROM users WHERE active = ?"
161
+ ```
162
+
163
+ **`Frag.sequence`** — Concatenate multiple fragments with no separator:
164
+
165
+ ```scala
166
+ import zio.blocks.sql._
167
+
168
+ val status = "active"
169
+ // status: String = "active"
170
+ val base = sql"SELECT * FROM users"
171
+ // base: Frag = Frag(parts = Vector("SELECT * FROM users"), params = Vector())
172
+ val where = sql" WHERE status = $status"
173
+ // where: Frag = Frag(
174
+ // parts = Vector(" WHERE status = ", ""),
175
+ // params = Vector(DbString("active"))
176
+ // )
177
+ val order = Frag.literal(" ORDER BY name")
178
+ // order: Frag = Frag(parts = Vector(" ORDER BY name"), params = Vector())
179
+ val full = Frag.sequence(base, where, order)
180
+ // full: Frag = Frag(
181
+ // parts = Vector("SELECT * FROM users WHERE status = ", " ORDER BY name"),
182
+ // params = Vector(DbString("active"))
183
+ // )
184
+ full.sql(SqlDialect.SQLite)
185
+ // res9: String = "SELECT * FROM users WHERE status = ? ORDER BY name"
186
+ ```
187
+
188
+ ## Execution Methods
189
+
190
+ All execution methods require an implicit `DbCon` (provided by `Transactor#connect` or `Transactor#transact`). They render the fragment to SQL, bind parameters, execute, and log the operation.
191
+
192
+ **`query[A]`** — Execute SELECT and return all rows:
193
+
194
+ ```scala
195
+ import zio.blocks.sql._
196
+ import zio.blocks.schema.Schema
197
+
198
+ case class User(id: Int, name: String)
199
+ object User { implicit val schema: Schema[User] = Schema.derived }
200
+
201
+ given DbCon = ???
202
+
203
+ val users: List[User] = sql"SELECT id, name FROM users".query[User]
204
+ ```
205
+
206
+ **`queryOne[A]`** — Execute SELECT and return at most one row:
207
+
208
+ ```scala
209
+ import zio.blocks.sql._
210
+ import zio.blocks.schema.Schema
211
+ import zio.blocks.maybe.Maybe
212
+
213
+ case class User(id: Int, name: String)
214
+ object User { implicit val schema: Schema[User] = Schema.derived }
215
+
216
+ given DbCon = ???
217
+
218
+ val user: Maybe[User] = sql"SELECT id, name FROM users WHERE id = ${1}".queryOne[User]
219
+ ```
220
+
221
+ **`queryLimit[A](n)`** — Execute SELECT and return up to n rows (fetched from Scala side):
222
+
223
+ ```scala
224
+ import zio.blocks.sql._
225
+ import zio.blocks.schema.Schema
226
+
227
+ case class User(id: Int, name: String)
228
+ object User { implicit val schema: Schema[User] = Schema.derived }
229
+
230
+ given DbCon = ???
231
+
232
+ val page: List[User] = sql"SELECT id, name FROM users ORDER BY name".queryLimit[User](10)
233
+ ```
234
+
235
+ **`queryStream[A]`** — Execute SELECT and return rows as a chunked stream (`zio.blocks.streams.Stream`), batching `DefaultQueryChunkSize` (64) rows per chunk. Acquisition is lazy — nothing touches the connection until the first pull — so consume the stream within the scope that provides the `DbCon`; leaving `Transactor.connect`/`transact` closes the captured connection, and pulling afterwards fails.
236
+
237
+ ```scala
238
+ import zio.blocks.chunk.Chunk
239
+ import zio.blocks.sql._
240
+ import zio.blocks.schema.Schema
241
+ import zio.blocks.streams.Stream
242
+
243
+ case class User(id: Int, name: String)
244
+ object User { implicit val schema: Schema[User] = Schema.derived }
245
+
246
+ given DbCon = ???
247
+
248
+ val chunks: Stream[Throwable, Chunk[User]] =
249
+ sql"SELECT id, name FROM users".queryStream[User]
250
+ val all: Either[Throwable, List[User]] = chunks.runCollect.map(_.toList.flatten)
251
+ ```
252
+
253
+ **`queryChunked[A](n)`** — Like `queryStream`, but with an explicit batch size. The statement and result set are acquired lazily on the first pull and released when the stream is closed or fully drained; each chunk holds up to `n` rows, so memory stays bounded for large result sets. The same lifetime rule applies: consume the stream before the enclosing `Transactor.connect`/`transact` callback returns — afterwards the captured connection is closed and the first pull fails.
254
+
255
+ ```scala
256
+ import zio.blocks.chunk.Chunk
257
+ import zio.blocks.sql._
258
+ import zio.blocks.schema.Schema
259
+ import zio.blocks.streams.Stream
260
+
261
+ case class User(id: Int, name: String)
262
+ object User { implicit val schema: Schema[User] = Schema.derived }
263
+
264
+ given DbCon = ???
265
+
266
+ val batches: Stream[Throwable, Chunk[User]] =
267
+ sql"SELECT id, name FROM users".queryChunked[User](500)
268
+ ```
269
+
270
+ **`update`** — Execute INSERT, UPDATE, or DELETE and return affected row count:
271
+
272
+ ```scala
273
+ import zio.blocks.sql._
274
+
275
+ given DbCon = ???
276
+
277
+ val deleted: Int = sql"DELETE FROM users WHERE inactive = ${true}".update
278
+ ```
279
+
280
+ **`updateReturningKeys[A]`** — Execute INSERT and return auto-generated primary key(s):
281
+
282
+ ```scala
283
+ import zio.blocks.sql._
284
+
285
+ given DbCon = ???
286
+
287
+ val keys: List[Long] = sql"INSERT INTO users (name) VALUES (${"Alice"})".updateReturningKeys[Long]
288
+ ```