@zio.dev/zio-blocks 0.0.33 → 0.0.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) 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 +292 -50
  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} +5 -5
  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} +13 -9
  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 +8 -8
  99. package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
  100. package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
  101. package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
  102. package/reference/{schema.md → schema/schema.md} +12 -0
  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 +5 -19
  138. package/sidebars.js +238 -43
  139. package/reference/formats.md +0 -694
  140. package/reference/http-model.md +0 -1716
  141. package/reference/streams.md +0 -989
  142. package/ringbuffer.md +0 -249
  143. /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
  144. /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
  145. /package/reference/{lazy.md → schema/lazy.md} +0 -0
  146. /package/reference/{reflect.md → schema/reflect.md} +0 -0
  147. /package/reference/{registers.md → schema/registers.md} +0 -0
  148. /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
  149. /package/reference/{syntax.md → schema/syntax.md} +0 -0
  150. /package/reference/{validation.md → schema/validation.md} +0 -0
@@ -45,14 +45,14 @@ FROM products
45
45
 
46
46
  None of these can be expressed with `SchemaExpr` alone. You could generate the SQL strings manually, but then you lose composability — you can no longer mix these operations with the type-safe `SchemaExpr` predicates from Parts 1 and 2.
47
47
 
48
- Since `SchemaExpr` is a sealed trait, you cannot add new cases to it. Instead, we define an `Expr` ADT that is a superset of `SchemaExpr` — it includes equivalent nodes for everything `SchemaExpr` can express, plus our custom SQL-specific operations. A `fromSchemaExpr` function translates `SchemaExpr` values into `Expr`, enabling seamless interoperability with a single unified interpreter.
48
+ `SchemaExpr` wraps a `DynamicSchemaExpr` sealed trait that you cannot add new cases to. The public API is `SchemaExpr`; `DynamicSchemaExpr` is the raw serializable AST exposed through `.dynamic`. Instead of extending `DynamicSchemaExpr` directly, we define an `Expr` ADT that is a superset — it includes equivalent nodes for everything `SchemaExpr` can express, plus our custom SQL-specific operations. A `fromSchemaExpr` function translates `SchemaExpr` values into `Expr` by reading the underlying `DynamicSchemaExpr`, enabling seamless interoperability with a single unified interpreter.
49
49
 
50
50
  ## Prerequisites
51
51
 
52
52
  This guide builds on [Part 1: Expressions](./query-dsl-reified-optics.md) and [Part 2: SQL Generation](./query-dsl-sql.md). You should be comfortable building `SchemaExpr` values and translating them to SQL.
53
53
 
54
54
  ```scala
55
- libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.33"
55
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.51"
56
56
  ```
57
57
 
58
58
  ```scala
@@ -85,31 +85,25 @@ object Product extends CompanionOptics[Product] {
85
85
 
86
86
  ## Designing the Expr ADT
87
87
 
88
- The key insight is the **translation pattern**: define your own sealed trait whose node types are a superset of `SchemaExpr`'s, then provide a `fromSchemaExpr` function that converts any `SchemaExpr` into your ADT. This gives you a single unified interpreter.
88
+ The key insight is the **translation pattern**: keep your public extension API typed, then provide a `fromSchemaExpr` function that lifts any built-in `SchemaExpr` into your extended ADT. Ordinary application code stays on `SchemaExpr`, `Optic`, and typed constructors. Only the interpreter internals need to inspect `.dynamic`.
89
89
 
90
90
  ```
91
- Built-in (sealed, not extensible) Your extension (superset)
91
+ Built-in API Your extension layer
92
92
  ┌───────────────────────────────┐ ┌───────────────────────────────────────┐
93
93
  │ SchemaExpr[S, A] │ │ Expr[S, A] │
94
- │ ├── Literal │──────▶│ ├── Lit(value, schema) │
95
- │ ├── Optic │──────▶│ ├── Column(Optic) │
96
- │ ├── Relational │──────▶│ ├── Relational(left, right, RelOp) │
97
- │ ├── Logical (And/Or) │──────▶│ ├── And / Or │
98
- │ ├── Not │──────▶│ ├── Not │
99
- │ ├── Arithmetic │──────▶│ ├── Arithmetic(left, right, ArithOp) │
100
- │ ├── StringConcat │──────▶│ ├── StringConcat │
101
- │ ├── StringRegexMatch │──────▶│ ├── StringRegexMatch │
102
- │ └── StringLength │──────▶│ ├── StringLength │
103
- └───────────────────────────────┘ │ ├── In(expr, values, schema) ← new │
104
- fromSchemaExpr ────────────────│ ├── Between(expr, low, high, schema) ← new │
105
- │ ├── IsNull(expr) ← new │
106
- │ ├── Like(expr, pattern) ← new │
107
- │ ├── Agg(function, expr) ← new │
108
- │ └── CaseWhen(branches, else) ← new │
94
+ │ built with optics/operators │──────▶│ ├── Builtin(schemaExpr) │
95
+ └───────────────────────────────┘ │ ├── Column(optic) │
96
+ fromSchemaExpr ────────────────│ ├── Lit(value, schema) │
97
+ │ ├── In(expr, values, schema) │
98
+ │ ├── Between(expr, low, high, schema) │
99
+ │ ├── IsNull(expr) │
100
+ │ ├── Like(expr, pattern) │
101
+ │ ├── Agg(function, expr) │
102
+ │ └── CaseWhen(branches, else) │
109
103
  └───────────────────────────────────────┘
110
104
  ```
111
105
 
112
- The `Expr` ADT includes mirrored nodes for every `SchemaExpr` case, plus SQL-specific extensions. It uses its own operator types (`RelOp`, `ArithOp`) and type-safe aggregate functions (`AggFunction[A, B]`).
106
+ The `Expr` ADT keeps the public surface typed. It can wrap any built-in `SchemaExpr` through `Builtin`, and it adds SQL-specific nodes on top. The interpreter is still free to inspect `schemaExpr.dynamic` internally when it needs to translate the built-in pieces.
113
107
 
114
108
  Here is the full `Expr` ADT with its supporting types:
115
109
 
@@ -118,7 +112,8 @@ sealed trait Expr[S, A]
118
112
 
119
113
  object Expr {
120
114
 
121
- // --- Core nodes (superset of SchemaExpr's nodes) ---
115
+ // --- Typed public nodes ---
116
+ final case class Builtin[S, A](schemaExpr: SchemaExpr[S, A]) extends Expr[S, A]
122
117
  final case class Column[S, A](optic: Optic[S, A]) extends Expr[S, A]
123
118
  final case class Lit[S, A](value: A, schema: Schema[A]) extends Expr[S, A]
124
119
 
@@ -172,43 +167,7 @@ object Expr {
172
167
  }
173
168
 
174
169
  // --- Translation from SchemaExpr ---
175
- def fromSchemaExpr[S, A](se: SchemaExpr[S, A]): Expr[S, A] = {
176
- val result = se match {
177
- case SchemaExpr.Optic(optic) => Column(optic)
178
- case l: SchemaExpr.Literal[_, _] => Lit(l.value, l.schema)
179
-
180
- case SchemaExpr.Relational(l, r, op) =>
181
- val relOp = op match {
182
- case SchemaExpr.RelationalOperator.Equal => RelOp.Equal
183
- case SchemaExpr.RelationalOperator.NotEqual => RelOp.NotEqual
184
- case SchemaExpr.RelationalOperator.LessThan => RelOp.LessThan
185
- case SchemaExpr.RelationalOperator.LessThanOrEqual => RelOp.LessThanOrEqual
186
- case SchemaExpr.RelationalOperator.GreaterThan => RelOp.GreaterThan
187
- case SchemaExpr.RelationalOperator.GreaterThanOrEqual => RelOp.GreaterThanOrEqual
188
- }
189
- Relational(fromSchemaExpr(l), fromSchemaExpr(r), relOp)
190
-
191
- case SchemaExpr.Logical(l, r, op) => op match {
192
- case SchemaExpr.LogicalOperator.And => And(fromSchemaExpr(l), fromSchemaExpr(r))
193
- case SchemaExpr.LogicalOperator.Or => Or(fromSchemaExpr(l), fromSchemaExpr(r))
194
- }
195
-
196
- case SchemaExpr.Not(inner) => Not(fromSchemaExpr(inner))
197
-
198
- case SchemaExpr.Arithmetic(l, r, op, _) =>
199
- val arithOp = op match {
200
- case SchemaExpr.ArithmeticOperator.Add => ArithOp.Add
201
- case SchemaExpr.ArithmeticOperator.Subtract => ArithOp.Subtract
202
- case SchemaExpr.ArithmeticOperator.Multiply => ArithOp.Multiply
203
- }
204
- Arithmetic(fromSchemaExpr(l), fromSchemaExpr(r), arithOp)
205
-
206
- case SchemaExpr.StringConcat(l, r) => StringConcat(fromSchemaExpr(l), fromSchemaExpr(r))
207
- case SchemaExpr.StringRegexMatch(regex, string) => StringRegexMatch(fromSchemaExpr(regex), fromSchemaExpr(string))
208
- case SchemaExpr.StringLength(string) => StringLength(fromSchemaExpr(string))
209
- }
210
- result.asInstanceOf[Expr[S, A]]
211
- }
170
+ def fromSchemaExpr[S, A](se: SchemaExpr[S, A]): Expr[S, A] = Builtin(se)
212
171
  }
213
172
 
214
173
  // --- Operators ---
@@ -246,8 +205,8 @@ object AggFunction {
246
205
  Here are some keynotes on the design:
247
206
 
248
207
  - **Type-safe aggregates** — `AggFunction[A, B]` encodes the return type: `COUNT` returns `Long`, `SUM`/`AVG` return `Double`, `MIN`/`MAX` preserve the input type.
249
- - **Typed literals and predicates** — `Lit(value, schema)`, `In(expr, values, schema)`, and `Between(expr, low, high, schema)` all carry a `Schema[A]` so the SQL renderer can format values correctly using the schema rather than runtime type checks.
250
- - **`fromSchemaExpr`** — one-way translation recursively converts every `SchemaExpr` node into its `Expr` equivalent, mapping operators along the way.
208
+ - **Typed public constructors** — `Column` stores an `Optic` and `Lit` stores a typed value plus its `Schema`, so extension code stays on the same public abstractions as ordinary `SchemaExpr` code.
209
+ - **`fromSchemaExpr`** — lifts a built-in `SchemaExpr` into the extended ADT. The dynamic representation is only consulted later by the interpreter.
251
210
 
252
211
  ## Extension Methods
253
212
 
@@ -290,12 +249,15 @@ The `.toExpr` method is still available for cases where you need to explicitly l
290
249
 
291
250
  ## The Unified SQL Interpreter
292
251
 
293
- With the `Expr` ADT, we write a single interpreter that handles all cases directly:
252
+ With the `Expr` ADT, we write a single interpreter that handles all cases directly. Typed extension nodes stay typed; built-in `SchemaExpr` fragments delegate to a helper that reads `.dynamic` internally:
294
253
 
295
254
  ```scala
296
255
  def columnName(optic: zio.blocks.schema.Optic[_, _]): String =
297
256
  optic.toDynamic.nodes.collect { case f: DynamicOptic.Node.Field => f.name }.mkString("_")
298
257
 
258
+ def columnName(path: DynamicOptic): String =
259
+ path.nodes.collect { case f: DynamicOptic.Node.Field => f.name }.mkString("_")
260
+
299
261
  def sqlLiteral[A](value: A, schema: Schema[A]): String = {
300
262
  val dv = schema.toDynamicValue(value)
301
263
  dv match {
@@ -308,9 +270,26 @@ def sqlLiteral[A](value: A, schema: Schema[A]): String = {
308
270
  }
309
271
  }
310
272
 
273
+ def sqlLiteralDV(dv: DynamicValue): String = dv match {
274
+ case DynamicValue.Primitive(pv) =>
275
+ pv match {
276
+ case PrimitiveValue.String(s) => s"'${s.replace("'", "''")}'"
277
+ case PrimitiveValue.Boolean(b) => if (b) "TRUE" else "FALSE"
278
+ case PrimitiveValue.Int(n) => n.toString
279
+ case PrimitiveValue.Long(n) => n.toString
280
+ case PrimitiveValue.Double(n) => n.toString
281
+ case PrimitiveValue.Float(n) => n.toString
282
+ case PrimitiveValue.Short(n) => n.toString
283
+ case PrimitiveValue.Byte(n) => n.toString
284
+ case other => other.toString
285
+ }
286
+ case other => other.toString
287
+ }
288
+
311
289
  def exprToSql[S, A](expr: Expr[S, A]): String = expr match {
312
- case Expr.Column(optic) => columnName(optic)
313
- case Expr.Lit(value, schema) => sqlLiteral(value, schema)
290
+ case Expr.Builtin(schemaExpr) => schemaExprToSql(schemaExpr)
291
+ case Expr.Column(optic) => columnName(optic)
292
+ case Expr.Lit(value, schema) => sqlLiteral(value, schema)
314
293
 
315
294
  case Expr.Relational(left, right, op) =>
316
295
  val sqlOp = op match {
@@ -358,9 +337,58 @@ def exprToSql[S, A](expr: Expr[S, A]): String = expr match {
358
337
  val elseClause = otherwise.map(e => s" ELSE ${exprToSql(e)}").getOrElse("")
359
338
  s"CASE $cases$elseClause END"
360
339
  }
340
+
341
+ def schemaExprToSql[S, A](expr: SchemaExpr[S, A]): String =
342
+ toSqlDynamic(expr.dynamic)
343
+
344
+ def toSqlDynamic(expr: DynamicSchemaExpr): String = expr match {
345
+ case DynamicSchemaExpr.Select(path) => columnName(path)
346
+ case DynamicSchemaExpr.Literal(value, _) => sqlLiteralDV(value)
347
+
348
+ case DynamicSchemaExpr.Relational(left, right, op) =>
349
+ val sqlOp = op match {
350
+ case DynamicSchemaExpr.RelationalOperator.Equal => "="
351
+ case DynamicSchemaExpr.RelationalOperator.NotEqual => "<>"
352
+ case DynamicSchemaExpr.RelationalOperator.LessThan => "<"
353
+ case DynamicSchemaExpr.RelationalOperator.LessThanOrEqual => "<="
354
+ case DynamicSchemaExpr.RelationalOperator.GreaterThan => ">"
355
+ case DynamicSchemaExpr.RelationalOperator.GreaterThanOrEqual => ">="
356
+ }
357
+ s"(${toSqlDynamic(left)} $sqlOp ${toSqlDynamic(right)})"
358
+
359
+ case DynamicSchemaExpr.Logical(left, right, op) =>
360
+ val sqlOp = op match {
361
+ case DynamicSchemaExpr.LogicalOperator.And => "AND"
362
+ case DynamicSchemaExpr.LogicalOperator.Or => "OR"
363
+ }
364
+ s"(${toSqlDynamic(left)} $sqlOp ${toSqlDynamic(right)})"
365
+
366
+ case DynamicSchemaExpr.Not(inner) =>
367
+ s"NOT (${toSqlDynamic(inner)})"
368
+
369
+ case DynamicSchemaExpr.Arithmetic(left, right, op, _) =>
370
+ val sqlOp = op match {
371
+ case DynamicSchemaExpr.ArithmeticOperator.Add => "+"
372
+ case DynamicSchemaExpr.ArithmeticOperator.Subtract => "-"
373
+ case DynamicSchemaExpr.ArithmeticOperator.Multiply => "*"
374
+ case _ => "?"
375
+ }
376
+ s"(${toSqlDynamic(left)} $sqlOp ${toSqlDynamic(right)})"
377
+
378
+ case DynamicSchemaExpr.StringConcat(left, right) =>
379
+ s"CONCAT(${toSqlDynamic(left)}, ${toSqlDynamic(right)})"
380
+
381
+ case DynamicSchemaExpr.StringRegexMatch(regex, string) =>
382
+ s"(${toSqlDynamic(string)} LIKE ${toSqlDynamic(regex)})"
383
+
384
+ case DynamicSchemaExpr.StringLength(string) =>
385
+ s"LENGTH(${toSqlDynamic(string)})"
386
+
387
+ case _ => "?"
388
+ }
361
389
  ```
362
390
 
363
- The typed `sqlLiteral[A](value, schema)` uses the `Schema` carried by `Lit`, `In`, and `Between` to format values correctly — strings get quoted, booleans become `TRUE`/`FALSE`, numbers stay as-is. Every AST node that holds literal values carries a `Schema[A]`, so a single `sqlLiteral` function handles all formatting with no untyped fallbacks.
391
+ The typed `sqlLiteral[A](value, schema)` uses the `Schema` carried by `Lit`, `In`, and `Between` to format values correctly — strings get quoted, booleans become `TRUE`/`FALSE`, numbers stay as-is. `sqlLiteralDV` is only needed inside `toSqlDynamic`, where built-in `SchemaExpr` nodes have already crossed into the dynamic representation.
364
392
 
365
393
  ## SQL-Specific Predicates
366
394
 
@@ -512,7 +540,7 @@ println(selectSql)
512
540
 
513
541
  ## Putting It Together
514
542
 
515
- Here is a complete, self-contained example that defines the independent expression ADT, translates from `SchemaExpr`, and generates advanced SQL:
543
+ Here is a complete, self-contained example that defines the independent expression ADT, lifts built-in `SchemaExpr` values into it, and generates advanced SQL:
516
544
 
517
545
  ```scala
518
546
  import zio.blocks.schema._
@@ -542,6 +570,7 @@ object Product extends CompanionOptics[Product] {
542
570
  sealed trait Expr[S, A]
543
571
 
544
572
  object Expr {
573
+ final case class Builtin[S, A](schemaExpr: SchemaExpr[S, A]) extends Expr[S, A]
545
574
  final case class Column[S, A](optic: Optic[S, A]) extends Expr[S, A]
546
575
  final case class Lit[S, A](value: A, schema: Schema[A]) extends Expr[S, A]
547
576
 
@@ -581,38 +610,7 @@ object Expr {
581
610
  def end: Expr[S, A] = CaseWhen(branches, None)
582
611
  }
583
612
 
584
- def fromSchemaExpr[S, A](se: SchemaExpr[S, A]): Expr[S, A] = {
585
- val result = se match {
586
- case SchemaExpr.Optic(optic) => Column(optic)
587
- case l: SchemaExpr.Literal[_, _] => Lit(l.value, l.schema)
588
- case SchemaExpr.Relational(l, r, op) =>
589
- val relOp = op match {
590
- case SchemaExpr.RelationalOperator.Equal => RelOp.Equal
591
- case SchemaExpr.RelationalOperator.NotEqual => RelOp.NotEqual
592
- case SchemaExpr.RelationalOperator.LessThan => RelOp.LessThan
593
- case SchemaExpr.RelationalOperator.LessThanOrEqual => RelOp.LessThanOrEqual
594
- case SchemaExpr.RelationalOperator.GreaterThan => RelOp.GreaterThan
595
- case SchemaExpr.RelationalOperator.GreaterThanOrEqual => RelOp.GreaterThanOrEqual
596
- }
597
- Relational(fromSchemaExpr(l), fromSchemaExpr(r), relOp)
598
- case SchemaExpr.Logical(l, r, op) => op match {
599
- case SchemaExpr.LogicalOperator.And => And(fromSchemaExpr(l), fromSchemaExpr(r))
600
- case SchemaExpr.LogicalOperator.Or => Or(fromSchemaExpr(l), fromSchemaExpr(r))
601
- }
602
- case SchemaExpr.Not(inner) => Not(fromSchemaExpr(inner))
603
- case SchemaExpr.Arithmetic(l, r, op, _) =>
604
- val arithOp = op match {
605
- case SchemaExpr.ArithmeticOperator.Add => ArithOp.Add
606
- case SchemaExpr.ArithmeticOperator.Subtract => ArithOp.Subtract
607
- case SchemaExpr.ArithmeticOperator.Multiply => ArithOp.Multiply
608
- }
609
- Arithmetic(fromSchemaExpr(l), fromSchemaExpr(r), arithOp)
610
- case SchemaExpr.StringConcat(l, r) => StringConcat(fromSchemaExpr(l), fromSchemaExpr(r))
611
- case SchemaExpr.StringRegexMatch(regex, string) => StringRegexMatch(fromSchemaExpr(regex), fromSchemaExpr(string))
612
- case SchemaExpr.StringLength(string) => StringLength(fromSchemaExpr(string))
613
- }
614
- result.asInstanceOf[Expr[S, A]]
615
- }
613
+ def fromSchemaExpr[S, A](se: SchemaExpr[S, A]): Expr[S, A] = Builtin(se)
616
614
  }
617
615
 
618
616
  sealed trait RelOp
@@ -673,6 +671,9 @@ implicit final class SchemaExprBooleanBridge[S](private val self: SchemaExpr[S,
673
671
  def columnName(optic: zio.blocks.schema.Optic[_, _]): String =
674
672
  optic.toDynamic.nodes.collect { case f: DynamicOptic.Node.Field => f.name }.mkString("_")
675
673
 
674
+ def columnName(path: DynamicOptic): String =
675
+ path.nodes.collect { case f: DynamicOptic.Node.Field => f.name }.mkString("_")
676
+
676
677
  def sqlLiteral[A](value: A, schema: Schema[A]): String = {
677
678
  val dv = schema.toDynamicValue(value)
678
679
  dv match {
@@ -685,9 +686,26 @@ def sqlLiteral[A](value: A, schema: Schema[A]): String = {
685
686
  }
686
687
  }
687
688
 
689
+ def sqlLiteralDV(dv: DynamicValue): String = dv match {
690
+ case DynamicValue.Primitive(pv) =>
691
+ pv match {
692
+ case PrimitiveValue.String(s) => s"'${s.replace("'", "''")}'"
693
+ case PrimitiveValue.Boolean(b) => if (b) "TRUE" else "FALSE"
694
+ case PrimitiveValue.Int(n) => n.toString
695
+ case PrimitiveValue.Long(n) => n.toString
696
+ case PrimitiveValue.Double(n) => n.toString
697
+ case PrimitiveValue.Float(n) => n.toString
698
+ case PrimitiveValue.Short(n) => n.toString
699
+ case PrimitiveValue.Byte(n) => n.toString
700
+ case other => other.toString
701
+ }
702
+ case other => other.toString
703
+ }
704
+
688
705
  def exprToSql[S, A](expr: Expr[S, A]): String = expr match {
689
- case Expr.Column(optic) => columnName(optic)
690
- case Expr.Lit(value, schema) => sqlLiteral(value, schema)
706
+ case Expr.Builtin(schemaExpr) => schemaExprToSql(schemaExpr)
707
+ case Expr.Column(optic) => columnName(optic)
708
+ case Expr.Lit(value, schema) => sqlLiteral(value, schema)
691
709
  case Expr.Relational(left, right, op) =>
692
710
  val sqlOp = op match {
693
711
  case RelOp.Equal => "="; case RelOp.NotEqual => "<>"
@@ -721,6 +739,47 @@ def exprToSql[S, A](expr: Expr[S, A]): String = expr match {
721
739
  s"CASE $cases$elseClause END"
722
740
  }
723
741
 
742
+ def schemaExprToSql[S, A](expr: SchemaExpr[S, A]): String =
743
+ toSqlDynamic(expr.dynamic)
744
+
745
+ def toSqlDynamic(expr: DynamicSchemaExpr): String = expr match {
746
+ case DynamicSchemaExpr.Select(path) => columnName(path)
747
+ case DynamicSchemaExpr.Literal(value, _) => sqlLiteralDV(value)
748
+ case DynamicSchemaExpr.Relational(left, right, op) =>
749
+ val sqlOp = op match {
750
+ case DynamicSchemaExpr.RelationalOperator.Equal => "="
751
+ case DynamicSchemaExpr.RelationalOperator.NotEqual => "<>"
752
+ case DynamicSchemaExpr.RelationalOperator.LessThan => "<"
753
+ case DynamicSchemaExpr.RelationalOperator.LessThanOrEqual => "<="
754
+ case DynamicSchemaExpr.RelationalOperator.GreaterThan => ">"
755
+ case DynamicSchemaExpr.RelationalOperator.GreaterThanOrEqual => ">="
756
+ }
757
+ s"(${toSqlDynamic(left)} $sqlOp ${toSqlDynamic(right)})"
758
+ case DynamicSchemaExpr.Logical(left, right, op) =>
759
+ val sqlOp = op match {
760
+ case DynamicSchemaExpr.LogicalOperator.And => "AND"
761
+ case DynamicSchemaExpr.LogicalOperator.Or => "OR"
762
+ }
763
+ s"(${toSqlDynamic(left)} $sqlOp ${toSqlDynamic(right)})"
764
+ case DynamicSchemaExpr.Not(inner) =>
765
+ s"NOT (${toSqlDynamic(inner)})"
766
+ case DynamicSchemaExpr.Arithmetic(left, right, op, _) =>
767
+ val sqlOp = op match {
768
+ case DynamicSchemaExpr.ArithmeticOperator.Add => "+"
769
+ case DynamicSchemaExpr.ArithmeticOperator.Subtract => "-"
770
+ case DynamicSchemaExpr.ArithmeticOperator.Multiply => "*"
771
+ case _ => "?"
772
+ }
773
+ s"(${toSqlDynamic(left)} $sqlOp ${toSqlDynamic(right)})"
774
+ case DynamicSchemaExpr.StringConcat(left, right) =>
775
+ s"CONCAT(${toSqlDynamic(left)}, ${toSqlDynamic(right)})"
776
+ case DynamicSchemaExpr.StringRegexMatch(regex, string) =>
777
+ s"(${toSqlDynamic(string)} LIKE ${toSqlDynamic(regex)})"
778
+ case DynamicSchemaExpr.StringLength(string) =>
779
+ s"LENGTH(${toSqlDynamic(string)})"
780
+ case _ => "?"
781
+ }
782
+
724
783
  // --- Usage ---
725
784
 
726
785
  // 1. SQL-specific predicates — seamless composition
@@ -752,7 +811,7 @@ println(s"SELECT name, price, ${exprToSql(tier)} AS tier FROM products")
752
811
  - **[Part 1: Expressions](./query-dsl-reified-optics.md)** -- Building query expressions with reified optics
753
812
  - **[Part 2: SQL Generation](./query-dsl-sql.md)** -- Translating built-in expressions to SQL
754
813
  - **[Part 4: A Fluent SQL Builder](./query-dsl-fluent-builder.md)** -- Type-safe SELECT, UPDATE, INSERT, DELETE with seamless condition mixing
755
- - **[SchemaExpr Reference](../reference/schema-expr.md)** -- Full API coverage of expression types
756
- - **[Optics Reference](../reference/optics.md)** -- Lens, Prism, Optional, and Traversal
814
+ - **[SchemaExpr Reference](../reference/schema/schema-expr.md)** -- Full API coverage of expression types
815
+ - **[Optics Reference](../reference/schema/optics.md)** -- Lens, Prism, Optional, and Traversal
757
816
 
758
- The translation pattern shown here extends to any domain where `SchemaExpr` falls short. The same approach works for MongoDB operators (`$in`, `$exists`, `$elemMatch`), Elasticsearch queries (`terms`, `range`, `exists`), or GraphQL filters. Define an independent ADT, provide a `fromSchemaExpr` translation, add your domain-specific nodes, and write a single unified interpreter.
817
+ The translation pattern shown here extends to any domain where `SchemaExpr` falls short. The same approach works for MongoDB operators (`$in`, `$exists`, `$elemMatch`), Elasticsearch queries (`terms`, `range`, `exists`), or GraphQL filters. Define an independent ADT, provide a `fromSchemaExpr` lift for built-in expressions, add your domain-specific nodes, and let your interpreter inspect `.dynamic` only inside its internal translation helpers.