5.9 KiB
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:
lyngiois 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:
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:
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:
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:
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:
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 returnExecutionResult.transaction(block)— nested transaction with real savepoint semantics.
ResultSet
columns— positionalSqlColumnmetadata, 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
affectedRowsCountgetGeneratedKeys()
Statements that return rows directly, such as ... returning ..., should use select(...), not execute(...).
Value mapping
Portable bind values:
nullBoolInt,Double,DecimalStringBufferDate,DateTime,Instant
Unsupported parameter values fail with SqlUsageException.
Portable result metadata categories:
BinaryStringIntDoubleDecimalBoolDateDateTimeInstant
For temporal types, see time functions.
SQLite provider
lyng.io.db.sqlite currently provides the first concrete backend.
Typed helper:
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.dbsqlite:/absolute/path.db
Supported openDatabase(..., extraParams) keys for SQLite:
readOnly: BoolcreateIfMissing: BoolforeignKeys: BoolbusyTimeoutMillis: Int
SQLite write/read policy in v1:
Boolwrites as0/1Decimalwrites as canonical textDatewrites asYYYY-MM-DDDateTimewrites as ISO local timestamp text without timezoneInstantwrites as ISO UTC timestamp text with explicit timezone markerTIME*values stayStringTIMESTAMP/DATETIMEreject 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
ResultSetorSqlRowobjects 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 itlyng.io.db.sqlite— implemented on JVM and Linux Native in the current release tree
For the broader I/O overview, see lyngio overview.