@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
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: db-result-reader
|
|
3
|
+
title: "DbResultReader"
|
|
4
|
+
description: "Reference for DbResultReader, the interface for reading typed column values from a SQL result set."
|
|
5
|
+
keywords:
|
|
6
|
+
- "DbResultReader Column Access"
|
|
7
|
+
- "Result Set Abstraction"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
`DbResultReader` is a trait for reading typed column values from a database result set. It provides methods to access columns by either their 1-based index (following JDBC convention) or by column label.
|
|
11
|
+
|
|
12
|
+
Application code does not use `DbResultReader` directly — the framework creates it automatically when a `Frag` is executed. It is the read-side counterpart to `DbParamWriter`, which writes values to prepared statements.
|
|
13
|
+
|
|
14
|
+
## Core API
|
|
15
|
+
|
|
16
|
+
The core API reads numeric, text, binary, boolean, temporal, and UUID values, plus column metadata and NULL status. `getArray` also exists but defaults to throwing `UnsupportedOperationException` in the shared trait; only backends that override it (the JVM implementation does) actually support SQL array access:
|
|
17
|
+
|
|
18
|
+
```scala
|
|
19
|
+
trait DbResultReader {
|
|
20
|
+
// Numeric types
|
|
21
|
+
def getInt(index: Int): Int
|
|
22
|
+
def getInt(label: String): Int
|
|
23
|
+
def getLong(index: Int): Long
|
|
24
|
+
def getLong(label: String): Long
|
|
25
|
+
def getDouble(index: Int): Double
|
|
26
|
+
def getDouble(label: String): Double
|
|
27
|
+
def getFloat(index: Int): Float
|
|
28
|
+
def getFloat(label: String): Float
|
|
29
|
+
def getShort(index: Int): Short
|
|
30
|
+
def getShort(label: String): Short
|
|
31
|
+
def getByte(index: Int): Byte
|
|
32
|
+
def getByte(label: String): Byte
|
|
33
|
+
def getBigDecimal(index: Int): java.math.BigDecimal
|
|
34
|
+
def getBigDecimal(label: String): java.math.BigDecimal
|
|
35
|
+
|
|
36
|
+
// Text and binary
|
|
37
|
+
def getString(index: Int): String
|
|
38
|
+
def getString(label: String): String
|
|
39
|
+
def getBytes(index: Int): Array[Byte]
|
|
40
|
+
def getBytes(label: String): Array[Byte]
|
|
41
|
+
|
|
42
|
+
// Boolean
|
|
43
|
+
def getBoolean(index: Int): Boolean
|
|
44
|
+
def getBoolean(label: String): Boolean
|
|
45
|
+
|
|
46
|
+
// Date and time
|
|
47
|
+
def getLocalDate(index: Int): java.time.LocalDate
|
|
48
|
+
def getLocalDate(label: String): java.time.LocalDate
|
|
49
|
+
def getLocalDateTime(index: Int): java.time.LocalDateTime
|
|
50
|
+
def getLocalDateTime(label: String): java.time.LocalDateTime
|
|
51
|
+
def getLocalTime(index: Int): java.time.LocalTime
|
|
52
|
+
def getLocalTime(label: String): java.time.LocalTime
|
|
53
|
+
def getInstant(index: Int): java.time.Instant
|
|
54
|
+
def getInstant(label: String): java.time.Instant
|
|
55
|
+
def getDuration(index: Int): java.time.Duration
|
|
56
|
+
def getDuration(label: String): java.time.Duration
|
|
57
|
+
|
|
58
|
+
// Other types
|
|
59
|
+
def getUUID(index: Int): java.util.UUID
|
|
60
|
+
def getUUID(label: String): java.util.UUID
|
|
61
|
+
def getArray(index: Int): java.sql.Array // default: throws UnsupportedOperationException
|
|
62
|
+
def getArray(label: String): java.sql.Array // default: throws UnsupportedOperationException
|
|
63
|
+
|
|
64
|
+
// Metadata and NULL detection
|
|
65
|
+
def columnLabel(index: Int): String
|
|
66
|
+
def hasColumn(label: String): Boolean
|
|
67
|
+
def wasNull: Boolean
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Usage
|
|
72
|
+
|
|
73
|
+
You access `DbResultReader` through `DbResultSet.reader` after calling `executeQuery` on a prepared statement:
|
|
74
|
+
|
|
75
|
+
```scala
|
|
76
|
+
import zio.blocks.sql._
|
|
77
|
+
|
|
78
|
+
val transactor: Transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
79
|
+
|
|
80
|
+
transactor.connect {
|
|
81
|
+
val con = summon[DbCon].connection
|
|
82
|
+
val stmt = con.prepareStatement("SELECT id, name, age FROM users")
|
|
83
|
+
val rs = stmt.executeQuery()
|
|
84
|
+
|
|
85
|
+
if (rs.next()) {
|
|
86
|
+
// Read by column label
|
|
87
|
+
val id: Long = rs.reader.getLong("id")
|
|
88
|
+
val name: String = rs.reader.getString("name")
|
|
89
|
+
val age: Int = rs.reader.getInt("age")
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
rs.close()
|
|
93
|
+
stmt.close()
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## NULL Handling
|
|
98
|
+
|
|
99
|
+
Numeric and boolean getters return zero-like defaults (`0`, `0L`, `false`) for SQL `NULL`. String, decimal, and temporal getters return `null`. After any `get*` call, check `wasNull` to detect `NULL`:
|
|
100
|
+
|
|
101
|
+
```scala
|
|
102
|
+
import zio.blocks.sql._
|
|
103
|
+
|
|
104
|
+
val transactor: Transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
105
|
+
|
|
106
|
+
transactor.connect {
|
|
107
|
+
val con = summon[DbCon].connection
|
|
108
|
+
val stmt = con.prepareStatement("SELECT bio FROM users LIMIT 1")
|
|
109
|
+
val rs = stmt.executeQuery()
|
|
110
|
+
|
|
111
|
+
if (rs.next()) {
|
|
112
|
+
val bio: String = rs.reader.getString("bio")
|
|
113
|
+
val bioOption: Option[String] = if (rs.reader.wasNull) None else Some(bio)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
rs.close()
|
|
117
|
+
stmt.close()
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
:::caution
|
|
122
|
+
Always call `wasNull` immediately after the `get*` call whose NULL status you want to check. Do not call another `get*` method in between, or `wasNull` will reflect the wrong result.
|
|
123
|
+
:::
|
|
124
|
+
|
|
125
|
+
## How It Works
|
|
126
|
+
|
|
127
|
+
When you use high-level operations like `Frag.query` or `Repo.find`, `DbCodec` internally calls `DbResultReader` methods to decode result rows. You only see this trait directly when dropping down to raw prepared statements for advanced use cases.
|
|
128
|
+
|
|
129
|
+
Label-based access (preferred) lets you ignore column order:
|
|
130
|
+
|
|
131
|
+
```scala
|
|
132
|
+
import zio.blocks.sql._
|
|
133
|
+
|
|
134
|
+
val reader: DbResultReader = ???
|
|
135
|
+
val name = reader.getString("name") // Works regardless of SELECT order
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Index-based access (1-based per JDBC) is faster but requires stable column order:
|
|
139
|
+
|
|
140
|
+
```scala
|
|
141
|
+
import zio.blocks.sql._
|
|
142
|
+
|
|
143
|
+
val reader: DbResultReader = ???
|
|
144
|
+
val name = reader.getString(2) // Assumes name is the 2nd column
|
|
145
|
+
```
|
|
146
|
+
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: db-tx
|
|
3
|
+
title: "DbTx"
|
|
4
|
+
description: "Reference for DbTx, the SQL module's transactional scope marker that extends DbCon with commit-on-success and rollback-on-failure semantics."
|
|
5
|
+
keywords:
|
|
6
|
+
- "DbTx Transaction Scope"
|
|
7
|
+
- "Transactor Transact Context"
|
|
8
|
+
- "DbCon Subtype Marker"
|
|
9
|
+
- "Auto-Commit Disabled Transaction"
|
|
10
|
+
- "Commit Rollback Semantics"
|
|
11
|
+
- "SQL Module Connection Context"
|
|
12
|
+
- "JDBC Transaction Lifecycle"
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
`DbTx` is a marker trait in the `zio-blocks-sql` module that extends `DbCon` to signal a transactional execution scope. It declares no members of its own — its distinct type is what instructs `Transactor#transact` to disable auto-commit, commit the connection on success, and roll back on any thrown exception. You never construct a `DbTx` directly; the `Transactor` creates one and supplies it as a given context to the block passed to `transact`. The connection is always closed when the block exits, whether it commits, rolls back, or throws.
|
|
16
|
+
|
|
17
|
+
Key properties:
|
|
18
|
+
- **Transactional context marker** — A `DbTx` value in scope guarantees the underlying JDBC connection has auto-commit disabled.
|
|
19
|
+
- **Commit-on-success semantics** — The `Transactor` commits the connection when the `transact` block returns normally.
|
|
20
|
+
- **Rollback-on-failure semantics** — Any uncaught exception causes the `Transactor` to roll back the connection before re-throwing.
|
|
21
|
+
|
|
22
|
+
The structural declaration of `DbTx` is:
|
|
23
|
+
|
|
24
|
+
```scala
|
|
25
|
+
trait DbTx extends DbCon
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Every context member that `DbTx` exposes is inherited from `DbCon`, which declares the three fields every SQL operation consumes:
|
|
29
|
+
|
|
30
|
+
```scala
|
|
31
|
+
trait DbCon {
|
|
32
|
+
def connection: DbConnection
|
|
33
|
+
def dialect: SqlDialect
|
|
34
|
+
def logger: SqlLogger
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
The following example opens a transaction via `Transactor#transact`, accesses all three context members, and combines a `Repo` CRUD operation with a hand-written `Frag` query — both of which accept `DbTx` transparently in place of `DbCon`:
|
|
41
|
+
|
|
42
|
+
```scala
|
|
43
|
+
import zio.blocks.sql._
|
|
44
|
+
import zio.blocks.schema.Schema
|
|
45
|
+
|
|
46
|
+
case class User(id: Int, name: String, email: String)
|
|
47
|
+
object User {
|
|
48
|
+
implicit val schema: Schema[User] = Schema.derived
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
val repo = Repo.derived[User, Int]("users", "id", _.id)
|
|
52
|
+
// repo: Repo[User, Int] = zio.blocks.sql.Repo$DerivedRepo@1b21124
|
|
53
|
+
val tx = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
54
|
+
// tx: JdbcTransactor = zio.blocks.sql.JdbcTransactor@1583ec4c
|
|
55
|
+
|
|
56
|
+
// On normal return: transaction commits and connection closes.
|
|
57
|
+
// On any exception: transaction rolls back, then the exception propagates.
|
|
58
|
+
tx.transact {
|
|
59
|
+
// All three context members are accessible via summon[DbTx]
|
|
60
|
+
val conn: DbConnection = summon[DbTx].connection // managed JDBC connection — do not close manually
|
|
61
|
+
val d: SqlDialect = summon[DbTx].dialect
|
|
62
|
+
val log: SqlLogger = summon[DbTx].logger
|
|
63
|
+
|
|
64
|
+
// Repo and Frag operations accept DbTx because DbTx extends DbCon
|
|
65
|
+
repo.table.createTable(summon[DbTx].dialect).update
|
|
66
|
+
repo.insert(User(1, "Alice", "alice@example.com"))
|
|
67
|
+
repo.insert(User(2, "Bob", "bob@example.com"))
|
|
68
|
+
|
|
69
|
+
val all: List[User] = repo.all
|
|
70
|
+
val custom: List[User] =
|
|
71
|
+
sql"SELECT id, name, email FROM users WHERE name LIKE ${"A%"}".query[User]
|
|
72
|
+
|
|
73
|
+
(all, custom)
|
|
74
|
+
}
|
|
75
|
+
// res1: Tuple2[List[User], List[User]] = (
|
|
76
|
+
// List(
|
|
77
|
+
// User(id = 1, name = "Alice", email = "alice@example.com"),
|
|
78
|
+
// User(id = 2, name = "Bob", email = "bob@example.com")
|
|
79
|
+
// ),
|
|
80
|
+
// List(User(id = 1, name = "Alice", email = "alice@example.com"))
|
|
81
|
+
// )
|
|
82
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: db-value
|
|
3
|
+
title: "DbValue"
|
|
4
|
+
description: "Reference for DbValue, the sealed ADT of typed SQL column values shared by Frag, DbCodec, and SqlDialect in the sql module."
|
|
5
|
+
keywords:
|
|
6
|
+
- "DbValue sealed ADT"
|
|
7
|
+
- "SQL column value types"
|
|
8
|
+
- "DbNull DbInt DbString"
|
|
9
|
+
- "typed database parameters"
|
|
10
|
+
- "SqlDialect typeName"
|
|
11
|
+
- "DbCodec toDbValues"
|
|
12
|
+
- "Frag params representation"
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
`DbValue` is a sealed ADT representing every typed SQL column value the `sql` module handles, one `case class` per Scala type. `Frag` stores its bound parameters as `IndexedSeq[DbValue]`, `DbCodec#toDbValues` produces them, and `SqlDialect#typeName` maps a `DbValue` to its DDL type string — it is the common typed currency all three share, with no JDBC imports or dependencies of its own.
|
|
16
|
+
|
|
17
|
+
| Variant | Scala field type | PostgreSQL DDL | SQLite DDL |
|
|
18
|
+
|-------------------|----------------------------|---------------------|------------|
|
|
19
|
+
| `DbNull` | *(none)* | `NULL` | `NULL` |
|
|
20
|
+
| `DbInt` | `Int` | `INTEGER` | `INTEGER` |
|
|
21
|
+
| `DbLong` | `Long` | `BIGINT` | `INTEGER` |
|
|
22
|
+
| `DbDouble` | `Double` | `DOUBLE PRECISION` | `REAL` |
|
|
23
|
+
| `DbFloat` | `Float` | `REAL` | `REAL` |
|
|
24
|
+
| `DbBoolean` | `Boolean` | `BOOLEAN` | `INTEGER` |
|
|
25
|
+
| `DbString` | `String` | `TEXT` | `TEXT` |
|
|
26
|
+
| `DbBigDecimal` | `scala.BigDecimal` | `NUMERIC` | `TEXT` |
|
|
27
|
+
| `DbBytes` | `Array[Byte]` | `BYTEA` | `BLOB` |
|
|
28
|
+
| `DbShort` | `Short` | `SMALLINT` | `INTEGER` |
|
|
29
|
+
| `DbByte` | `Byte` | `SMALLINT` | `INTEGER` |
|
|
30
|
+
| `DbChar` | `Char` | `CHAR(1)` | `TEXT` |
|
|
31
|
+
| `DbLocalDate` | `java.time.LocalDate` | `DATE` | `TEXT` |
|
|
32
|
+
| `DbLocalDateTime` | `java.time.LocalDateTime` | `TIMESTAMP` | `TEXT` |
|
|
33
|
+
| `DbLocalTime` | `java.time.LocalTime` | `TIME` | `TEXT` |
|
|
34
|
+
| `DbInstant` | `java.time.Instant` | `TIMESTAMPTZ` | `TEXT` |
|
|
35
|
+
| `DbDuration` | `java.time.Duration` | `INTERVAL` | `TEXT` |
|
|
36
|
+
| `DbUUID` | `java.util.UUID` | `UUID` | `TEXT` |
|
|
37
|
+
| `DbArray` | `(String, IndexedSeq[Any])` | *(not in typeName)* | *(not in typeName)* |
|
|
38
|
+
|
|
39
|
+
`DbNull` is what `DbParam[Option[A]]` produces for `None` and `DbParam[Maybe[A]]` produces for `Maybe.absent`; `DbCodec` matches on it to decode nullable columns, throwing `IllegalStateException` if a non-optional codec hits it. `DbArray` is the one variant with two fields (`elementType`, `elements`) instead of `value`, used for collection-typed columns and not currently covered by `SqlDialect#typeName`.
|
|
40
|
+
|
|
41
|
+
See [Frag](./frag.md), [DbCodec](./db-codec.md), and the [SQL module index](./index.md).
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: ddl
|
|
3
|
+
title: "Ddl"
|
|
4
|
+
description: "Reference for Ddl, the helper singleton that generates CREATE TABLE and DROP TABLE SQL fragments."
|
|
5
|
+
keywords:
|
|
6
|
+
- "Ddl createTable"
|
|
7
|
+
- "Ddl dropTable"
|
|
8
|
+
- "ColumnDef SQL type"
|
|
9
|
+
- "CREATE TABLE IF NOT EXISTS"
|
|
10
|
+
- "DROP TABLE IF EXISTS"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
`Ddl` is a helper object that generates DDL (Data Definition Language) SQL fragments for creating and dropping tables. `ColumnDef` is a simple data class pairing a column name with a SQL type string and a nullability flag.
|
|
14
|
+
|
|
15
|
+
Normally you don't call `Ddl` directly — `Table#createTable` and `Table#dropTable` use it internally. You may need `Ddl` directly when building custom DDL tooling.
|
|
16
|
+
|
|
17
|
+
## Core API
|
|
18
|
+
|
|
19
|
+
```scala
|
|
20
|
+
object Ddl {
|
|
21
|
+
def createTable(tableName: String, columns: IndexedSeq[ColumnDef]): Frag
|
|
22
|
+
def dropTable(tableName: String): Frag
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
final case class ColumnDef(name: String, sqlType: String, nullable: Boolean)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
Create a `ColumnDef` for each column, then pass them to `Ddl.createTable`:
|
|
31
|
+
|
|
32
|
+
```scala
|
|
33
|
+
import zio.blocks.sql._
|
|
34
|
+
|
|
35
|
+
val transactor: Transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
36
|
+
// transactor: Transactor = zio.blocks.sql.JdbcTransactor@1bf2e7e
|
|
37
|
+
|
|
38
|
+
val columns = IndexedSeq(
|
|
39
|
+
ColumnDef("id", "INTEGER", nullable = false),
|
|
40
|
+
ColumnDef("name", "TEXT", nullable = false),
|
|
41
|
+
ColumnDef("created_at", "TEXT", nullable = true)
|
|
42
|
+
)
|
|
43
|
+
// columns: IndexedSeq[ColumnDef] = Vector(
|
|
44
|
+
// ColumnDef(name = "id", sqlType = "INTEGER", nullable = false),
|
|
45
|
+
// ColumnDef(name = "name", sqlType = "TEXT", nullable = false),
|
|
46
|
+
// ColumnDef(name = "created_at", sqlType = "TEXT", nullable = true)
|
|
47
|
+
// )
|
|
48
|
+
|
|
49
|
+
val createFrag = Ddl.createTable("users", columns)
|
|
50
|
+
// createFrag: Frag = Frag(
|
|
51
|
+
// parts = Vector(
|
|
52
|
+
// """CREATE TABLE IF NOT EXISTS users (
|
|
53
|
+
// id INTEGER NOT NULL,
|
|
54
|
+
// name TEXT NOT NULL,
|
|
55
|
+
// created_at TEXT
|
|
56
|
+
// )"""
|
|
57
|
+
// ),
|
|
58
|
+
// params = Vector()
|
|
59
|
+
// )
|
|
60
|
+
val dropFrag = Ddl.dropTable("users")
|
|
61
|
+
// dropFrag: Frag = Frag(
|
|
62
|
+
// parts = Vector("DROP TABLE IF EXISTS users"),
|
|
63
|
+
// params = Vector()
|
|
64
|
+
// )
|
|
65
|
+
|
|
66
|
+
// Execute the fragments
|
|
67
|
+
transactor.transact {
|
|
68
|
+
createFrag.update
|
|
69
|
+
// ... do work ...
|
|
70
|
+
dropFrag.update
|
|
71
|
+
}
|
|
72
|
+
// res1: Int = 0
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## How It Works
|
|
76
|
+
|
|
77
|
+
`Ddl` is typically used by `Table#createTable`, which derives `ColumnDef` from schema metadata:
|
|
78
|
+
|
|
79
|
+
1. `Table.derived[A]` builds a schema.
|
|
80
|
+
2. `Table#createTable(dialect)` converts schema columns to `ColumnDef` using `dialect.typeName`.
|
|
81
|
+
3. `Ddl.createTable` receives the `ColumnDef` list and generates the SQL fragment.
|
|
82
|
+
|
|
83
|
+
You call `Ddl` directly only when you need custom DDL that doesn't fit the `Table` abstraction.
|
|
84
|
+
|
|
85
|
+
See [Table](./table.md) for the high-level DDL API.
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: frag
|
|
3
|
+
title: "Frag"
|
|
4
|
+
description: "Reference for Frag, the SQL fragment type with safe parameterization and JDBC execution."
|
|
5
|
+
keywords:
|
|
6
|
+
- "SQL Fragment"
|
|
7
|
+
- "Frag SQL Interpolator"
|
|
8
|
+
- "Type-safe SQL parameters"
|
|
9
|
+
- "SQL Injection Prevention"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
`Frag` is an immutable SQL fragment — a piece of SQL text with typed parameter values kept safely separate from the literal SQL. The `sql"..."` string interpolator builds fragments by checking at compile time that every interpolated expression can be bound as a parameter. Fragments compose with `++` and execute through methods like `query`, `update`, and `queryOne`.
|
|
13
|
+
|
|
14
|
+
`Frag` is safe from SQL injection because parameter values never appear in the SQL string — they are stored separately and bound to `?` placeholders at execution time.
|
|
15
|
+
|
|
16
|
+
## Core API
|
|
17
|
+
|
|
18
|
+
```scala
|
|
19
|
+
final case class Frag(parts: IndexedSeq[String], params: IndexedSeq[DbValue]) {
|
|
20
|
+
def ++(other: Frag): Frag
|
|
21
|
+
def sql(dialect: SqlDialect): String
|
|
22
|
+
def queryParams: IndexedSeq[DbValue]
|
|
23
|
+
def isEmpty: Boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
object Frag {
|
|
27
|
+
val empty: Frag
|
|
28
|
+
def literal(sqlStr: String): Frag
|
|
29
|
+
def sequence(frags: Frag*): Frag
|
|
30
|
+
def values[A](rows: Seq[A])(using codec: DbCodec[A]): Frag
|
|
31
|
+
|
|
32
|
+
extension (frag: Frag) {
|
|
33
|
+
def query[A](using DbCon, DbCodec[A]): List[A]
|
|
34
|
+
def queryOne[A](using DbCon, DbCodec[A]): Maybe[A]
|
|
35
|
+
def queryLimit[A](limit: Int)(using DbCon, DbCodec[A]): List[A]
|
|
36
|
+
def update(using DbCon): Int
|
|
37
|
+
def updateReturningKeys[A](using DbCon, DbCodec[A]): List[A]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
Build fragments with the `sql"..."` interpolator and execute them inside a `Transactor` block:
|
|
45
|
+
|
|
46
|
+
```scala
|
|
47
|
+
import zio.blocks.sql._
|
|
48
|
+
import zio.blocks.schema.Schema
|
|
49
|
+
import zio.blocks.maybe.Maybe
|
|
50
|
+
|
|
51
|
+
case class User(id: Int, name: String, email: String)
|
|
52
|
+
object User { implicit val schema: Schema[User] = Schema.derived }
|
|
53
|
+
|
|
54
|
+
val tx: Transactor = JdbcTransactor.fromUrl("jdbc:sqlite::memory:", SqlDialect.SQLite)
|
|
55
|
+
|
|
56
|
+
val userId = 42
|
|
57
|
+
val active = true
|
|
58
|
+
|
|
59
|
+
tx.connect {
|
|
60
|
+
// Query — returns all matching rows
|
|
61
|
+
val users: List[User] =
|
|
62
|
+
sql"SELECT id, name, email FROM users WHERE id > $userId AND active = $active".query[User]
|
|
63
|
+
|
|
64
|
+
// QueryOne — returns at most one row
|
|
65
|
+
val one: Maybe[User] =
|
|
66
|
+
sql"SELECT id, name, email FROM users WHERE id = ${1}".queryOne[User]
|
|
67
|
+
|
|
68
|
+
// Update — returns affected row count
|
|
69
|
+
val deleted: Int =
|
|
70
|
+
sql"DELETE FROM users WHERE active = ${false}".update
|
|
71
|
+
|
|
72
|
+
// UpdateReturningKeys — returns generated keys after INSERT
|
|
73
|
+
val keys: List[Long] =
|
|
74
|
+
sql"INSERT INTO users (name, email) VALUES (${"Alice"}, ${"alice@example.com"})".updateReturningKeys[Long]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Building Fragments
|
|
79
|
+
|
|
80
|
+
**`sql"..."` interpolator** — The primary way to build fragments. Compile-time checked: every interpolated value must have a `DbParam` instance (provided for all common Scala and Java types).
|
|
81
|
+
|
|
82
|
+
```scala
|
|
83
|
+
import zio.blocks.sql._
|
|
84
|
+
|
|
85
|
+
val userId = 42
|
|
86
|
+
// userId: Int = 42
|
|
87
|
+
val frag = sql"SELECT * FROM users WHERE id = $userId"
|
|
88
|
+
// frag: Frag = Frag(
|
|
89
|
+
// parts = ArraySeq("SELECT * FROM users WHERE id = ", ""),
|
|
90
|
+
// params = Vector(DbInt(42))
|
|
91
|
+
// )
|
|
92
|
+
frag.params
|
|
93
|
+
// res2: IndexedSeq[DbValue] = Vector(DbInt(42))
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**`Frag.literal`** — Wrap static SQL text (no parameters):
|
|
97
|
+
|
|
98
|
+
```scala
|
|
99
|
+
val query = sql"SELECT * FROM users" ++ Frag.literal(" ORDER BY name")
|
|
100
|
+
// query: Frag = Frag(
|
|
101
|
+
// parts = ArraySeq("SELECT * FROM users ORDER BY name"),
|
|
102
|
+
// params = Vector()
|
|
103
|
+
// )
|
|
104
|
+
query.sql(SqlDialect.SQLite)
|
|
105
|
+
// res3: String = "SELECT * FROM users ORDER BY name"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**`Frag.values`** — Build a multi-row INSERT VALUES clause:
|
|
109
|
+
|
|
110
|
+
```scala
|
|
111
|
+
import zio.blocks.sql._
|
|
112
|
+
|
|
113
|
+
case class Product(name: String, price: BigDecimal) derives DbCodec
|
|
114
|
+
|
|
115
|
+
val products = List(Product("Widget", BigDecimal("9.99")), Product("Gadget", BigDecimal("24.99")))
|
|
116
|
+
// products: List[Product] = List(
|
|
117
|
+
// Product(name = "Widget", price = 9.99),
|
|
118
|
+
// Product(name = "Gadget", price = 24.99)
|
|
119
|
+
// )
|
|
120
|
+
val insert = Frag.literal("INSERT INTO product (name, price) VALUES ") ++ Frag.values(products)
|
|
121
|
+
// insert: Frag = Frag(
|
|
122
|
+
// parts = Vector(
|
|
123
|
+
// "INSERT INTO product (name, price) VALUES (",
|
|
124
|
+
// ", ",
|
|
125
|
+
// "), (",
|
|
126
|
+
// ", ",
|
|
127
|
+
// ")"
|
|
128
|
+
// ),
|
|
129
|
+
// params = Vector(
|
|
130
|
+
// DbString("Widget"),
|
|
131
|
+
// DbBigDecimal(9.99),
|
|
132
|
+
// DbString("Gadget"),
|
|
133
|
+
// DbBigDecimal(24.99)
|
|
134
|
+
// )
|
|
135
|
+
// )
|
|
136
|
+
insert.sql(SqlDialect.SQLite)
|
|
137
|
+
// res5: String = "INSERT INTO product (name, price) VALUES (?, ?), (?, ?)"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**`Frag.empty`** — The identity fragment for composition. Useful for optional clauses:
|
|
141
|
+
|
|
142
|
+
```scala
|
|
143
|
+
import zio.blocks.sql._
|
|
144
|
+
|
|
145
|
+
val hasFilter = true
|
|
146
|
+
// hasFilter: Boolean = true
|
|
147
|
+
val where = if (hasFilter) sql" WHERE active = ${true}" else Frag.empty
|
|
148
|
+
// where: Frag = Frag(
|
|
149
|
+
// parts = ArraySeq(" WHERE active = ", ""),
|
|
150
|
+
// params = Vector(DbBoolean(true))
|
|
151
|
+
// )
|
|
152
|
+
val query = sql"SELECT * FROM users" ++ where
|
|
153
|
+
// query: Frag = Frag(
|
|
154
|
+
// parts = ArraySeq("SELECT * FROM users WHERE active = ", ""),
|
|
155
|
+
// params = Vector(DbBoolean(true))
|
|
156
|
+
// )
|
|
157
|
+
query.sql(SqlDialect.SQLite)
|
|
158
|
+
// res7: String = "SELECT * FROM users WHERE active = ?"
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**`Frag.sequence`** — Concatenate multiple fragments with no separator:
|
|
162
|
+
|
|
163
|
+
```scala
|
|
164
|
+
import zio.blocks.sql._
|
|
165
|
+
|
|
166
|
+
val status = "active"
|
|
167
|
+
// status: String = "active"
|
|
168
|
+
val base = sql"SELECT * FROM users"
|
|
169
|
+
// base: Frag = Frag(
|
|
170
|
+
// parts = ArraySeq("SELECT * FROM users"),
|
|
171
|
+
// params = Vector()
|
|
172
|
+
// )
|
|
173
|
+
val where = sql" WHERE status = $status"
|
|
174
|
+
// where: Frag = Frag(
|
|
175
|
+
// parts = ArraySeq(" WHERE status = ", ""),
|
|
176
|
+
// params = Vector(DbString("active"))
|
|
177
|
+
// )
|
|
178
|
+
val order = Frag.literal(" ORDER BY name")
|
|
179
|
+
// order: Frag = Frag(parts = Vector(" ORDER BY name"), params = Vector())
|
|
180
|
+
val full = Frag.sequence(base, where, order)
|
|
181
|
+
// full: Frag = Frag(
|
|
182
|
+
// parts = Vector("SELECT * FROM users WHERE status = ", " ORDER BY name"),
|
|
183
|
+
// params = Vector(DbString("active"))
|
|
184
|
+
// )
|
|
185
|
+
full.sql(SqlDialect.SQLite)
|
|
186
|
+
// res9: String = "SELECT * FROM users WHERE status = ? ORDER BY name"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Execution Methods
|
|
190
|
+
|
|
191
|
+
All execution methods require an implicit `DbCon` (provided by `Transactor#connect` or `Transactor#transact`). They render the fragment to SQL, bind parameters, execute, and log the operation.
|
|
192
|
+
|
|
193
|
+
**`query[A]`** — Execute SELECT and return all rows:
|
|
194
|
+
|
|
195
|
+
```scala
|
|
196
|
+
import zio.blocks.sql._
|
|
197
|
+
import zio.blocks.schema.Schema
|
|
198
|
+
|
|
199
|
+
case class User(id: Int, name: String)
|
|
200
|
+
object User { implicit val schema: Schema[User] = Schema.derived }
|
|
201
|
+
|
|
202
|
+
given DbCon = ???
|
|
203
|
+
|
|
204
|
+
val users: List[User] = sql"SELECT id, name FROM users".query[User]
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**`queryOne[A]`** — Execute SELECT and return at most one row:
|
|
208
|
+
|
|
209
|
+
```scala
|
|
210
|
+
import zio.blocks.sql._
|
|
211
|
+
import zio.blocks.schema.Schema
|
|
212
|
+
import zio.blocks.maybe.Maybe
|
|
213
|
+
|
|
214
|
+
case class User(id: Int, name: String)
|
|
215
|
+
object User { implicit val schema: Schema[User] = Schema.derived }
|
|
216
|
+
|
|
217
|
+
given DbCon = ???
|
|
218
|
+
|
|
219
|
+
val user: Maybe[User] = sql"SELECT id, name FROM users WHERE id = ${1}".queryOne[User]
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**`queryLimit[A](n)`** — Execute SELECT and return up to n rows (fetched from Scala side):
|
|
223
|
+
|
|
224
|
+
```scala
|
|
225
|
+
import zio.blocks.sql._
|
|
226
|
+
import zio.blocks.schema.Schema
|
|
227
|
+
|
|
228
|
+
case class User(id: Int, name: String)
|
|
229
|
+
object User { implicit val schema: Schema[User] = Schema.derived }
|
|
230
|
+
|
|
231
|
+
given DbCon = ???
|
|
232
|
+
|
|
233
|
+
val page: List[User] = sql"SELECT id, name FROM users ORDER BY name".queryLimit[User](10)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**`update`** — Execute INSERT, UPDATE, or DELETE and return affected row count:
|
|
237
|
+
|
|
238
|
+
```scala
|
|
239
|
+
import zio.blocks.sql._
|
|
240
|
+
|
|
241
|
+
given DbCon = ???
|
|
242
|
+
|
|
243
|
+
val deleted: Int = sql"DELETE FROM users WHERE inactive = ${true}".update
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**`updateReturningKeys[A]`** — Execute INSERT and return auto-generated primary key(s):
|
|
247
|
+
|
|
248
|
+
```scala
|
|
249
|
+
import zio.blocks.sql._
|
|
250
|
+
|
|
251
|
+
given DbCon = ???
|
|
252
|
+
|
|
253
|
+
val keys: List[Long] = sql"INSERT INTO users (name) VALUES (${"Alice"})".updateReturningKeys[Long]
|
|
254
|
+
```
|