@zio.dev/zio-blocks 0.0.32 → 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.
Files changed (152) hide show
  1. package/guides/compile-time-resource-safety-with-scope.md +16 -17
  2. package/guides/getting-started-with-mux.md +1507 -0
  3. package/guides/query-dsl-extending.md +161 -102
  4. package/guides/query-dsl-fluent-builder.md +217 -157
  5. package/guides/query-dsl-reified-optics.md +12 -10
  6. package/guides/query-dsl-sql.md +246 -165
  7. package/guides/telemetry-guide.md +1069 -0
  8. package/guides/zio-schema-migration.md +29 -22
  9. package/index.md +293 -51
  10. package/package.json +1 -1
  11. package/plans/config-follow-up-prs.md +188 -0
  12. package/plans/config-pr-assessment-roadmap.md +310 -0
  13. package/reference/MuxDataFlow.jsx +250 -0
  14. package/reference/async.md +651 -0
  15. package/reference/chunk.md +3533 -308
  16. package/reference/codegen/case-class.md +436 -0
  17. package/reference/codegen/emitter-config.md +383 -0
  18. package/reference/codegen/examples.md +664 -0
  19. package/reference/codegen/field.md +316 -0
  20. package/reference/codegen/index.md +317 -0
  21. package/reference/codegen/scala-emitter.md +392 -0
  22. package/reference/codegen/scala-file.md +276 -0
  23. package/reference/codegen/sealed-trait.md +408 -0
  24. package/reference/codegen/type-definition.md +340 -0
  25. package/reference/codegen/type-ref.md +201 -0
  26. package/reference/combinators.md +347 -117
  27. package/reference/config.md +158 -0
  28. package/reference/context.md +4 -4
  29. package/reference/datastar.md +346 -0
  30. package/reference/docs.md +1461 -345
  31. package/reference/endpoint/auth-type.md +146 -0
  32. package/reference/endpoint/endpoint.md +297 -0
  33. package/reference/endpoint/http-codec.md +249 -0
  34. package/reference/endpoint/index.md +825 -0
  35. package/reference/endpoint/path-codec.md +237 -0
  36. package/reference/endpoint/route-pattern.md +196 -0
  37. package/reference/endpoint/route-tree.md +111 -0
  38. package/reference/endpoint/segment-codec.md +212 -0
  39. package/reference/html.md +1120 -0
  40. package/reference/htmx/attribute-values.md +359 -0
  41. package/reference/htmx/hx-encoding.md +111 -0
  42. package/reference/htmx/hx-params.md +204 -0
  43. package/reference/htmx/hx-swap.md +276 -0
  44. package/reference/htmx/hx-sync.md +251 -0
  45. package/reference/htmx/hx-target.md +314 -0
  46. package/reference/htmx/hx-trigger.md +457 -0
  47. package/reference/htmx/hx-url-update.md +239 -0
  48. package/reference/htmx/index.md +855 -0
  49. package/reference/http-model/index.md +47 -0
  50. package/reference/http-model/model.md +1481 -0
  51. package/reference/http-model/schema.md +747 -0
  52. package/reference/maybe.md +826 -0
  53. package/reference/media-type.md +2 -2
  54. package/reference/mux.mdx +823 -0
  55. package/reference/openapi.md +1351 -0
  56. package/reference/resource-management/defer-handle.md +1 -1
  57. package/reference/resource-management/resource.md +31 -2
  58. package/reference/resource-management/scope.md +28 -12
  59. package/reference/resource-management/wire.md +3 -7
  60. package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
  61. package/reference/ringbuffer/MpscDiagram.jsx +618 -0
  62. package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
  63. package/reference/ringbuffer/SpscDiagram.jsx +677 -0
  64. package/reference/ringbuffer/advanced.mdx +109 -0
  65. package/reference/ringbuffer/index.mdx +145 -0
  66. package/reference/ringbuffer/mpmc.mdx +151 -0
  67. package/reference/ringbuffer/mpsc.mdx +132 -0
  68. package/reference/ringbuffer/spmc.mdx +108 -0
  69. package/reference/ringbuffer/spsc.mdx +344 -0
  70. package/reference/{allows.md → schema/allows.md} +4 -4
  71. package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
  72. package/reference/{binding.md → schema/binding.md} +2 -3
  73. package/reference/schema/built-in-codecs/avro.md +451 -0
  74. package/reference/schema/built-in-codecs/bson.md +480 -0
  75. package/reference/schema/built-in-codecs/csv.md +564 -0
  76. package/reference/schema/built-in-codecs/index.md +77 -0
  77. package/reference/schema/built-in-codecs/json/index.md +295 -0
  78. package/reference/schema/built-in-codecs/json/json-config.md +217 -0
  79. package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +3 -3
  80. package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
  81. package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
  82. package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
  83. package/reference/schema/built-in-codecs/messagepack.md +508 -0
  84. package/reference/schema/built-in-codecs/thrift.md +433 -0
  85. package/reference/schema/built-in-codecs/toon.md +1078 -0
  86. package/reference/{xml.md → schema/built-in-codecs/xml.md} +34 -192
  87. package/reference/schema/built-in-codecs/yaml.md +552 -0
  88. package/reference/{codec.md → schema/codec.md} +10 -10
  89. package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
  90. package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
  91. package/reference/schema/format.md +92 -0
  92. package/reference/schema/index.md +50 -0
  93. package/reference/schema/migration.md +297 -0
  94. package/reference/{modifier.md → schema/modifier.md} +58 -7
  95. package/reference/{optics.md → schema/optics.md} +2 -2
  96. package/reference/{patch.md → schema/patch.md} +1 -1
  97. package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
  98. package/reference/{schema-evolution → schema/schema-evolution}/as.md +5 -5
  99. package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
  100. package/reference/{schema-evolution → schema/schema-evolution}/into.md +3 -3
  101. package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
  102. package/reference/{schema.md → schema/schema.md} +13 -1
  103. package/reference/{structural-types.md → schema/structural-types.md} +1 -1
  104. package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
  105. package/reference/smithy.md +533 -0
  106. package/reference/sql/db-codec-deriver.md +71 -0
  107. package/reference/sql/db-codec.md +687 -0
  108. package/reference/sql/db-con.md +271 -0
  109. package/reference/sql/db-connection.md +153 -0
  110. package/reference/sql/db-param-writer.md +77 -0
  111. package/reference/sql/db-param.md +66 -0
  112. package/reference/sql/db-result-reader.md +146 -0
  113. package/reference/sql/db-tx.md +82 -0
  114. package/reference/sql/db-value.md +41 -0
  115. package/reference/sql/ddl.md +85 -0
  116. package/reference/sql/frag.md +254 -0
  117. package/reference/sql/index.md +341 -0
  118. package/reference/sql/repo.md +600 -0
  119. package/reference/sql/sql-dialect.md +73 -0
  120. package/reference/sql/sql-logger.md +62 -0
  121. package/reference/sql/sql-name-mapper.md +70 -0
  122. package/reference/sql/table-metadata.md +134 -0
  123. package/reference/sql/table.md +448 -0
  124. package/reference/sql/transactor-zio.md +399 -0
  125. package/reference/sql/transactor.md +353 -0
  126. package/reference/sql-zio.md +112 -0
  127. package/reference/streams/concurrent-operators.md +106 -0
  128. package/reference/streams/index.md +653 -0
  129. package/reference/streams/pipeline.md +718 -0
  130. package/reference/streams/reader.md +1284 -0
  131. package/reference/streams/scala-2-compatibility.md +55 -0
  132. package/reference/streams/sink.md +1426 -0
  133. package/reference/streams/stream.md +2526 -0
  134. package/reference/streams/writer.md +1045 -0
  135. package/reference/streams/zero-boxing.md +275 -0
  136. package/reference/telemetry.md +693 -0
  137. package/reference/typeid.md +2922 -583
  138. package/sidebars.js +238 -43
  139. package/superpowers/plans/2026-03-19-docs-critique-subagent.md +407 -0
  140. package/superpowers/specs/2026-03-19-docs-critique-subagent-design.md +222 -0
  141. package/reference/formats.md +0 -694
  142. package/reference/http-model.md +0 -1716
  143. package/reference/streams.md +0 -989
  144. package/ringbuffer.md +0 -249
  145. /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
  146. /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
  147. /package/reference/{lazy.md → schema/lazy.md} +0 -0
  148. /package/reference/{reflect.md → schema/reflect.md} +0 -0
  149. /package/reference/{registers.md → schema/registers.md} +0 -0
  150. /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
  151. /package/reference/{syntax.md → schema/syntax.md} +0 -0
  152. /package/reference/{validation.md → schema/validation.md} +0 -0
@@ -9,7 +9,7 @@ This is Part 2 of the Query DSL series. [Part 1](./query-dsl-reified-optics.md)
9
9
 
10
10
  **What we'll cover:**
11
11
 
12
- - Interpreting `SchemaExpr` as a sealed AST via pattern matching
12
+ - Interpreting `SchemaExpr` while keeping the typed API at the boundary
13
13
  - Extracting column names from optic paths using `DynamicOptic`
14
14
  - Translating relational, logical, arithmetic, and string operations to SQL
15
15
  - Building complete `SELECT ... FROM ... WHERE ...` statements
@@ -36,14 +36,14 @@ def findProducts(category: Option[String], maxPrice: Option[Double], inStock: Op
36
36
 
37
37
  This is fragile, repetitive, and vulnerable to SQL injection. Every new query shape requires new string-building code. The query logic is duplicated -- once as a `SchemaExpr` for in-memory filtering, and again as hand-written SQL for the database.
38
38
 
39
- Since `SchemaExpr` is a sealed trait, we can write a single interpreter that translates *any* query expression into SQL. Write the interpreter once, and every query you build with the Part 1 DSL automatically gets a SQL translation.
39
+ `SchemaExpr` is the user-facing query API. Internally it wraps a `DynamicSchemaExpr` — a sealed trait whose cases represent the full expression AST. That means we can write a single interpreter that accepts `SchemaExpr`, then crosses into the dynamic AST internally to translate *any* query expression into SQL. Write the interpreter once, and every query you build with the Part 1 DSL automatically gets a SQL translation.
40
40
 
41
41
  ## Prerequisites
42
42
 
43
43
  This guide builds on [Part 1: Expressions](./query-dsl-reified-optics.md). You should be comfortable building `SchemaExpr` values with optic operators (`===`, `>`, `&&`, etc.).
44
44
 
45
45
  ```scala
46
- libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.32"
46
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.51"
47
47
  ```
48
48
 
49
49
  ```scala
@@ -74,24 +74,33 @@ object Product extends CompanionOptics[Product] {
74
74
  }
75
75
  ```
76
76
 
77
- ## The SchemaExpr AST
77
+ ## The SchemaExpr API
78
78
 
79
- Before we build the interpreter, let's understand the structure we are interpreting. `SchemaExpr` is a sealed trait with these cases:
79
+ Before we build the interpreter, keep the API boundary in mind: application code builds `SchemaExpr[A, B]` values, while interpreter code may inspect the underlying `DynamicSchemaExpr` through `.dynamic`.
80
80
 
81
81
  ```
82
- SchemaExpr[A, B]
83
- ├── Literal[S, A](value, schema) -- a constant value
84
- ├── Optic[A, B](optic) -- a field reference
85
- ├── StringRegexMatch[A](regex, string) -- regex pattern matching
86
- ├── StringLength[A](string) -- string length calculation
87
- ├── UnaryOp[A, B] -- abstract trait for unary operations
88
- │ └── Not[A](expr) -- boolean negation
89
- └── BinaryOp[A, B, C] -- abstract trait for binary operations
90
- ├── Relational[A, B](left, right, operator) -- comparison operations
91
- ├── Logical[A](left, right, operator) -- boolean operations
92
- ├── Arithmetic[S, A](left, right, operator, isNumeric) -- numeric operations
93
- └── StringConcat[A](left, right) -- string concatenation
82
+ SchemaExpr[A, B] -- user-facing, typed API
83
+ └── .dynamic: DynamicSchemaExpr -- interpreter/runtime boundary
84
+ ├── Select(path: DynamicOptic) -- field reference
85
+ ├── Literal(value: DynamicValue, schema: Schema[_]) -- constant value
86
+ ├── Relational(left, right, op) -- comparisons
87
+ ├── Logical(left, right, op) -- boolean operators
88
+ ├── Not(expr) -- negation
89
+ ├── Arithmetic(left, right, op, _) -- numeric operators
90
+ ├── StringConcat(left, right) -- string concatenation
91
+ ├── StringRegexMatch(regex, string) -- pattern matching
92
+ └── StringLength(string) -- string length
93
+ ```
94
+
95
+ Most users never need to construct `DynamicSchemaExpr` directly. The normal workflow is:
96
+
97
+ 1. Build a typed `SchemaExpr` with optics and operators.
98
+ 2. Pass that `SchemaExpr` to your interpreter.
99
+ 3. Let the interpreter read `.dynamic` internally.
100
+
101
+ The dynamic cases are still worth understanding because they are what your interpreter will pattern-match on:
94
102
 
103
+ ```
95
104
  RelationalOperator
96
105
  ├── LessThan
97
106
  ├── GreaterThan
@@ -110,7 +119,7 @@ ArithmeticOperator
110
119
  └── Multiply
111
120
  ```
112
121
 
113
- Each case carries enough information to produce SQL: `Optic` nodes carry field paths, `Literal` nodes carry values, and operator nodes carry the operation type. Our interpreter walks this tree and emits SQL fragments.
122
+ Each dynamic case carries enough information to produce SQL: `Select` nodes carry field paths, `Literal` nodes carry values, and operator nodes carry the operation type. Our interpreter walks this tree and emits SQL fragments.
114
123
 
115
124
  ## Extracting Column Names from Optics
116
125
 
@@ -121,6 +130,9 @@ def columnName(optic: zio.blocks.schema.Optic[?, ?]): String = {
121
130
  val nodes = optic.toDynamic.nodes
122
131
  nodes.collect { case f: DynamicOptic.Node.Field => f.name }.mkString("_")
123
132
  }
133
+
134
+ def columnName(path: DynamicOptic): String =
135
+ path.nodes.collect { case f: DynamicOptic.Node.Field => f.name }.mkString("_")
124
136
  ```
125
137
 
126
138
  This converts the optic path to a column name. For a simple field like `Product.price`, it produces `"price"`. For a nested path, it joins field names with underscores (we will refine this for table-qualified names later).
@@ -136,92 +148,106 @@ columnName(Product.category)
136
148
 
137
149
  ## Translating Literals to SQL
138
150
 
139
- Literal values need proper SQL formatting -- strings must be quoted, booleans converted to SQL syntax:
151
+ Once we cross the interpreter boundary, literal values appear as `DynamicValue`. We need a function to format them as SQL:
140
152
 
141
153
  ```scala
142
- def sqlLiteral(value: Any): String = value match {
143
- case s: String => s"'${s.replace("'", "''")}'"
144
- case b: Boolean => if (b) "TRUE" else "FALSE"
145
- case n: Number => n.toString
146
- case other => other.toString
154
+ def sqlLiteralDV(dv: DynamicValue): String = dv match {
155
+ case DynamicValue.Primitive(pv) =>
156
+ pv match {
157
+ case PrimitiveValue.String(s) => s"'${s.replace("'", "''")}'"
158
+ case PrimitiveValue.Boolean(b) => if (b) "TRUE" else "FALSE"
159
+ case PrimitiveValue.Int(n) => n.toString
160
+ case PrimitiveValue.Long(n) => n.toString
161
+ case PrimitiveValue.Double(n) => n.toString
162
+ case PrimitiveValue.Float(n) => n.toString
163
+ case PrimitiveValue.Short(n) => n.toString
164
+ case PrimitiveValue.Byte(n) => n.toString
165
+ case other => other.toString
166
+ }
167
+ case other => other.toString
147
168
  }
148
169
  ```
149
170
 
150
171
  ## Building the SQL Interpreter
151
172
 
152
- Now we build the core interpreter. It pattern-matches on each `SchemaExpr` case and produces a SQL string:
173
+ Now we build the core interpreter. The public entry point accepts `SchemaExpr`; the internal helper does the `DynamicSchemaExpr` pattern matching:
153
174
 
154
175
  ```scala
155
- def toSql[A, B](expr: SchemaExpr[A, B]): String = expr match {
176
+ def toSql[A, B](expr: SchemaExpr[A, B]): String = toSqlDynamic(expr.dynamic)
177
+
178
+ private def toSqlDynamic(expr: DynamicSchemaExpr): String = expr match {
156
179
 
157
180
  // Field reference → column name
158
- case SchemaExpr.Optic(optic) =>
159
- columnName(optic)
181
+ case DynamicSchemaExpr.Select(path) =>
182
+ columnName(path)
160
183
 
161
184
  // Constant value → SQL literal
162
- case SchemaExpr.Literal(value, _) =>
163
- sqlLiteral(value)
185
+ case DynamicSchemaExpr.Literal(value, _) =>
186
+ sqlLiteralDV(value)
164
187
 
165
188
  // Comparison operators → SQL relational operators
166
- case SchemaExpr.Relational(left, right, op) =>
189
+ case DynamicSchemaExpr.Relational(left, right, op) =>
167
190
  val sqlOp = op match {
168
- case SchemaExpr.RelationalOperator.Equal => "="
169
- case SchemaExpr.RelationalOperator.NotEqual => "<>"
170
- case SchemaExpr.RelationalOperator.LessThan => "<"
171
- case SchemaExpr.RelationalOperator.LessThanOrEqual => "<="
172
- case SchemaExpr.RelationalOperator.GreaterThan => ">"
173
- case SchemaExpr.RelationalOperator.GreaterThanOrEqual => ">="
191
+ case DynamicSchemaExpr.RelationalOperator.Equal => "="
192
+ case DynamicSchemaExpr.RelationalOperator.NotEqual => "<>"
193
+ case DynamicSchemaExpr.RelationalOperator.LessThan => "<"
194
+ case DynamicSchemaExpr.RelationalOperator.LessThanOrEqual => "<="
195
+ case DynamicSchemaExpr.RelationalOperator.GreaterThan => ">"
196
+ case DynamicSchemaExpr.RelationalOperator.GreaterThanOrEqual => ">="
174
197
  }
175
- s"(${toSql(left)} $sqlOp ${toSql(right)})"
198
+ s"(${toSqlDynamic(left)} $sqlOp ${toSqlDynamic(right)})"
176
199
 
177
200
  // Boolean operators → AND / OR
178
- case SchemaExpr.Logical(left, right, op) =>
201
+ case DynamicSchemaExpr.Logical(left, right, op) =>
179
202
  val sqlOp = op match {
180
- case SchemaExpr.LogicalOperator.And => "AND"
181
- case SchemaExpr.LogicalOperator.Or => "OR"
203
+ case DynamicSchemaExpr.LogicalOperator.And => "AND"
204
+ case DynamicSchemaExpr.LogicalOperator.Or => "OR"
182
205
  }
183
- s"(${toSql(left)} $sqlOp ${toSql(right)})"
206
+ s"(${toSqlDynamic(left)} $sqlOp ${toSqlDynamic(right)})"
184
207
 
185
208
  // Negation → NOT
186
- case SchemaExpr.Not(inner) =>
187
- s"NOT (${toSql(inner)})"
209
+ case DynamicSchemaExpr.Not(inner) =>
210
+ s"NOT (${toSqlDynamic(inner)})"
188
211
 
189
212
  // Arithmetic → SQL math operators
190
- case SchemaExpr.Arithmetic(left, right, op, _) =>
213
+ case DynamicSchemaExpr.Arithmetic(left, right, op, _) =>
191
214
  val sqlOp = op match {
192
- case SchemaExpr.ArithmeticOperator.Add => "+"
193
- case SchemaExpr.ArithmeticOperator.Subtract => "-"
194
- case SchemaExpr.ArithmeticOperator.Multiply => "*"
215
+ case DynamicSchemaExpr.ArithmeticOperator.Add => "+"
216
+ case DynamicSchemaExpr.ArithmeticOperator.Subtract => "-"
217
+ case DynamicSchemaExpr.ArithmeticOperator.Multiply => "*"
218
+ case _ => "?"
195
219
  }
196
- s"(${toSql(left)} $sqlOp ${toSql(right)})"
220
+ s"(${toSqlDynamic(left)} $sqlOp ${toSqlDynamic(right)})"
197
221
 
198
222
  // String concatenation → CONCAT()
199
- case SchemaExpr.StringConcat(left, right) =>
200
- s"CONCAT(${toSql(left)}, ${toSql(right)})"
223
+ case DynamicSchemaExpr.StringConcat(left, right) =>
224
+ s"CONCAT(${toSqlDynamic(left)}, ${toSqlDynamic(right)})"
201
225
 
202
226
  // Regex match → column LIKE pattern (simplified)
203
- case SchemaExpr.StringRegexMatch(regex, string) =>
204
- s"(${toSql(string)} LIKE ${toSql(regex)})"
227
+ case DynamicSchemaExpr.StringRegexMatch(regex, string) =>
228
+ s"(${toSqlDynamic(string)} LIKE ${toSqlDynamic(regex)})"
205
229
 
206
230
  // String length → LENGTH()
207
- case SchemaExpr.StringLength(string) =>
208
- s"LENGTH(${toSql(string)})"
231
+ case DynamicSchemaExpr.StringLength(string) =>
232
+ s"LENGTH(${toSqlDynamic(string)})"
233
+
234
+ case _ => "?"
209
235
  }
210
236
  ```
211
237
 
212
- The mapping from `SchemaExpr` to SQL is direct:
238
+ The mapping from `DynamicSchemaExpr` to SQL is direct, but that dynamic matching stays inside the interpreter implementation:
213
239
 
214
- | SchemaExpr Case | SQL Output |
215
- |---------------------|--------------------------------------|
216
- | `Optic(optic)` | Column name from `toDynamic` |
217
- | `Literal(v, _)` | SQL literal (`'text'`, `42`, `TRUE`) |
218
- | `Relational(_, _, op)` | `=`, `<>`, `<`, `>`, `<=`, `>=` |
219
- | `Logical(_, _, op)` | `AND`, `OR` |
220
- | `Not(expr)` | `NOT (...)` |
221
- | `Arithmetic(_, _, op, _)` | `+`, `-`, `*` |
222
- | `StringConcat` | `CONCAT(a, b)` |
223
- | `StringRegexMatch` | `LIKE` (pattern matching) |
224
- | `StringLength` | `LENGTH(col)` |
240
+ | DynamicSchemaExpr Case | SQL Output |
241
+ |------------------------|--------------------------------------|
242
+ | `Select(path)` | Column name from `DynamicOptic` |
243
+ | `Literal(value, schema)` | SQL literal (`'text'`, `42`, `TRUE`) |
244
+ | `Relational(_, _, op)` | `=`, `<>`, `<`, `>`, `<=`, `>=` |
245
+ | `Logical(_, _, op)` | `AND`, `OR` |
246
+ | `Not(expr)` | `NOT (...)` |
247
+ | `Arithmetic(_, _, op, _)` | `+`, `-`, `*` |
248
+ | `StringConcat` | `CONCAT(a, b)` |
249
+ | `StringRegexMatch` | `LIKE` (pattern matching) |
250
+ | `StringLength` | `LENGTH(col)` |
225
251
 
226
252
  ## Generating SQL from Queries
227
253
 
@@ -367,63 +393,80 @@ The `toSql` function above inlines literal values directly into the SQL string.
367
393
  ```scala
368
394
  case class SqlQuery(sql: String, params: List[Any])
369
395
 
370
- def toParameterized[A, B](expr: SchemaExpr[A, B]): SqlQuery = expr match {
371
-
372
- case SchemaExpr.Optic(optic) =>
373
- SqlQuery(columnName(optic), Nil)
374
-
375
- case SchemaExpr.Literal(value, _) =>
376
- SqlQuery("?", List(value))
396
+ def toParameterized[A, B](expr: SchemaExpr[A, B]): SqlQuery = toParameterizedDynamic(expr.dynamic)
397
+
398
+ private def toParameterizedDynamic(expr: DynamicSchemaExpr): SqlQuery = expr match {
399
+
400
+ case DynamicSchemaExpr.Select(path) =>
401
+ SqlQuery(columnName(path), Nil)
402
+
403
+ case DynamicSchemaExpr.Literal(value, _) =>
404
+ val param = value match {
405
+ case DynamicValue.Primitive(pv) => pv match {
406
+ case PrimitiveValue.String(s) => s
407
+ case PrimitiveValue.Boolean(b) => b
408
+ case PrimitiveValue.Int(n) => n
409
+ case PrimitiveValue.Long(n) => n
410
+ case PrimitiveValue.Double(n) => n
411
+ case PrimitiveValue.Float(n) => n
412
+ case PrimitiveValue.Short(n) => n
413
+ case PrimitiveValue.Byte(n) => n
414
+ case PrimitiveValue.BigInt(n) => n
415
+ case PrimitiveValue.BigDecimal(n) => n
416
+ case PrimitiveValue.Char(c) => c
417
+ case other => other.toString
418
+ }
419
+ case other => other.toString
420
+ }
421
+ SqlQuery("?", List(param))
377
422
 
378
- case SchemaExpr.Relational(left, right, op) =>
379
- val l = toParameterized(left)
380
- val r = toParameterized(right)
423
+ case DynamicSchemaExpr.Relational(left, right, op) =>
424
+ val l = toParameterizedDynamic(left); val r = toParameterizedDynamic(right)
381
425
  val sqlOp = op match {
382
- case SchemaExpr.RelationalOperator.Equal => "="
383
- case SchemaExpr.RelationalOperator.NotEqual => "<>"
384
- case SchemaExpr.RelationalOperator.LessThan => "<"
385
- case SchemaExpr.RelationalOperator.LessThanOrEqual => "<="
386
- case SchemaExpr.RelationalOperator.GreaterThan => ">"
387
- case SchemaExpr.RelationalOperator.GreaterThanOrEqual => ">="
426
+ case DynamicSchemaExpr.RelationalOperator.Equal => "="
427
+ case DynamicSchemaExpr.RelationalOperator.NotEqual => "<>"
428
+ case DynamicSchemaExpr.RelationalOperator.LessThan => "<"
429
+ case DynamicSchemaExpr.RelationalOperator.LessThanOrEqual => "<="
430
+ case DynamicSchemaExpr.RelationalOperator.GreaterThan => ">"
431
+ case DynamicSchemaExpr.RelationalOperator.GreaterThanOrEqual => ">="
388
432
  }
389
433
  SqlQuery(s"(${l.sql} $sqlOp ${r.sql})", l.params ++ r.params)
390
434
 
391
- case SchemaExpr.Logical(left, right, op) =>
392
- val l = toParameterized(left)
393
- val r = toParameterized(right)
435
+ case DynamicSchemaExpr.Logical(left, right, op) =>
436
+ val l = toParameterizedDynamic(left); val r = toParameterizedDynamic(right)
394
437
  val sqlOp = op match {
395
- case SchemaExpr.LogicalOperator.And => "AND"
396
- case SchemaExpr.LogicalOperator.Or => "OR"
438
+ case DynamicSchemaExpr.LogicalOperator.And => "AND"
439
+ case DynamicSchemaExpr.LogicalOperator.Or => "OR"
397
440
  }
398
441
  SqlQuery(s"(${l.sql} $sqlOp ${r.sql})", l.params ++ r.params)
399
442
 
400
- case SchemaExpr.Not(inner) =>
401
- val i = toParameterized(inner)
443
+ case DynamicSchemaExpr.Not(inner) =>
444
+ val i = toParameterizedDynamic(inner)
402
445
  SqlQuery(s"NOT (${i.sql})", i.params)
403
446
 
404
- case SchemaExpr.Arithmetic(left, right, op, _) =>
405
- val l = toParameterized(left)
406
- val r = toParameterized(right)
447
+ case DynamicSchemaExpr.Arithmetic(left, right, op, _) =>
448
+ val l = toParameterizedDynamic(left); val r = toParameterizedDynamic(right)
407
449
  val sqlOp = op match {
408
- case SchemaExpr.ArithmeticOperator.Add => "+"
409
- case SchemaExpr.ArithmeticOperator.Subtract => "-"
410
- case SchemaExpr.ArithmeticOperator.Multiply => "*"
450
+ case DynamicSchemaExpr.ArithmeticOperator.Add => "+"
451
+ case DynamicSchemaExpr.ArithmeticOperator.Subtract => "-"
452
+ case DynamicSchemaExpr.ArithmeticOperator.Multiply => "*"
453
+ case _ => "?"
411
454
  }
412
455
  SqlQuery(s"(${l.sql} $sqlOp ${r.sql})", l.params ++ r.params)
413
456
 
414
- case SchemaExpr.StringConcat(left, right) =>
415
- val l = toParameterized(left)
416
- val r = toParameterized(right)
457
+ case DynamicSchemaExpr.StringConcat(left, right) =>
458
+ val l = toParameterizedDynamic(left); val r = toParameterizedDynamic(right)
417
459
  SqlQuery(s"CONCAT(${l.sql}, ${r.sql})", l.params ++ r.params)
418
460
 
419
- case SchemaExpr.StringRegexMatch(regex, string) =>
420
- val s = toParameterized(string)
421
- val r = toParameterized(regex)
461
+ case DynamicSchemaExpr.StringRegexMatch(regex, string) =>
462
+ val s = toParameterizedDynamic(string); val r = toParameterizedDynamic(regex)
422
463
  SqlQuery(s"(${s.sql} LIKE ${r.sql})", s.params ++ r.params)
423
464
 
424
- case SchemaExpr.StringLength(string) =>
425
- val s = toParameterized(string)
465
+ case DynamicSchemaExpr.StringLength(string) =>
466
+ val s = toParameterizedDynamic(string)
426
467
  SqlQuery(s"LENGTH(${s.sql})", s.params)
468
+
469
+ case _ => SqlQuery("?", Nil)
427
470
  }
428
471
  ```
429
472
 
@@ -549,90 +592,128 @@ object Product extends CompanionOptics[Product] {
549
592
  def columnName(optic: zio.blocks.schema.Optic[?, ?]): String =
550
593
  optic.toDynamic.nodes.collect { case f: DynamicOptic.Node.Field => f.name }.mkString("_")
551
594
 
552
- def sqlLiteral(value: Any): String = value match {
553
- case s: String => s"'${s.replace("'", "''")}'"
554
- case b: Boolean => if (b) "TRUE" else "FALSE"
555
- case n: Number => n.toString
556
- case other => other.toString
595
+ def columnName(path: DynamicOptic): String =
596
+ path.nodes.collect { case f: DynamicOptic.Node.Field => f.name }.mkString("_")
597
+
598
+ def sqlLiteralDV(dv: DynamicValue): String = dv match {
599
+ case DynamicValue.Primitive(pv) =>
600
+ pv match {
601
+ case PrimitiveValue.String(s) => s"'${s.replace("'", "''")}'"
602
+ case PrimitiveValue.Boolean(b) => if (b) "TRUE" else "FALSE"
603
+ case PrimitiveValue.Int(n) => n.toString
604
+ case PrimitiveValue.Long(n) => n.toString
605
+ case PrimitiveValue.Double(n) => n.toString
606
+ case PrimitiveValue.Float(n) => n.toString
607
+ case PrimitiveValue.Short(n) => n.toString
608
+ case PrimitiveValue.Byte(n) => n.toString
609
+ case other => other.toString
610
+ }
611
+ case other => other.toString
557
612
  }
558
613
 
559
- def toSql[A, B](expr: SchemaExpr[A, B]): String = expr match {
560
- case SchemaExpr.Optic(optic) => columnName(optic)
561
- case SchemaExpr.Literal(value, _) => sqlLiteral(value)
562
- case SchemaExpr.Relational(left, right, op) =>
614
+ def toSql[A, B](expr: SchemaExpr[A, B]): String = toSqlDynamic(expr.dynamic)
615
+
616
+ private def toSqlDynamic(expr: DynamicSchemaExpr): String = expr match {
617
+ case DynamicSchemaExpr.Select(path) => columnName(path)
618
+ case DynamicSchemaExpr.Literal(value, _) => sqlLiteralDV(value)
619
+ case DynamicSchemaExpr.Relational(left, right, op) =>
563
620
  val sqlOp = op match {
564
- case SchemaExpr.RelationalOperator.Equal => "="
565
- case SchemaExpr.RelationalOperator.NotEqual => "<>"
566
- case SchemaExpr.RelationalOperator.LessThan => "<"
567
- case SchemaExpr.RelationalOperator.LessThanOrEqual => "<="
568
- case SchemaExpr.RelationalOperator.GreaterThan => ">"
569
- case SchemaExpr.RelationalOperator.GreaterThanOrEqual => ">="
621
+ case DynamicSchemaExpr.RelationalOperator.Equal => "="
622
+ case DynamicSchemaExpr.RelationalOperator.NotEqual => "<>"
623
+ case DynamicSchemaExpr.RelationalOperator.LessThan => "<"
624
+ case DynamicSchemaExpr.RelationalOperator.LessThanOrEqual => "<="
625
+ case DynamicSchemaExpr.RelationalOperator.GreaterThan => ">"
626
+ case DynamicSchemaExpr.RelationalOperator.GreaterThanOrEqual => ">="
570
627
  }
571
- s"(${toSql(left)} $sqlOp ${toSql(right)})"
572
- case SchemaExpr.Logical(left, right, op) =>
628
+ s"(${toSqlDynamic(left)} $sqlOp ${toSqlDynamic(right)})"
629
+ case DynamicSchemaExpr.Logical(left, right, op) =>
573
630
  val sqlOp = op match {
574
- case SchemaExpr.LogicalOperator.And => "AND"
575
- case SchemaExpr.LogicalOperator.Or => "OR"
631
+ case DynamicSchemaExpr.LogicalOperator.And => "AND"
632
+ case DynamicSchemaExpr.LogicalOperator.Or => "OR"
576
633
  }
577
- s"(${toSql(left)} $sqlOp ${toSql(right)})"
578
- case SchemaExpr.Not(inner) => s"NOT (${toSql(inner)})"
579
- case SchemaExpr.Arithmetic(left, right, op, _) =>
634
+ s"(${toSqlDynamic(left)} $sqlOp ${toSqlDynamic(right)})"
635
+ case DynamicSchemaExpr.Not(inner) => s"NOT (${toSqlDynamic(inner)})"
636
+ case DynamicSchemaExpr.Arithmetic(left, right, op, _) =>
580
637
  val sqlOp = op match {
581
- case SchemaExpr.ArithmeticOperator.Add => "+"
582
- case SchemaExpr.ArithmeticOperator.Subtract => "-"
583
- case SchemaExpr.ArithmeticOperator.Multiply => "*"
638
+ case DynamicSchemaExpr.ArithmeticOperator.Add => "+"
639
+ case DynamicSchemaExpr.ArithmeticOperator.Subtract => "-"
640
+ case DynamicSchemaExpr.ArithmeticOperator.Multiply => "*"
641
+ case _ => "?"
584
642
  }
585
- s"(${toSql(left)} $sqlOp ${toSql(right)})"
586
- case SchemaExpr.StringConcat(left, right) => s"CONCAT(${toSql(left)}, ${toSql(right)})"
587
- case SchemaExpr.StringRegexMatch(regex, string) => s"(${toSql(string)} LIKE ${toSql(regex)})"
588
- case SchemaExpr.StringLength(string) => s"LENGTH(${toSql(string)})"
643
+ s"(${toSqlDynamic(left)} $sqlOp ${toSqlDynamic(right)})"
644
+ case DynamicSchemaExpr.StringConcat(left, right) => s"CONCAT(${toSqlDynamic(left)}, ${toSqlDynamic(right)})"
645
+ case DynamicSchemaExpr.StringRegexMatch(regex, string) => s"(${toSqlDynamic(string)} LIKE ${toSqlDynamic(regex)})"
646
+ case DynamicSchemaExpr.StringLength(string) => s"LENGTH(${toSqlDynamic(string)})"
647
+ case _ => "?"
589
648
  }
590
649
 
591
650
  // --- Parameterized queries ---
592
651
 
593
652
  case class SqlQuery(sql: String, params: List[Any])
594
653
 
595
- def toParameterized[A, B](expr: SchemaExpr[A, B]): SqlQuery = expr match {
596
- case SchemaExpr.Optic(optic) => SqlQuery(columnName(optic), Nil)
597
- case SchemaExpr.Literal(value, _) => SqlQuery("?", List(value))
598
- case SchemaExpr.Relational(left, right, op) =>
599
- val l = toParameterized(left); val r = toParameterized(right)
654
+ def toParameterized[A, B](expr: SchemaExpr[A, B]): SqlQuery = toParameterizedDynamic(expr.dynamic)
655
+
656
+ private def toParameterizedDynamic(expr: DynamicSchemaExpr): SqlQuery = expr match {
657
+ case DynamicSchemaExpr.Select(path) => SqlQuery(columnName(path), Nil)
658
+ case DynamicSchemaExpr.Literal(value, _) =>
659
+ val param = value match {
660
+ case DynamicValue.Primitive(pv) => pv match {
661
+ case PrimitiveValue.String(s) => s
662
+ case PrimitiveValue.Boolean(b) => b
663
+ case PrimitiveValue.Int(n) => n
664
+ case PrimitiveValue.Long(n) => n
665
+ case PrimitiveValue.Double(n) => n
666
+ case PrimitiveValue.Float(n) => n
667
+ case PrimitiveValue.Short(n) => n
668
+ case PrimitiveValue.Byte(n) => n
669
+ case PrimitiveValue.BigInt(n) => n
670
+ case PrimitiveValue.BigDecimal(n) => n
671
+ case PrimitiveValue.Char(c) => c
672
+ case other => other.toString
673
+ }
674
+ case other => other.toString
675
+ }
676
+ SqlQuery("?", List(param))
677
+ case DynamicSchemaExpr.Relational(left, right, op) =>
678
+ val l = toParameterizedDynamic(left); val r = toParameterizedDynamic(right)
600
679
  val sqlOp = op match {
601
- case SchemaExpr.RelationalOperator.Equal => "="
602
- case SchemaExpr.RelationalOperator.NotEqual => "<>"
603
- case SchemaExpr.RelationalOperator.LessThan => "<"
604
- case SchemaExpr.RelationalOperator.LessThanOrEqual => "<="
605
- case SchemaExpr.RelationalOperator.GreaterThan => ">"
606
- case SchemaExpr.RelationalOperator.GreaterThanOrEqual => ">="
680
+ case DynamicSchemaExpr.RelationalOperator.Equal => "="
681
+ case DynamicSchemaExpr.RelationalOperator.NotEqual => "<>"
682
+ case DynamicSchemaExpr.RelationalOperator.LessThan => "<"
683
+ case DynamicSchemaExpr.RelationalOperator.LessThanOrEqual => "<="
684
+ case DynamicSchemaExpr.RelationalOperator.GreaterThan => ">"
685
+ case DynamicSchemaExpr.RelationalOperator.GreaterThanOrEqual => ">="
607
686
  }
608
687
  SqlQuery(s"(${l.sql} $sqlOp ${r.sql})", l.params ++ r.params)
609
- case SchemaExpr.Logical(left, right, op) =>
610
- val l = toParameterized(left); val r = toParameterized(right)
688
+ case DynamicSchemaExpr.Logical(left, right, op) =>
689
+ val l = toParameterizedDynamic(left); val r = toParameterizedDynamic(right)
611
690
  val sqlOp = op match {
612
- case SchemaExpr.LogicalOperator.And => "AND"
613
- case SchemaExpr.LogicalOperator.Or => "OR"
691
+ case DynamicSchemaExpr.LogicalOperator.And => "AND"
692
+ case DynamicSchemaExpr.LogicalOperator.Or => "OR"
614
693
  }
615
694
  SqlQuery(s"(${l.sql} $sqlOp ${r.sql})", l.params ++ r.params)
616
- case SchemaExpr.Not(inner) =>
617
- val i = toParameterized(inner)
695
+ case DynamicSchemaExpr.Not(inner) =>
696
+ val i = toParameterizedDynamic(inner)
618
697
  SqlQuery(s"NOT (${i.sql})", i.params)
619
- case SchemaExpr.Arithmetic(left, right, op, _) =>
620
- val l = toParameterized(left); val r = toParameterized(right)
698
+ case DynamicSchemaExpr.Arithmetic(left, right, op, _) =>
699
+ val l = toParameterizedDynamic(left); val r = toParameterizedDynamic(right)
621
700
  val sqlOp = op match {
622
- case SchemaExpr.ArithmeticOperator.Add => "+"
623
- case SchemaExpr.ArithmeticOperator.Subtract => "-"
624
- case SchemaExpr.ArithmeticOperator.Multiply => "*"
701
+ case DynamicSchemaExpr.ArithmeticOperator.Add => "+"
702
+ case DynamicSchemaExpr.ArithmeticOperator.Subtract => "-"
703
+ case DynamicSchemaExpr.ArithmeticOperator.Multiply => "*"
704
+ case _ => "?"
625
705
  }
626
706
  SqlQuery(s"(${l.sql} $sqlOp ${r.sql})", l.params ++ r.params)
627
- case SchemaExpr.StringConcat(left, right) =>
628
- val l = toParameterized(left); val r = toParameterized(right)
707
+ case DynamicSchemaExpr.StringConcat(left, right) =>
708
+ val l = toParameterizedDynamic(left); val r = toParameterizedDynamic(right)
629
709
  SqlQuery(s"CONCAT(${l.sql}, ${r.sql})", l.params ++ r.params)
630
- case SchemaExpr.StringRegexMatch(regex, string) =>
631
- val s = toParameterized(string); val r = toParameterized(regex)
710
+ case DynamicSchemaExpr.StringRegexMatch(regex, string) =>
711
+ val s = toParameterizedDynamic(string); val r = toParameterizedDynamic(regex)
632
712
  SqlQuery(s"(${s.sql} LIKE ${r.sql})", s.params ++ r.params)
633
- case SchemaExpr.StringLength(string) =>
634
- val s = toParameterized(string)
713
+ case DynamicSchemaExpr.StringLength(string) =>
714
+ val s = toParameterizedDynamic(string)
635
715
  SqlQuery(s"LENGTH(${s.sql})", s.params)
716
+ case _ => SqlQuery("?", Nil)
636
717
  }
637
718
 
638
719
  // --- Complete SELECT builder ---
@@ -673,8 +754,8 @@ println(toSql(Product.price * 0.9))
673
754
  - **[Part 1: Expressions](./query-dsl-reified-optics.md)** -- Building query expressions with reified optics
674
755
  - **[Part 3: Extending the Expression Language](./query-dsl-extending.md)** -- Adding custom operators (IN, BETWEEN, aggregates) beyond SchemaExpr
675
756
  - **[Part 4: A Fluent SQL Builder](./query-dsl-fluent-builder.md)** -- Type-safe SELECT, UPDATE, INSERT, DELETE with seamless condition mixing
676
- - **[SchemaExpr Reference](../reference/schema-expr.md)** -- Full API coverage of expression types
677
- - **[Optics Reference](../reference/optics.md)** -- Lens, Prism, Optional, and Traversal
678
- - **[DynamicOptic Reference](../reference/dynamic-optic.md)** -- Runtime optic paths for programmatic field extraction
757
+ - **[SchemaExpr Reference](../reference/schema/schema-expr.md)** -- Full API coverage of expression types
758
+ - **[Optics Reference](../reference/schema/optics.md)** -- Lens, Prism, Optional, and Traversal
759
+ - **[DynamicOptic Reference](../reference/schema/dynamic-optic.md)** -- Runtime optic paths for programmatic field extraction
679
760
 
680
- The interpreter pattern shown here extends naturally to other query targets. Because `SchemaExpr` is a sealed trait and `DynamicOptic` carries full path metadata, you can write interpreters for MongoDB filters, Elasticsearch queries, GraphQL filters, or any other query language using the same approach: pattern match on the AST, map operators, and extract field names from optic paths.
761
+ The interpreter pattern shown here extends naturally to other query targets. Because `SchemaExpr` wraps a `DynamicSchemaExpr` sealed trait and `DynamicOptic` carries full path metadata, you can write interpreters for MongoDB filters, Elasticsearch queries, GraphQL filters, or any other query language using the same approach: access `.dynamic`, pattern match on the AST, map operators, and extract field names from optic paths.