Add runnable DB serialization example

This commit is contained in:
Sergey Chernov 2026-04-25 16:23:08 +03:00
parent 92e9325f40
commit 66b8806b11
2 changed files with 99 additions and 15 deletions

View File

@ -1,4 +1,4 @@
### lyng.io.db — SQL database access for Lyng scripts # lyng.io.db — SQL database access for Lyng scripts
This module provides the portable SQL database contract for Lyng. The current shipped providers are SQLite via `lyng.io.db.sqlite` and a JVM-only JDBC bridge via `lyng.io.db.jdbc`. This module provides the portable SQL database contract for Lyng. The current shipped providers are SQLite via `lyng.io.db.sqlite` and a JVM-only JDBC bridge via `lyng.io.db.jdbc`.
@ -6,7 +6,7 @@ This module provides the portable SQL database contract for Lyng. The current sh
--- ---
#### Install the module into a Lyng session ## Install the module into a Lyng session
For SQLite-backed database access, install both the generic DB module and the SQLite provider: For SQLite-backed database access, install both the generic DB module and the SQLite provider:
@ -54,7 +54,7 @@ suspend fun bootstrapJdbc() {
--- ---
#### Using from Lyng scripts ## Using from Lyng scripts
Typed SQLite open helper: Typed SQLite open helper:
@ -188,13 +188,41 @@ assertThrows(RollbackException) {
--- ---
#### Portable API ## Runnable serialization sample
##### `Database` A complete runnable example is in [examples/sqlite_serialization.lyng](/home/sergeych/dev/lyng/examples/sqlite_serialization.lyng).
It uses:
- `@DbJson`
- `@DbLynon`
- `@DbExcept`
- `@cols(...)`, `@vals(...)`, `@set(...)`
- `decodeAs<T>()`
The current direct read form that works under `jlyng` is:
```lyng
tx.select("select * from item where id = ?", 1).decodeAs<Item>().first
```
If we want a shorter form such as:
```lyng
tx.selectAllAs<Item>("item where id = ?", 1).first
```
it should be added as a built-in `SqlTransaction` API. A pure Lyng generic wrapper around `decodeAs<T>()` does not currently preserve `T` reliably enough under `jlyng`.
---
## Portable API
### `Database`
- `transaction(block)` — opens a transaction, commits on normal exit, rolls back on uncaught failure. - `transaction(block)` — opens a transaction, commits on normal exit, rolls back on uncaught failure.
##### `SqlTransaction` ### `SqlTransaction`
- `select(clause, params...)` — execute a statement whose primary result is a row set. - `select(clause, params...)` — execute a statement whose primary result is a row set.
- `execute(clause, params...)` — execute a side-effect statement and return `ExecutionResult`. - `execute(clause, params...)` — execute a side-effect statement and return `ExecutionResult`.
@ -221,7 +249,7 @@ tx.execute("update item set @set(?1) where id = ?2", item, item.id)
When a clause uses any of these macros, non-expanded scalar parameters in the same SQL string must use explicit indexed placeholders such as `?2`, `?3`, and so on. When a clause uses any of these macros, non-expanded scalar parameters in the same SQL string must use explicit indexed placeholders such as `?2`, `?3`, and so on.
##### `ResultSet` ### `ResultSet`
- `columns` — positional `SqlColumn` metadata, available before iteration. - `columns` — positional `SqlColumn` metadata, available before iteration.
- `size()` — result row count. - `size()` — result row count.
@ -230,7 +258,7 @@ When a clause uses any of these macros, non-expanded scalar parameters in the sa
- `toList()` — materialize detached `SqlRow` snapshots that may be used after the transaction ends. - `toList()` — materialize detached `SqlRow` snapshots that may be used after the transaction ends.
- `decodeAs<T>()` — transaction-scoped iterable view that decodes each row into `T`. - `decodeAs<T>()` — transaction-scoped iterable view that decodes each row into `T`.
##### `SqlRow` ### `SqlRow`
- `row[index]` — zero-based positional access. - `row[index]` — zero-based positional access.
- `row["columnName"]` — case-insensitive lookup by output column label. - `row["columnName"]` — case-insensitive lookup by output column label.
@ -238,7 +266,7 @@ When a clause uses any of these macros, non-expanded scalar parameters in the sa
Name-based access fails with `SqlUsageException` if the name is missing or ambiguous. Name-based access fails with `SqlUsageException` if the name is missing or ambiguous.
##### `DbFieldAdapter` ### `DbFieldAdapter`
Custom DB field projection hook used by `@DbDecodeWith(...)` and `@DbSerializeWith(...)`. Custom DB field projection hook used by `@DbDecodeWith(...)` and `@DbSerializeWith(...)`.
@ -251,7 +279,7 @@ Use `@DbSerializeWith(adapter)` on constructor parameters and class-body fields/
Annotation arguments are evaluated once when the declaration is created, and the resulting adapter instance is retained in declaration metadata. Annotation arguments are evaluated once when the declaration is created, and the resulting adapter instance is retained in declaration metadata.
##### `ExecutionResult` ### `ExecutionResult`
- `affectedRowsCount` - `affectedRowsCount`
- `getGeneratedKeys()` - `getGeneratedKeys()`
@ -260,7 +288,7 @@ Statements that return rows directly, such as `... returning ...`, should use `s
--- ---
#### Value mapping ## Value mapping
Portable bind values: Portable bind values:
@ -387,7 +415,7 @@ For temporal types, see [time functions](time.md).
--- ---
#### SQLite provider ## SQLite provider
`lyng.io.db.sqlite` currently provides the first concrete backend. `lyng.io.db.sqlite` currently provides the first concrete backend.
@ -431,7 +459,7 @@ Open-time validation failures:
- malformed URL or bad option shape -> `IllegalArgumentException` - malformed URL or bad option shape -> `IllegalArgumentException`
- runtime open failure -> `DatabaseException` - runtime open failure -> `DatabaseException`
#### JDBC provider ## JDBC provider
`lyng.io.db.jdbc` is currently implemented on the JVM target only. The `lyngio-jvm` artifact bundles and explicitly loads these JDBC drivers: `lyng.io.db.jdbc` is currently implemented on the JVM target only. The `lyngio-jvm` artifact bundles and explicitly loads these JDBC drivers:
@ -503,7 +531,7 @@ PostgreSQL-specific notes:
--- ---
#### Lifetime rules ## Lifetime rules
`ResultSet` is valid only while its owning transaction is active. `ResultSet` is valid only while its owning transaction is active.
@ -528,7 +556,7 @@ The same rule applies to generated keys from `ExecutionResult.getGeneratedKeys()
--- ---
#### Platform support ## Platform support
- `lyng.io.db` — generic contract, available when host code installs it - `lyng.io.db` — generic contract, available when host code installs it
- `lyng.io.db.sqlite` — implemented on JVM and Linux Native in the current release tree - `lyng.io.db.sqlite` — implemented on JVM and Linux Native in the current release tree

View File

@ -0,0 +1,56 @@
import lyng.io.db.sqlite
println("SQLite serialization demo: write-side projection and decodeAs<T>()")
class Payload(name: String, count: Int)
class Item(
id: Int,
title: String,
@DbJson meta: Payload,
@DbLynon state: Payload
) {
var note: String = ""
@DbExcept var cache: String = ""
}
val restored = openSqlite(":memory:").transaction { tx ->
tx.execute(
"create table item(id integer not null, title text not null, meta text not null, state blob not null, note text not null)"
)
val item = Item(1, "first", Payload("json", 10), Payload("bin", 20))
item.note = "created"
item.cache = "not stored"
tx.execute("insert into item(@cols(?1)) values(@vals(?1))", item)
item.title = "second"
item.meta = Payload("json2", 11)
item.state = Payload("bin2", 21)
item.note = "updated"
tx.execute(
"update item set @set(?1 except: \"id\") where id = ?2",
item,
item.id
)
val restored = tx.select("select * from item where id = ?", 1).decodeAs<Item>().first
assertEquals("second", restored.title)
assertEquals("json2", restored.meta.name)
assertEquals(11, restored.meta.count)
assertEquals("bin2", restored.state.name)
assertEquals(21, restored.state.count)
assertEquals("updated", restored.note)
restored
} as Item
println("Restored item:")
println(" id=" + restored.id)
println(" title=" + restored.title)
println(" meta=" + restored.meta.name + "/" + restored.meta.count)
println(" state=" + restored.state.name + "/" + restored.state.count)
println(" note=" + restored.note)
println("OK")