@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.
- 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 +293 -51
- 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} +3 -3
- 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} +34 -192
- 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 +5 -5
- package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
- package/reference/{schema-evolution → schema/schema-evolution}/into.md +3 -3
- package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
- package/reference/{schema.md → schema/schema.md} +13 -1
- 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 +2922 -583
- package/sidebars.js +238 -43
- package/superpowers/plans/2026-03-19-docs-critique-subagent.md +407 -0
- package/superpowers/specs/2026-03-19-docs-critique-subagent-design.md +222 -0
- 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
package/guides/query-dsl-sql.md
CHANGED
|
@@ -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`
|
|
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
|
-
|
|
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.
|
|
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
|
|
77
|
+
## The SchemaExpr API
|
|
78
78
|
|
|
79
|
-
Before we build the interpreter,
|
|
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
|
-
|
|
84
|
-
├──
|
|
85
|
-
├──
|
|
86
|
-
├──
|
|
87
|
-
├──
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
├──
|
|
91
|
-
├──
|
|
92
|
-
|
|
93
|
-
|
|
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: `
|
|
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
|
-
|
|
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
|
|
143
|
-
case
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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.
|
|
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
|
|
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
|
|
159
|
-
columnName(
|
|
181
|
+
case DynamicSchemaExpr.Select(path) =>
|
|
182
|
+
columnName(path)
|
|
160
183
|
|
|
161
184
|
// Constant value → SQL literal
|
|
162
|
-
case
|
|
163
|
-
|
|
185
|
+
case DynamicSchemaExpr.Literal(value, _) =>
|
|
186
|
+
sqlLiteralDV(value)
|
|
164
187
|
|
|
165
188
|
// Comparison operators → SQL relational operators
|
|
166
|
-
case
|
|
189
|
+
case DynamicSchemaExpr.Relational(left, right, op) =>
|
|
167
190
|
val sqlOp = op match {
|
|
168
|
-
case
|
|
169
|
-
case
|
|
170
|
-
case
|
|
171
|
-
case
|
|
172
|
-
case
|
|
173
|
-
case
|
|
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"(${
|
|
198
|
+
s"(${toSqlDynamic(left)} $sqlOp ${toSqlDynamic(right)})"
|
|
176
199
|
|
|
177
200
|
// Boolean operators → AND / OR
|
|
178
|
-
case
|
|
201
|
+
case DynamicSchemaExpr.Logical(left, right, op) =>
|
|
179
202
|
val sqlOp = op match {
|
|
180
|
-
case
|
|
181
|
-
case
|
|
203
|
+
case DynamicSchemaExpr.LogicalOperator.And => "AND"
|
|
204
|
+
case DynamicSchemaExpr.LogicalOperator.Or => "OR"
|
|
182
205
|
}
|
|
183
|
-
s"(${
|
|
206
|
+
s"(${toSqlDynamic(left)} $sqlOp ${toSqlDynamic(right)})"
|
|
184
207
|
|
|
185
208
|
// Negation → NOT
|
|
186
|
-
case
|
|
187
|
-
s"NOT (${
|
|
209
|
+
case DynamicSchemaExpr.Not(inner) =>
|
|
210
|
+
s"NOT (${toSqlDynamic(inner)})"
|
|
188
211
|
|
|
189
212
|
// Arithmetic → SQL math operators
|
|
190
|
-
case
|
|
213
|
+
case DynamicSchemaExpr.Arithmetic(left, right, op, _) =>
|
|
191
214
|
val sqlOp = op match {
|
|
192
|
-
case
|
|
193
|
-
case
|
|
194
|
-
case
|
|
215
|
+
case DynamicSchemaExpr.ArithmeticOperator.Add => "+"
|
|
216
|
+
case DynamicSchemaExpr.ArithmeticOperator.Subtract => "-"
|
|
217
|
+
case DynamicSchemaExpr.ArithmeticOperator.Multiply => "*"
|
|
218
|
+
case _ => "?"
|
|
195
219
|
}
|
|
196
|
-
s"(${
|
|
220
|
+
s"(${toSqlDynamic(left)} $sqlOp ${toSqlDynamic(right)})"
|
|
197
221
|
|
|
198
222
|
// String concatenation → CONCAT()
|
|
199
|
-
case
|
|
200
|
-
s"CONCAT(${
|
|
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
|
|
204
|
-
s"(${
|
|
227
|
+
case DynamicSchemaExpr.StringRegexMatch(regex, string) =>
|
|
228
|
+
s"(${toSqlDynamic(string)} LIKE ${toSqlDynamic(regex)})"
|
|
205
229
|
|
|
206
230
|
// String length → LENGTH()
|
|
207
|
-
case
|
|
208
|
-
s"LENGTH(${
|
|
231
|
+
case DynamicSchemaExpr.StringLength(string) =>
|
|
232
|
+
s"LENGTH(${toSqlDynamic(string)})"
|
|
233
|
+
|
|
234
|
+
case _ => "?"
|
|
209
235
|
}
|
|
210
236
|
```
|
|
211
237
|
|
|
212
|
-
The mapping from `
|
|
238
|
+
The mapping from `DynamicSchemaExpr` to SQL is direct, but that dynamic matching stays inside the interpreter implementation:
|
|
213
239
|
|
|
214
|
-
|
|
|
215
|
-
|
|
216
|
-
| `
|
|
217
|
-
| `Literal(
|
|
218
|
-
| `Relational(_, _, op)` | `=`, `<>`, `<`, `>`, `<=`, `>=`
|
|
219
|
-
| `Logical(_, _, op)`
|
|
220
|
-
| `Not(expr)`
|
|
221
|
-
| `Arithmetic(_, _, op, _)` | `+`, `-`, `*`
|
|
222
|
-
| `StringConcat`
|
|
223
|
-
| `StringRegexMatch`
|
|
224
|
-
| `StringLength`
|
|
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
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
|
379
|
-
val l =
|
|
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
|
|
383
|
-
case
|
|
384
|
-
case
|
|
385
|
-
case
|
|
386
|
-
case
|
|
387
|
-
case
|
|
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
|
|
392
|
-
val l =
|
|
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
|
|
396
|
-
case
|
|
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
|
|
401
|
-
val i =
|
|
443
|
+
case DynamicSchemaExpr.Not(inner) =>
|
|
444
|
+
val i = toParameterizedDynamic(inner)
|
|
402
445
|
SqlQuery(s"NOT (${i.sql})", i.params)
|
|
403
446
|
|
|
404
|
-
case
|
|
405
|
-
val l =
|
|
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
|
|
409
|
-
case
|
|
410
|
-
case
|
|
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
|
|
415
|
-
val l =
|
|
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
|
|
420
|
-
val s =
|
|
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
|
|
425
|
-
val s =
|
|
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
|
|
553
|
-
case
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
case
|
|
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
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
case
|
|
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
|
|
565
|
-
case
|
|
566
|
-
case
|
|
567
|
-
case
|
|
568
|
-
case
|
|
569
|
-
case
|
|
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"(${
|
|
572
|
-
case
|
|
628
|
+
s"(${toSqlDynamic(left)} $sqlOp ${toSqlDynamic(right)})"
|
|
629
|
+
case DynamicSchemaExpr.Logical(left, right, op) =>
|
|
573
630
|
val sqlOp = op match {
|
|
574
|
-
case
|
|
575
|
-
case
|
|
631
|
+
case DynamicSchemaExpr.LogicalOperator.And => "AND"
|
|
632
|
+
case DynamicSchemaExpr.LogicalOperator.Or => "OR"
|
|
576
633
|
}
|
|
577
|
-
s"(${
|
|
578
|
-
case
|
|
579
|
-
case
|
|
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
|
|
582
|
-
case
|
|
583
|
-
case
|
|
638
|
+
case DynamicSchemaExpr.ArithmeticOperator.Add => "+"
|
|
639
|
+
case DynamicSchemaExpr.ArithmeticOperator.Subtract => "-"
|
|
640
|
+
case DynamicSchemaExpr.ArithmeticOperator.Multiply => "*"
|
|
641
|
+
case _ => "?"
|
|
584
642
|
}
|
|
585
|
-
s"(${
|
|
586
|
-
case
|
|
587
|
-
case
|
|
588
|
-
case
|
|
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
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
case
|
|
599
|
-
|
|
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
|
|
602
|
-
case
|
|
603
|
-
case
|
|
604
|
-
case
|
|
605
|
-
case
|
|
606
|
-
case
|
|
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
|
|
610
|
-
val l =
|
|
688
|
+
case DynamicSchemaExpr.Logical(left, right, op) =>
|
|
689
|
+
val l = toParameterizedDynamic(left); val r = toParameterizedDynamic(right)
|
|
611
690
|
val sqlOp = op match {
|
|
612
|
-
case
|
|
613
|
-
case
|
|
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
|
|
617
|
-
val i =
|
|
695
|
+
case DynamicSchemaExpr.Not(inner) =>
|
|
696
|
+
val i = toParameterizedDynamic(inner)
|
|
618
697
|
SqlQuery(s"NOT (${i.sql})", i.params)
|
|
619
|
-
case
|
|
620
|
-
val l =
|
|
698
|
+
case DynamicSchemaExpr.Arithmetic(left, right, op, _) =>
|
|
699
|
+
val l = toParameterizedDynamic(left); val r = toParameterizedDynamic(right)
|
|
621
700
|
val sqlOp = op match {
|
|
622
|
-
case
|
|
623
|
-
case
|
|
624
|
-
case
|
|
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
|
|
628
|
-
val l =
|
|
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
|
|
631
|
-
val s =
|
|
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
|
|
634
|
-
val s =
|
|
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`
|
|
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.
|