Add Lyng DB contract and SQLite provider skeleton
This commit is contained in:
1 parent
b42ceec686
commit
55ba6113e7
21 files changed
+3860
No files matched your search
@@ -0,0 +1,209 @@
|
||||
package lyng.io.db
|
||||
|
||||
/*
|
||||
Portable value categories exposed by the Lyng SQL layer and used in
|
||||
`SqlColumn`.
|
||||
*/
|
||||
enum SqlType {
|
||||
Binary, String, Int, Double, Decimal,
|
||||
Bool, Instant, Date, DateTime
|
||||
}
|
||||
|
||||
extern class SqlColumn {
|
||||
val name: String
|
||||
val sqlType: SqlType
|
||||
val nullable: Bool
|
||||
/*
|
||||
Original database type name as reported by the backend, such as
|
||||
VARCHAR, TEXT, INT8, TIMESTAMPTZ, or BYTEA.
|
||||
*/
|
||||
val nativeType: String
|
||||
}
|
||||
|
||||
extern class SqlRow {
|
||||
/* Number of columns in the row */
|
||||
val size: Int
|
||||
val values: List<Object?>
|
||||
|
||||
/*
|
||||
Return the already converted Lyng value for a column addressed by
|
||||
index or output column label. SQL NULL is returned as null.
|
||||
|
||||
Name lookup uses result-column labels. If several columns share the same
|
||||
label, name-based access is ambiguous and should fail. Missing column
|
||||
names and invalid indexes should also fail.
|
||||
*/
|
||||
override fun getAt(indexOrName: String | Int): Object?
|
||||
}
|
||||
|
||||
/*
|
||||
A result set is valid only while its owning transaction is active.
|
||||
|
||||
Implementations may stream rows or buffer them internally, but:
|
||||
- rows must be exposed through normal iteration
|
||||
- iteration to the end or canceled iteration should close the underlying
|
||||
resources automatically
|
||||
- using the result set after its transaction ends is invalid
|
||||
- rows obtained from the result set are also invalid after the owning
|
||||
transaction ends, even if the implementation had already buffered them
|
||||
|
||||
If user code wants row data to survive independently, it should copy the
|
||||
values it needs into ordinary Lyng objects while the transaction is active.
|
||||
*/
|
||||
extern class ResultSet : Iterable<SqlRow> {
|
||||
/*
|
||||
Column metadata for the result rows, in positional order.
|
||||
*/
|
||||
val columns: List<SqlColumn>
|
||||
|
||||
/*
|
||||
Number of rows if the implementation can determine it. Implementations
|
||||
may need to consume or buffer the whole result in order to answer, but
|
||||
this must not change visible later iteration behavior.
|
||||
*/
|
||||
fun size(): Int
|
||||
|
||||
/*
|
||||
Fast emptiness check when the implementation can provide it without
|
||||
consuming the result. Implementations may still peek or buffer
|
||||
internally, but this must not change visible later iteration behavior.
|
||||
*/
|
||||
override fun isEmpty(): Bool
|
||||
}
|
||||
|
||||
extern class ExecutionResult {
|
||||
val affectedRowsCount: Int
|
||||
|
||||
/*
|
||||
Return implementation-supported auto-generated values produced by
|
||||
`execute`. This is intentionally stricter than arbitrary SQL result
|
||||
sets: statements such as INSERT/UPDATE/DELETE ... RETURNING should be
|
||||
executed with `select`, not exposed here.
|
||||
|
||||
The returned result set has the same transaction-scoped lifetime as any
|
||||
other result set.
|
||||
|
||||
If the statement produced no generated values, the returned result set
|
||||
is empty.
|
||||
*/
|
||||
fun getGeneratedKeys(): ResultSet
|
||||
}
|
||||
|
||||
extern class DatabaseException: Exception
|
||||
extern class SqlExecutionException: DatabaseException
|
||||
extern class SqlConstraintException: SqlExecutionException
|
||||
extern class SqlUsageException: DatabaseException
|
||||
|
||||
/*
|
||||
Special exception to be thrown from `SqlTransaction.transaction` when an
|
||||
intentional rollback is requested without treating it as a backend failure.
|
||||
|
||||
It causes rollback and is propagated to the caller, but should not be
|
||||
treated as a backend/driver failure.
|
||||
|
||||
If rollback itself fails, that rollback failure becomes the primary backend
|
||||
error instead.
|
||||
*/
|
||||
extern class RollbackException: Exception
|
||||
|
||||
/*
|
||||
Transaction represents a database transaction.
|
||||
|
||||
Important: a transaction has no explicit commit; instead it commits when
|
||||
leaving the transaction block normally.
|
||||
|
||||
If the transaction block throws any exception not caught inside the calling
|
||||
code, it will be rolled back.
|
||||
*/
|
||||
extern class SqlTransaction {
|
||||
/*
|
||||
Execute a SQL statement that returns rows. This includes plain SELECT
|
||||
queries and database-specific DML statements with row-returning clauses
|
||||
such as RETURNING or OUTPUT.
|
||||
|
||||
Portable SQL uses positional `?` placeholders only.
|
||||
|
||||
Portable bindable values are:
|
||||
- null
|
||||
- Bool
|
||||
- Int, Double, Decimal
|
||||
- String
|
||||
- Buffer
|
||||
- Date, DateTime, Instant
|
||||
|
||||
Unsupported parameter values should fail with `SqlUsageException`.
|
||||
*/
|
||||
fun select(clause: String, params...): ResultSet
|
||||
|
||||
/*
|
||||
Execute a SQL statement for side effects. Use `select` for any statement
|
||||
whose primary result is a row set.
|
||||
|
||||
Parameters follow the same binding rules as `select`.
|
||||
*/
|
||||
fun execute(clause: String, params...): ExecutionResult
|
||||
|
||||
/*
|
||||
Create a nested transaction with real nested semantics, typically using
|
||||
database savepoints.
|
||||
|
||||
If the backend cannot provide real nested transaction semantics, this
|
||||
call should fail with `SqlUsageException` rather than flattening into
|
||||
the outer transaction.
|
||||
|
||||
Failure inside the nested transaction rolls back only the nested scope;
|
||||
the outer transaction remains active unless the exception is allowed to
|
||||
propagate further.
|
||||
*/
|
||||
fun transaction<T>(block: (SqlTransaction) -> T): T
|
||||
}
|
||||
|
||||
extern class Database {
|
||||
/*
|
||||
Open a transaction. Any pooling, physical connection lifecycle, and
|
||||
implementation-specific configuration are owned by the database
|
||||
implementation and hidden from the user.
|
||||
|
||||
The transaction commits when the block finishes normally,
|
||||
and rolls back if the block exits with an uncaught exception.
|
||||
|
||||
Failure precedence is:
|
||||
- user exception + successful rollback -> original exception escapes
|
||||
- user exception + rollback failure -> original exception stays primary
|
||||
- RollbackException + rollback failure -> rollback failure is primary
|
||||
- commit failure after normal completion -> commit failure is primary
|
||||
*/
|
||||
fun transaction<T>(block: (SqlTransaction) -> T): T
|
||||
}
|
||||
|
||||
/*
|
||||
Register a database provider for a URL scheme.
|
||||
|
||||
Provider modules should call this during module initialization when first
|
||||
imported. Scheme matching is case-insensitive and normalized to lowercase.
|
||||
|
||||
Registering the same scheme more than once should fail.
|
||||
*/
|
||||
extern fun registerDatabaseProvider(
|
||||
scheme: String,
|
||||
opener: (String, Map<String, Object?>) -> Database
|
||||
)
|
||||
|
||||
/*
|
||||
The mandatory generic entry point for all providers. It opens a database
|
||||
handle from a provider-specific connection URL plus extra parameters.
|
||||
|
||||
Providers may expose additional typed constructors such as `openSqlite(...)`
|
||||
or `openPostgres(...)`, but `openDatabase(...)` should remain available for
|
||||
configuration-driven usage.
|
||||
|
||||
It should throw IllegalArgumentException for malformed connection URLs or
|
||||
invalid extra parameter shapes detected before opening the backend.
|
||||
Runtime opening failures such as authentication, connectivity, or provider
|
||||
initialization errors should be reported as DatabaseException.
|
||||
|
||||
The matching provider must already be registered, normally because its
|
||||
module was imported and executed. Unknown schemes or missing providers
|
||||
should fail with DatabaseException.
|
||||
*/
|
||||
extern fun openDatabase(connectionUrl: String, extraParams: Map<String, Object?>): Database
|
||||
@@ -0,0 +1,24 @@
|
||||
package lyng.io.db.sqlite
|
||||
|
||||
import lyng.io.db
|
||||
|
||||
/*
|
||||
SQLite provider for `lyng.io.db`.
|
||||
|
||||
Importing this module registers the `sqlite:` URL scheme for
|
||||
`openDatabase(...)`.
|
||||
|
||||
SQLite provider defaults:
|
||||
- `Bool` is written as `0` / `1`
|
||||
- `Decimal` is written as canonical text
|
||||
- `Date` is written as `YYYY-MM-DD`
|
||||
- `DateTime` is written as an ISO local timestamp without timezone
|
||||
- `Instant` is written as an ISO UTC timestamp with explicit timezone marker
|
||||
*/
|
||||
extern fun openSqlite(
|
||||
path: String,
|
||||
readOnly: Bool = false,
|
||||
createIfMissing: Bool = true,
|
||||
foreignKeys: Bool = true,
|
||||
busyTimeoutMillis: Int = 5000
|
||||
): Database
|
||||
Reference in new issue
Block a user