@zio.dev/zio-blocks 0.0.33 → 0.0.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/guides/compile-time-resource-safety-with-scope.md +16 -17
- package/guides/getting-started-with-mux.md +1507 -0
- package/guides/query-dsl-extending.md +161 -102
- package/guides/query-dsl-fluent-builder.md +217 -157
- package/guides/query-dsl-reified-optics.md +12 -10
- package/guides/query-dsl-sql.md +246 -165
- package/guides/telemetry-guide.md +1069 -0
- package/guides/zio-schema-migration.md +29 -22
- package/index.md +292 -50
- package/package.json +1 -1
- package/plans/config-follow-up-prs.md +188 -0
- package/plans/config-pr-assessment-roadmap.md +310 -0
- package/reference/MuxDataFlow.jsx +250 -0
- package/reference/async.md +651 -0
- package/reference/chunk.md +3533 -308
- package/reference/codegen/case-class.md +436 -0
- package/reference/codegen/emitter-config.md +383 -0
- package/reference/codegen/examples.md +664 -0
- package/reference/codegen/field.md +316 -0
- package/reference/codegen/index.md +317 -0
- package/reference/codegen/scala-emitter.md +392 -0
- package/reference/codegen/scala-file.md +276 -0
- package/reference/codegen/sealed-trait.md +408 -0
- package/reference/codegen/type-definition.md +340 -0
- package/reference/codegen/type-ref.md +201 -0
- package/reference/combinators.md +347 -117
- package/reference/config.md +158 -0
- package/reference/context.md +4 -4
- package/reference/datastar.md +346 -0
- package/reference/docs.md +1461 -345
- package/reference/endpoint/auth-type.md +146 -0
- package/reference/endpoint/endpoint.md +297 -0
- package/reference/endpoint/http-codec.md +249 -0
- package/reference/endpoint/index.md +825 -0
- package/reference/endpoint/path-codec.md +237 -0
- package/reference/endpoint/route-pattern.md +196 -0
- package/reference/endpoint/route-tree.md +111 -0
- package/reference/endpoint/segment-codec.md +212 -0
- package/reference/html.md +1120 -0
- package/reference/htmx/attribute-values.md +359 -0
- package/reference/htmx/hx-encoding.md +111 -0
- package/reference/htmx/hx-params.md +204 -0
- package/reference/htmx/hx-swap.md +276 -0
- package/reference/htmx/hx-sync.md +251 -0
- package/reference/htmx/hx-target.md +314 -0
- package/reference/htmx/hx-trigger.md +457 -0
- package/reference/htmx/hx-url-update.md +239 -0
- package/reference/htmx/index.md +855 -0
- package/reference/http-model/index.md +47 -0
- package/reference/http-model/model.md +1481 -0
- package/reference/http-model/schema.md +747 -0
- package/reference/maybe.md +826 -0
- package/reference/media-type.md +2 -2
- package/reference/mux.mdx +823 -0
- package/reference/openapi.md +1351 -0
- package/reference/resource-management/defer-handle.md +1 -1
- package/reference/resource-management/resource.md +31 -2
- package/reference/resource-management/scope.md +28 -12
- package/reference/resource-management/wire.md +3 -7
- package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
- package/reference/ringbuffer/MpscDiagram.jsx +618 -0
- package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
- package/reference/ringbuffer/SpscDiagram.jsx +677 -0
- package/reference/ringbuffer/advanced.mdx +109 -0
- package/reference/ringbuffer/index.mdx +145 -0
- package/reference/ringbuffer/mpmc.mdx +151 -0
- package/reference/ringbuffer/mpsc.mdx +132 -0
- package/reference/ringbuffer/spmc.mdx +108 -0
- package/reference/ringbuffer/spsc.mdx +344 -0
- package/reference/{allows.md → schema/allows.md} +4 -4
- package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
- package/reference/{binding.md → schema/binding.md} +2 -3
- package/reference/schema/built-in-codecs/avro.md +451 -0
- package/reference/schema/built-in-codecs/bson.md +480 -0
- package/reference/schema/built-in-codecs/csv.md +564 -0
- package/reference/schema/built-in-codecs/index.md +77 -0
- package/reference/schema/built-in-codecs/json/index.md +295 -0
- package/reference/schema/built-in-codecs/json/json-config.md +217 -0
- package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
- package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
- package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
- package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
- package/reference/schema/built-in-codecs/messagepack.md +508 -0
- package/reference/schema/built-in-codecs/thrift.md +433 -0
- package/reference/schema/built-in-codecs/toon.md +1078 -0
- package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
- package/reference/schema/built-in-codecs/yaml.md +552 -0
- package/reference/{codec.md → schema/codec.md} +10 -10
- package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
- package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
- package/reference/schema/format.md +92 -0
- package/reference/schema/index.md +50 -0
- package/reference/schema/migration.md +297 -0
- package/reference/{modifier.md → schema/modifier.md} +58 -7
- package/reference/{optics.md → schema/optics.md} +2 -2
- package/reference/{patch.md → schema/patch.md} +1 -1
- package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
- package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
- package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
- package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
- package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
- package/reference/{schema.md → schema/schema.md} +12 -0
- package/reference/{structural-types.md → schema/structural-types.md} +1 -1
- package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
- package/reference/smithy.md +533 -0
- package/reference/sql/db-codec-deriver.md +71 -0
- package/reference/sql/db-codec.md +687 -0
- package/reference/sql/db-con.md +271 -0
- package/reference/sql/db-connection.md +153 -0
- package/reference/sql/db-param-writer.md +77 -0
- package/reference/sql/db-param.md +66 -0
- package/reference/sql/db-result-reader.md +146 -0
- package/reference/sql/db-tx.md +82 -0
- package/reference/sql/db-value.md +41 -0
- package/reference/sql/ddl.md +85 -0
- package/reference/sql/frag.md +254 -0
- package/reference/sql/index.md +341 -0
- package/reference/sql/repo.md +600 -0
- package/reference/sql/sql-dialect.md +73 -0
- package/reference/sql/sql-logger.md +62 -0
- package/reference/sql/sql-name-mapper.md +70 -0
- package/reference/sql/table-metadata.md +134 -0
- package/reference/sql/table.md +448 -0
- package/reference/sql/transactor-zio.md +399 -0
- package/reference/sql/transactor.md +353 -0
- package/reference/sql-zio.md +112 -0
- package/reference/streams/concurrent-operators.md +106 -0
- package/reference/streams/index.md +653 -0
- package/reference/streams/pipeline.md +718 -0
- package/reference/streams/reader.md +1284 -0
- package/reference/streams/scala-2-compatibility.md +55 -0
- package/reference/streams/sink.md +1426 -0
- package/reference/streams/stream.md +2526 -0
- package/reference/streams/writer.md +1045 -0
- package/reference/streams/zero-boxing.md +275 -0
- package/reference/telemetry.md +693 -0
- package/reference/typeid.md +5 -19
- package/sidebars.js +238 -43
- package/reference/formats.md +0 -694
- package/reference/http-model.md +0 -1716
- package/reference/streams.md +0 -989
- package/ringbuffer.md +0 -249
- /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
- /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
- /package/reference/{lazy.md → schema/lazy.md} +0 -0
- /package/reference/{reflect.md → schema/reflect.md} +0 -0
- /package/reference/{registers.md → schema/registers.md} +0 -0
- /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
- /package/reference/{syntax.md → schema/syntax.md} +0 -0
- /package/reference/{validation.md → schema/validation.md} +0 -0
|
@@ -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
|
-
|
|
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.
|
|
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**:
|
|
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
|
|
91
|
+
Built-in API Your extension layer
|
|
92
92
|
┌───────────────────────────────┐ ┌───────────────────────────────────────┐
|
|
93
93
|
│ SchemaExpr[S, A] │ │ Expr[S, A] │
|
|
94
|
-
│
|
|
95
|
-
│ ├──
|
|
96
|
-
|
|
97
|
-
│ ├──
|
|
98
|
-
│ ├──
|
|
99
|
-
│ ├──
|
|
100
|
-
│ ├──
|
|
101
|
-
│ ├──
|
|
102
|
-
│ └──
|
|
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
|
|
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
|
-
// ---
|
|
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
|
|
250
|
-
- **`fromSchemaExpr`** —
|
|
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.
|
|
313
|
-
case Expr.
|
|
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.
|
|
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,
|
|
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.
|
|
690
|
-
case Expr.
|
|
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`
|
|
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.
|