Connection & transaction wrappers

This commit is contained in:
Andrew Golovashevich 2024-11-21 02:53:01 +03:00
parent fd5f02e7a9
commit b3a6b7f52c
10 changed files with 516 additions and 1 deletions

View File

@ -0,0 +1,6 @@
package ru.landgrafhomyak.serdha.api.v0
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.BINARY)
@RequiresOptIn
public annotation class LowLevelApi

View File

@ -3,7 +3,7 @@ package ru.landgrafhomyak.serdha.api.v0.ddl
import ru.landgrafhomyak.serdha.api.v0.Expression import ru.landgrafhomyak.serdha.api.v0.Expression
public interface TableUpdater<TableOldUserWrapper : Any, TableNewUserWrapper : Any> : TableCreator<TableNewUserWrapper> { public interface TableUpdater<TableNewUserWrapper : Any, TableOldUserWrapper : Any> : TableCreator<TableNewUserWrapper> {
public fun <RuntimeType, DatabaseType : ColumnType<RuntimeType & Any>> keepColumn(c: Column<RuntimeType, DatabaseType, TableOldUserWrapper>): Column<RuntimeType, DatabaseType, TableNewUserWrapper> public fun <RuntimeType, DatabaseType : ColumnType<RuntimeType & Any>> keepColumn(c: Column<RuntimeType, DatabaseType, TableOldUserWrapper>): Column<RuntimeType, DatabaseType, TableNewUserWrapper>
public fun <R, D : ColumnType<R & Any>> renameColumn(c: Column<R, D, TableOldUserWrapper>, newName: String): Column<R, D, TableNewUserWrapper> public fun <R, D : ColumnType<R & Any>> renameColumn(c: Column<R, D, TableOldUserWrapper>, newName: String): Column<R, D, TableNewUserWrapper>
public fun <R, D : ColumnType<R & Any>> mapColumn(c: Column<R, D, TableNewUserWrapper>, newValue: Expression<R, D, TableNewUserWrapper>, where: Expression<Boolean, ColumnType.BOOLEAN, TableNewUserWrapper>?) public fun <R, D : ColumnType<R & Any>> mapColumn(c: Column<R, D, TableNewUserWrapper>, newValue: Expression<R, D, TableNewUserWrapper>, where: Expression<Boolean, ColumnType.BOOLEAN, TableNewUserWrapper>?)

View File

@ -0,0 +1,13 @@
package ru.landgrafhomyak.serdha.api.v0.runtime
import ru.landgrafhomyak.serdha.api.v0.LowLevelApi
public interface Database {
@Suppress("FunctionName")
@LowLevelApi
public suspend fun _startTransaction(): Transaction
@Suppress("FunctionName")
@LowLevelApi
public suspend fun _close()
}

View File

@ -0,0 +1,18 @@
package ru.landgrafhomyak.serdha.api.v0.runtime
import ru.landgrafhomyak.serdha.api.v0.ddl.Column
public interface ParametersSetter<QueryUserWrapper : Any> {
public operator fun <RuntimeType> set(c: Column<RuntimeType, *, QueryUserWrapper>, value: RuntimeType)
public operator fun set(c: Column<Byte, *, QueryUserWrapper>, value: Byte): Unit = this.set<Byte>(c, value)
public operator fun set(c: Column<UByte, *, QueryUserWrapper>, value: UByte): Unit = this.set<UByte>(c, value)
public operator fun set(c: Column<Short, *, QueryUserWrapper>, value: Short): Unit = this.set<Short>(c, value)
public operator fun set(c: Column<UShort, *, QueryUserWrapper>, value: UShort): Unit = this.set<UShort>(c, value)
public operator fun set(c: Column<Int, *, QueryUserWrapper>, value: Int): Unit = this.set<Int>(c, value)
public operator fun set(c: Column<UInt, *, QueryUserWrapper>, value: UInt): Unit = this.set<UInt>(c, value)
public operator fun set(c: Column<Long, *, QueryUserWrapper>, value: Long): Unit = this.set<Long>(c, value)
public operator fun set(c: Column<ULong, *, QueryUserWrapper>, value: ULong): Unit = this.set<ULong>(c, value)
public operator fun set(c: Column<Char, *, QueryUserWrapper>, value: Char): Unit = this.set<Char>(c, value)
public operator fun set(c: Column<Boolean, *, QueryUserWrapper>, value: Boolean): Unit = this.set<Boolean>(c, value)
}

View File

@ -0,0 +1,18 @@
package ru.landgrafhomyak.serdha.api.v0.runtime
import ru.landgrafhomyak.serdha.api.v0.ddl.Column
public interface ResultSet<QueryUserWrapper : Any> {
public operator fun <RuntimeType> get(c: Column<RuntimeType, *, QueryUserWrapper>): RuntimeType
public operator fun get(c: Column<Byte, *, QueryUserWrapper>): Byte = this.get<Byte>(c)
public operator fun get(c: Column<UByte, *, QueryUserWrapper>): UByte = this.get<UByte>(c)
public operator fun get(c: Column<Short, *, QueryUserWrapper>): Short = this.get<Short>(c)
public operator fun get(c: Column<UShort, *, QueryUserWrapper>): UShort = this.get<UShort>(c)
public operator fun get(c: Column<Int, *, QueryUserWrapper>): Int = this.get<Int>(c)
public operator fun get(c: Column<UInt, *, QueryUserWrapper>): UInt = this.get<UInt>(c)
public operator fun get(c: Column<Long, *, QueryUserWrapper>): Long = this.get<Long>(c)
public operator fun get(c: Column<ULong, *, QueryUserWrapper>): ULong = this.get<ULong>(c)
public operator fun get(c: Column<Char, *, QueryUserWrapper>): Char = this.get<Char>(c)
public operator fun get(c: Column<Boolean, *, QueryUserWrapper>): Boolean = this.get<Boolean>(c)
}

View File

@ -0,0 +1,19 @@
package ru.landgrafhomyak.serdha.api.v0.runtime
import ru.landgrafhomyak.serdha.api.v0.ddl.Table
import ru.landgrafhomyak.serdha.api.v0.ddl.TableCreator
import ru.landgrafhomyak.serdha.api.v0.ddl.TableUpdater
import ru.landgrafhomyak.serdha.api.v0.dml.Insert
import ru.landgrafhomyak.serdha.api.v0.dml.InsertCreator
import ru.landgrafhomyak.serdha.api.v0.dml.Select
import ru.landgrafhomyak.serdha.api.v0.dml.SelectCreator
import ru.landgrafhomyak.serdha.api.v0.dml.Update
import ru.landgrafhomyak.serdha.api.v0.dml.UpdateCreator
public interface Synchronizer {
public fun <TableUserWrapper : Any> createTable(initializer: (TableCreator<TableUserWrapper>) -> TableUserWrapper): Table<TableUserWrapper, Nothing>
public fun <TableOldUserWrapper : Any, TableNewUserWrapper : Any> updateTable(oldTable: Table<TableOldUserWrapper, *>, initializer: (TableUpdater<TableNewUserWrapper, TableOldUserWrapper>) -> TableNewUserWrapper): Table<TableNewUserWrapper, TableOldUserWrapper>
public fun <QueryUserWrapper : Any> createSelect(initializer: (SelectCreator<QueryUserWrapper>) -> QueryUserWrapper): Select<QueryUserWrapper>
public fun <TableUserWrapper : Any, QueryUserWrapper : Any> createInsert(table: Table<TableUserWrapper, *>, initializer: (InsertCreator<TableUserWrapper, QueryUserWrapper>) -> QueryUserWrapper): Insert<QueryUserWrapper>
public fun <TableUserWrapper : Any, QueryUserWrapper : Any> createUpdate(table: Table<TableUserWrapper, *>, initializer: (UpdateCreator<TableUserWrapper, QueryUserWrapper>) -> QueryUserWrapper): Update<QueryUserWrapper>
}

View File

@ -0,0 +1,28 @@
package ru.landgrafhomyak.serdha.api.v0.runtime
import ru.landgrafhomyak.serdha.api.v0.LowLevelApi
import ru.landgrafhomyak.serdha.api.v0.dml.Insert
import ru.landgrafhomyak.serdha.api.v0.dml.Select
import ru.landgrafhomyak.serdha.api.v0.dml.Update
public interface Transaction {
@Suppress("FunctionName")
@LowLevelApi
public fun <QueryUserWrapper : Any> _select(compiledQuery: Select<QueryUserWrapper>): _ParametersSetter<QueryUserWrapper, _ResultSet<QueryUserWrapper>>
@Suppress("FunctionName")
@LowLevelApi
public fun <QueryUserWrapper : Any> _insert(compiledQuery: Insert<QueryUserWrapper>): _ParametersSetter<QueryUserWrapper, _ResultSet<QueryUserWrapper>?>
@Suppress("FunctionName")
@LowLevelApi
public fun <QueryUserWrapper : Any> _update(compiledQuery: Update<QueryUserWrapper>): _ParametersSetter<QueryUserWrapper, _ResultSet<QueryUserWrapper>?>
public suspend fun rollback()
public suspend fun commit()
@Suppress("FunctionName")
@LowLevelApi
public suspend fun _assertTransactionFinishedAndReleaseResources()
}

View File

@ -0,0 +1,13 @@
package ru.landgrafhomyak.serdha.api.v0.runtime
import ru.landgrafhomyak.serdha.api.v0.LowLevelApi
@Suppress("ClassName", "FunctionName")
@LowLevelApi
public interface _ParametersSetter<QueryUserWrapper : Any, out Result> : ParametersSetter<QueryUserWrapper> {
@LowLevelApi
public suspend fun _execute(): Result
@LowLevelApi
public suspend fun _abort()
}

View File

@ -0,0 +1,15 @@
package ru.landgrafhomyak.serdha.api.v0.runtime
import ru.landgrafhomyak.serdha.api.v0.LowLevelApi
@Suppress("ClassName")
@LowLevelApi
public interface _ResultSet<QueryUserWrapper : Any> : ResultSet<QueryUserWrapper> {
@Suppress("FunctionName")
@LowLevelApi
public suspend fun _next(): Boolean
@Suppress("FunctionName")
@LowLevelApi
public suspend fun _close()
}

View File

@ -0,0 +1,385 @@
@file:JvmName("TransactionMethodsKt")
package ru.landgrafhomyak.serdha.api.v0.runtime
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.jvm.JvmName
import ru.landgrafhomyak.serdha.api.v0.LowLevelApi
import ru.landgrafhomyak.serdha.api.v0.dml.Insert
import ru.landgrafhomyak.serdha.api.v0.dml.Select
import ru.landgrafhomyak.serdha.api.v0.dml.Update
@Suppress("FunctionName")
@PublishedApi
internal inline fun <R> _safeAutoClose(onAbort: () -> Unit = {}, onSuccess: () -> Unit = {}, action: () -> R): R {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
callsInPlace(onAbort, InvocationKind.AT_MOST_ONCE)
callsInPlace(onSuccess, InvocationKind.AT_MOST_ONCE)
}
val ret: R
try {
ret = action()
} catch (e1: Throwable) {
try {
onAbort()
} catch (e2: Throwable) {
e1.addSuppressed(e2)
}
throw e1
}
onSuccess()
return ret
}
@Suppress("FunctionName")
@PublishedApi
internal inline fun <R> _safeAutoClose(finally: () -> Unit, action: () -> R): R {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
callsInPlace(finally, InvocationKind.EXACTLY_ONCE)
}
return _safeAutoClose(finally, finally, action)
}
public class QueryWithoutReturnError(message: String, public val query: Any) : Error(message)
@Suppress("FunctionName")
@PublishedApi
@OptIn(LowLevelApi::class)
internal suspend inline fun <Q : Any> _wrapWithLambdas(
compiledQuery: Any,
queryGetter: () -> _ParametersSetter<Q, _ResultSet<Q>?>,
parameters: (ParametersSetter<Q>) -> Unit,
hasReturning: Boolean,
returning: (_ResultSet<Q>) -> Unit,
queryWithoutErrorMessage: String
) {
contract {
callsInPlace(queryGetter, InvocationKind.EXACTLY_ONCE)
callsInPlace(parameters, InvocationKind.EXACTLY_ONCE)
callsInPlace(returning, InvocationKind.AT_MOST_ONCE)
}
val paramsSetter = queryGetter()
_safeAutoClose(onAbort = { paramsSetter._abort() }) {
parameters(paramsSetter)
}
val rows = paramsSetter._execute()
if (rows == null) {
if (hasReturning)
throw QueryWithoutReturnError(queryWithoutErrorMessage, compiledQuery)
return
}
_safeAutoClose(finally = { rows._close() }) {
returning(rows)
}
}
public class ResultNotSingleError : Error()
@Suppress("FunctionName")
@PublishedApi
@OptIn(LowLevelApi::class)
internal suspend inline fun <Q : Any, R> _wrapWithLambdasSingleOrNull(
compiledQuery: Any,
queryGetter: () -> _ParametersSetter<Q, _ResultSet<Q>?>,
parameters: (ParametersSetter<Q>) -> Unit,
hasReturning: Boolean,
returning: (ResultSet<Q>) -> R,
queryWithoutErrorMessage: String
): R? {
contract {
callsInPlace(queryGetter, InvocationKind.EXACTLY_ONCE)
callsInPlace(parameters, InvocationKind.EXACTLY_ONCE)
callsInPlace(returning, InvocationKind.AT_MOST_ONCE)
}
_wrapWithLambdas(
compiledQuery, queryGetter, parameters, hasReturning,
{ rs ->
if (!rs._next())
return@_wrapWithLambdasSingleOrNull null
val ret = returning(rs)
if (rs._next())
throw ResultNotSingleError()
return@_wrapWithLambdasSingleOrNull ret
},
queryWithoutErrorMessage
)
throw QueryWithoutReturnError(queryWithoutErrorMessage, compiledQuery)
}
@Suppress("FunctionName")
@PublishedApi
@OptIn(LowLevelApi::class)
internal suspend inline fun <Q : Any> _wrapWithLambdasIterate(
compiledQuery: Any,
queryGetter: () -> _ParametersSetter<Q, _ResultSet<Q>?>,
parameters: (ParametersSetter<Q>) -> Unit,
hasReturning: Boolean,
returning: (ResultSet<Q>) -> Unit,
queryWithoutErrorMessage: String
) {
contract {
callsInPlace(queryGetter, InvocationKind.EXACTLY_ONCE)
callsInPlace(parameters, InvocationKind.EXACTLY_ONCE)
callsInPlace(returning, InvocationKind.UNKNOWN)
}
return _wrapWithLambdas(compiledQuery, queryGetter, parameters, hasReturning, { rs -> while (rs._next()) returning(rs) }, queryWithoutErrorMessage)
}
@Suppress("FunctionName")
@PublishedApi
@OptIn(LowLevelApi::class)
internal suspend inline fun <Q : Any, R> _wrapWithLambdasMap(
compiledQuery: Any,
queryGetter: () -> _ParametersSetter<Q, _ResultSet<Q>?>,
parameters: (ParametersSetter<Q>) -> Unit,
hasReturning: Boolean,
returning: (ResultSet<Q>) -> R,
queryWithoutErrorMessage: String
): List<R> {
contract {
callsInPlace(queryGetter, InvocationKind.EXACTLY_ONCE)
callsInPlace(parameters, InvocationKind.EXACTLY_ONCE)
callsInPlace(returning, InvocationKind.UNKNOWN)
}
_wrapWithLambdas(
compiledQuery, queryGetter, parameters, hasReturning,
{ rs ->
val out = ArrayList<R>()
while (rs._next())
out.add(returning(rs))
return@_wrapWithLambdasMap out
},
queryWithoutErrorMessage
)
throw QueryWithoutReturnError(queryWithoutErrorMessage, compiledQuery)
}
@OptIn(LowLevelApi::class)
public suspend inline fun <QueryUserWrapper : Any, R> Transaction.selectSingleOrNull(
compiledQuery: Select<QueryUserWrapper>,
parameters: (ParametersSetter<QueryUserWrapper>) -> Unit = {},
rowsConsumer: (ResultSet<QueryUserWrapper>) -> R
): R? {
contract {
callsInPlace(parameters, InvocationKind.EXACTLY_ONCE)
callsInPlace(rowsConsumer, InvocationKind.AT_MOST_ONCE)
}
return _wrapWithLambdasSingleOrNull(
compiledQuery = compiledQuery,
queryGetter = { this._select(compiledQuery) },
parameters = parameters,
hasReturning = true,
returning = rowsConsumer,
""
)
}
@OptIn(LowLevelApi::class)
public suspend inline fun <QueryUserWrapper : Any> Transaction.selectThenIterate(
compiledQuery: Select<QueryUserWrapper>,
parameters: (ParametersSetter<QueryUserWrapper>) -> Unit,
rowsConsumer: (ResultSet<QueryUserWrapper>) -> Unit
) {
contract {
callsInPlace(parameters, InvocationKind.EXACTLY_ONCE)
callsInPlace(rowsConsumer, InvocationKind.UNKNOWN)
}
_wrapWithLambdasIterate(
compiledQuery = compiledQuery,
queryGetter = { this._select(compiledQuery) },
parameters = parameters,
hasReturning = true,
returning = rowsConsumer,
""
)
}
@OptIn(LowLevelApi::class)
public suspend inline fun <QueryUserWrapper : Any, R> Transaction.selectThenMap(
compiledQuery: Select<QueryUserWrapper>,
parameters: (ParametersSetter<QueryUserWrapper>) -> Unit,
rowsConsumer: (ResultSet<QueryUserWrapper>) -> R
): List<R> {
contract {
callsInPlace(parameters, InvocationKind.EXACTLY_ONCE)
callsInPlace(rowsConsumer, InvocationKind.UNKNOWN)
}
return _wrapWithLambdasMap(
compiledQuery = compiledQuery,
queryGetter = { this._select(compiledQuery) },
parameters = parameters,
hasReturning = true,
returning = rowsConsumer,
""
)
}
@OptIn(LowLevelApi::class)
public suspend inline fun <QueryUserWrapper : Any> Transaction.insert(
compiledQuery: Insert<QueryUserWrapper>,
parameters: (ParametersSetter<QueryUserWrapper>) -> Unit = {},
) {
contract {
callsInPlace(parameters, InvocationKind.EXACTLY_ONCE)
}
_wrapWithLambdas(
compiledQuery = compiledQuery,
queryGetter = { this._insert(compiledQuery) },
parameters = parameters,
hasReturning = false,
returning = {},
""
)
}
@OptIn(LowLevelApi::class)
public suspend inline fun <QueryUserWrapper : Any, R> Transaction.insertReturningSingleOrNull(
compiledQuery: Insert<QueryUserWrapper>,
parameters: (ParametersSetter<QueryUserWrapper>) -> Unit = {},
rowsConsumer: (ResultSet<QueryUserWrapper>) -> R
): R? {
contract {
callsInPlace(parameters, InvocationKind.EXACTLY_ONCE)
callsInPlace(rowsConsumer, InvocationKind.AT_MOST_ONCE)
}
return _wrapWithLambdasSingleOrNull(
compiledQuery = compiledQuery,
queryGetter = { this._insert(compiledQuery) },
parameters = parameters,
hasReturning = true,
returning = rowsConsumer,
""
)
}
@OptIn(LowLevelApi::class)
public suspend inline fun <QueryUserWrapper : Any> Transaction.insertReturningIterate(
compiledQuery: Insert<QueryUserWrapper>,
parameters: (ParametersSetter<QueryUserWrapper>) -> Unit,
rowsConsumer: (ResultSet<QueryUserWrapper>) -> Unit
) {
contract {
callsInPlace(parameters, InvocationKind.EXACTLY_ONCE)
callsInPlace(rowsConsumer, InvocationKind.UNKNOWN)
}
_wrapWithLambdasIterate(
compiledQuery = compiledQuery,
queryGetter = { this._insert(compiledQuery) },
parameters = parameters,
hasReturning = true,
returning = rowsConsumer,
""
)
}
@OptIn(LowLevelApi::class)
public suspend inline fun <QueryUserWrapper : Any, R> Transaction.insertReturningMap(
compiledQuery: Insert<QueryUserWrapper>,
parameters: (ParametersSetter<QueryUserWrapper>) -> Unit,
rowsConsumer: (ResultSet<QueryUserWrapper>) -> R
): List<R> {
contract {
callsInPlace(parameters, InvocationKind.EXACTLY_ONCE)
callsInPlace(rowsConsumer, InvocationKind.UNKNOWN)
}
return _wrapWithLambdasMap(
compiledQuery = compiledQuery,
queryGetter = { this._insert(compiledQuery) },
parameters = parameters,
hasReturning = true,
returning = rowsConsumer,
""
)
}
@OptIn(LowLevelApi::class)
public suspend inline fun <QueryUserWrapper : Any> Transaction.update(
compiledQuery: Update<QueryUserWrapper>,
parameters: (ParametersSetter<QueryUserWrapper>) -> Unit = {},
) {
contract {
callsInPlace(parameters, InvocationKind.EXACTLY_ONCE)
}
_wrapWithLambdas(
compiledQuery = compiledQuery,
queryGetter = { this._update(compiledQuery) },
parameters = parameters,
hasReturning = false,
returning = {},
""
)
}
@OptIn(LowLevelApi::class)
public suspend inline fun <QueryUserWrapper : Any, R> Transaction.updateReturningSingleOrNull(
compiledQuery: Update<QueryUserWrapper>,
parameters: (ParametersSetter<QueryUserWrapper>) -> Unit = {},
rowsConsumer: (ResultSet<QueryUserWrapper>) -> R
): R? {
contract {
callsInPlace(parameters, InvocationKind.EXACTLY_ONCE)
callsInPlace(rowsConsumer, InvocationKind.AT_MOST_ONCE)
}
return _wrapWithLambdasSingleOrNull(
compiledQuery = compiledQuery,
queryGetter = { this._update(compiledQuery) },
parameters = parameters,
hasReturning = true,
returning = rowsConsumer,
""
)
}
@OptIn(LowLevelApi::class)
public suspend inline fun <QueryUserWrapper : Any> Transaction.updateReturningIterate(
compiledQuery: Update<QueryUserWrapper>,
parameters: (ParametersSetter<QueryUserWrapper>) -> Unit,
rowsConsumer: (ResultSet<QueryUserWrapper>) -> Unit
) {
contract {
callsInPlace(parameters, InvocationKind.EXACTLY_ONCE)
callsInPlace(rowsConsumer, InvocationKind.UNKNOWN)
}
_wrapWithLambdasIterate(
compiledQuery = compiledQuery,
queryGetter = { this._update(compiledQuery) },
parameters = parameters,
hasReturning = true,
returning = rowsConsumer,
""
)
}
@OptIn(LowLevelApi::class)
public suspend inline fun <QueryUserWrapper : Any, R> Transaction.updateReturningMap(
compiledQuery: Update<QueryUserWrapper>,
parameters: (ParametersSetter<QueryUserWrapper>) -> Unit,
rowsConsumer: (ResultSet<QueryUserWrapper>) -> R
): List<R> {
contract {
callsInPlace(parameters, InvocationKind.EXACTLY_ONCE)
callsInPlace(rowsConsumer, InvocationKind.UNKNOWN)
}
return _wrapWithLambdasMap(
compiledQuery = compiledQuery,
queryGetter = { this._update(compiledQuery) },
parameters = parameters,
hasReturning = true,
returning = rowsConsumer,
""
)
}