211 lines
7.3 KiB
Plaintext

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 stay usable after the owning
transaction ends once they have been materialized
Calling `toList()` while the transaction is active is the normal way to
detach rows for use after the transaction block returns.
*/
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. Call `toList()` if generated-key rows must outlive the
transaction.
*/
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