diff --git a/docs/lyng.io.db.md b/docs/lyng.io.db.md index 12def73..10bd8f1 100644 --- a/docs/lyng.io.db.md +++ b/docs/lyng.io.db.md @@ -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`. @@ -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: @@ -54,7 +54,7 @@ suspend fun bootstrapJdbc() { --- -#### Using from Lyng scripts +## Using from Lyng scripts 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()` + +The current direct read form that works under `jlyng` is: + +```lyng +tx.select("select * from item where id = ?", 1).decodeAs().first +``` + +If we want a shorter form such as: + +```lyng +tx.selectAllAs("item where id = ?", 1).first +``` + +it should be added as a built-in `SqlTransaction` API. A pure Lyng generic wrapper around `decodeAs()` 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. -##### `SqlTransaction` +### `SqlTransaction` - `select(clause, params...)` — execute a statement whose primary result is a row set. - `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. -##### `ResultSet` +### `ResultSet` - `columns` — positional `SqlColumn` metadata, available before iteration. - `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. - `decodeAs()` — transaction-scoped iterable view that decodes each row into `T`. -##### `SqlRow` +### `SqlRow` - `row[index]` — zero-based positional access. - `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. -##### `DbFieldAdapter` +### `DbFieldAdapter` 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. -##### `ExecutionResult` +### `ExecutionResult` - `affectedRowsCount` - `getGeneratedKeys()` @@ -260,7 +288,7 @@ Statements that return rows directly, such as `... returning ...`, should use `s --- -#### Value mapping +## Value mapping 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. @@ -431,7 +459,7 @@ Open-time validation failures: - malformed URL or bad option shape -> `IllegalArgumentException` - 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: @@ -503,7 +531,7 @@ PostgreSQL-specific notes: --- -#### Lifetime rules +## Lifetime rules `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.sqlite` — implemented on JVM and Linux Native in the current release tree diff --git a/examples/sqlite_serialization.lyng b/examples/sqlite_serialization.lyng new file mode 100644 index 0000000..de9e47c --- /dev/null +++ b/examples/sqlite_serialization.lyng @@ -0,0 +1,56 @@ +import lyng.io.db.sqlite + +println("SQLite serialization demo: write-side projection and decodeAs()") + +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().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")