Add Date type and database release docs
This commit is contained in:
1 parent
14dc73db3e
commit
b3be908242
19 files changed
+928
-106
No files matched your search
@@ -0,0 +1,240 @@
|
||||
### lyng.io.db — SQL database access for Lyng scripts
|
||||
|
||||
This module provides the portable SQL database contract for Lyng. The first shipped provider is SQLite via `lyng.io.db.sqlite`.
|
||||
|
||||
> **Note:** `lyngio` is a separate library module. It must be explicitly added as a dependency to your host application and initialized in your Lyng scopes.
|
||||
|
||||
---
|
||||
|
||||
#### Install the module into a Lyng session
|
||||
|
||||
For SQLite-backed database access, install both the generic DB module and the SQLite provider:
|
||||
|
||||
```kotlin
|
||||
import net.sergeych.lyng.EvalSession
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.io.db.createDbModule
|
||||
import net.sergeych.lyng.io.db.sqlite.createSqliteModule
|
||||
|
||||
suspend fun bootstrapDb() {
|
||||
val session = EvalSession()
|
||||
val scope: Scope = session.getScope()
|
||||
createDbModule(scope)
|
||||
createSqliteModule(scope)
|
||||
session.eval("""
|
||||
import lyng.io.db
|
||||
import lyng.io.db.sqlite
|
||||
""".trimIndent())
|
||||
}
|
||||
```
|
||||
|
||||
`createSqliteModule(...)` also registers the `sqlite:` scheme for generic `openDatabase(...)`.
|
||||
|
||||
---
|
||||
|
||||
#### Using from Lyng scripts
|
||||
|
||||
Typed SQLite open helper:
|
||||
|
||||
```lyng
|
||||
import lyng.io.db.sqlite
|
||||
|
||||
val db = openSqlite(":memory:")
|
||||
|
||||
val userCount = db.transaction { tx ->
|
||||
tx.execute("create table user(id integer primary key autoincrement, name text not null)")
|
||||
tx.execute("insert into user(name) values(?)", "Ada")
|
||||
tx.execute("insert into user(name) values(?)", "Linus")
|
||||
tx.select("select count(*) as count from user").toList()[0]["count"]
|
||||
}
|
||||
|
||||
assertEquals(2, userCount)
|
||||
```
|
||||
|
||||
Generic provider-based open:
|
||||
|
||||
```lyng
|
||||
import lyng.io.db
|
||||
import lyng.io.db.sqlite
|
||||
|
||||
val db = openDatabase(
|
||||
"sqlite:./app.db",
|
||||
Map(
|
||||
"foreignKeys" => true,
|
||||
"busyTimeoutMillis" => 5000
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
Nested transactions use real savepoint semantics:
|
||||
|
||||
```lyng
|
||||
import lyng.io.db
|
||||
import lyng.io.db.sqlite
|
||||
|
||||
val db = openSqlite(":memory:")
|
||||
|
||||
db.transaction { tx ->
|
||||
tx.execute("create table item(id integer primary key autoincrement, name text not null)")
|
||||
tx.execute("insert into item(name) values(?)", "outer")
|
||||
|
||||
try {
|
||||
tx.transaction { inner ->
|
||||
inner.execute("insert into item(name) values(?)", "inner")
|
||||
throw IllegalStateException("rollback nested")
|
||||
}
|
||||
} catch (_: IllegalStateException) {
|
||||
}
|
||||
|
||||
assertEquals(1, tx.select("select count(*) as count from item").toList()[0]["count"])
|
||||
}
|
||||
```
|
||||
|
||||
Intentional rollback without treating it as a backend failure:
|
||||
|
||||
```lyng
|
||||
import lyng.io.db
|
||||
import lyng.io.db.sqlite
|
||||
|
||||
val db = openSqlite(":memory:")
|
||||
|
||||
assertThrows(RollbackException) {
|
||||
db.transaction { tx ->
|
||||
tx.execute("create table item(id integer primary key autoincrement, name text not null)")
|
||||
tx.execute("insert into item(name) values(?)", "temporary")
|
||||
throw RollbackException("stop here")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Portable API
|
||||
|
||||
##### `Database`
|
||||
|
||||
- `transaction(block)` — opens a transaction, commits on normal exit, rolls back on uncaught failure.
|
||||
|
||||
##### `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`.
|
||||
- `transaction(block)` — nested transaction with real savepoint semantics.
|
||||
|
||||
##### `ResultSet`
|
||||
|
||||
- `columns` — positional `SqlColumn` metadata, available before iteration.
|
||||
- `size()` — result row count.
|
||||
- `isEmpty()` — fast emptiness check where possible.
|
||||
- `iterator()` / `toList()` — normal row iteration.
|
||||
|
||||
##### `SqlRow`
|
||||
|
||||
- `row[index]` — zero-based positional access.
|
||||
- `row["columnName"]` — case-insensitive lookup by output column label.
|
||||
|
||||
Name-based access fails with `SqlUsageException` if the name is missing or ambiguous.
|
||||
|
||||
##### `ExecutionResult`
|
||||
|
||||
- `affectedRowsCount`
|
||||
- `getGeneratedKeys()`
|
||||
|
||||
Statements that return rows directly, such as `... returning ...`, should use `select(...)`, not `execute(...)`.
|
||||
|
||||
---
|
||||
|
||||
#### Value mapping
|
||||
|
||||
Portable bind values:
|
||||
|
||||
- `null`
|
||||
- `Bool`
|
||||
- `Int`, `Double`, `Decimal`
|
||||
- `String`
|
||||
- `Buffer`
|
||||
- `Date`, `DateTime`, `Instant`
|
||||
|
||||
Unsupported parameter values fail with `SqlUsageException`.
|
||||
|
||||
Portable result metadata categories:
|
||||
|
||||
- `Binary`
|
||||
- `String`
|
||||
- `Int`
|
||||
- `Double`
|
||||
- `Decimal`
|
||||
- `Bool`
|
||||
- `Date`
|
||||
- `DateTime`
|
||||
- `Instant`
|
||||
|
||||
For temporal types, see [time functions](time.md).
|
||||
|
||||
---
|
||||
|
||||
#### SQLite provider
|
||||
|
||||
`lyng.io.db.sqlite` currently provides the first concrete backend.
|
||||
|
||||
Typed helper:
|
||||
|
||||
```lyng
|
||||
openSqlite(
|
||||
path: String,
|
||||
readOnly: Bool = false,
|
||||
createIfMissing: Bool = true,
|
||||
foreignKeys: Bool = true,
|
||||
busyTimeoutMillis: Int = 5000
|
||||
): Database
|
||||
```
|
||||
|
||||
Accepted generic URL forms:
|
||||
|
||||
- `sqlite::memory:`
|
||||
- `sqlite:relative/path.db`
|
||||
- `sqlite:/absolute/path.db`
|
||||
|
||||
Supported `openDatabase(..., extraParams)` keys for SQLite:
|
||||
|
||||
- `readOnly: Bool`
|
||||
- `createIfMissing: Bool`
|
||||
- `foreignKeys: Bool`
|
||||
- `busyTimeoutMillis: Int`
|
||||
|
||||
SQLite write/read policy in v1:
|
||||
|
||||
- `Bool` writes as `0` / `1`
|
||||
- `Decimal` writes as canonical text
|
||||
- `Date` writes as `YYYY-MM-DD`
|
||||
- `DateTime` writes as ISO local timestamp text without timezone
|
||||
- `Instant` writes as ISO UTC timestamp text with explicit timezone marker
|
||||
- `TIME*` values stay `String`
|
||||
- `TIMESTAMP` / `DATETIME` reject timezone-bearing stored text
|
||||
|
||||
Open-time validation failures:
|
||||
|
||||
- malformed URL or bad option shape -> `IllegalArgumentException`
|
||||
- runtime open failure -> `DatabaseException`
|
||||
|
||||
---
|
||||
|
||||
#### Lifetime rules
|
||||
|
||||
Result sets and rows are valid only while their owning transaction is active.
|
||||
|
||||
This means:
|
||||
|
||||
- do not keep `ResultSet` or `SqlRow` objects after the transaction block returns
|
||||
- copy the values you need into ordinary Lyng objects inside the transaction
|
||||
|
||||
The same lifetime rule applies to generated keys returned by `ExecutionResult.getGeneratedKeys()`.
|
||||
|
||||
---
|
||||
|
||||
#### 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
|
||||
|
||||
For the broader I/O overview, see [lyngio overview](lyngio.md).
|
||||
Reference in new issue
Block a user