From b3a6b7f52c72318f9090ad3d98323f0c8cf32f2f Mon Sep 17 00:00:00 2001 From: Andrew Golovashevich Date: Thu, 21 Nov 2024 02:53:01 +0300 Subject: [PATCH] Connection & transaction wrappers --- .../serdha/api/v0/LowLevelApi.kt | 6 + .../serdha/api/v0/ddl/TableUpdater.kt | 2 +- .../serdha/api/v0/runtime/Database.kt | 13 + .../serdha/api/v0/runtime/ParametersSetter.kt | 18 + .../serdha/api/v0/runtime/ResultSet.kt | 18 + .../serdha/api/v0/runtime/Synchronizer.kt | 19 + .../serdha/api/v0/runtime/Transaction.kt | 28 ++ .../api/v0/runtime/_ParametersSetter.kt | 13 + .../serdha/api/v0/runtime/_ResultSet.kt | 15 + .../api/v0/runtime/transaction_methods.kt | 385 ++++++++++++++++++ 10 files changed, 516 insertions(+), 1 deletion(-) create mode 100644 src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/LowLevelApi.kt create mode 100644 src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/Database.kt create mode 100644 src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/ParametersSetter.kt create mode 100644 src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/ResultSet.kt create mode 100644 src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/Synchronizer.kt create mode 100644 src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/Transaction.kt create mode 100644 src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/_ParametersSetter.kt create mode 100644 src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/_ResultSet.kt create mode 100644 src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/transaction_methods.kt diff --git a/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/LowLevelApi.kt b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/LowLevelApi.kt new file mode 100644 index 0000000..268c693 --- /dev/null +++ b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/LowLevelApi.kt @@ -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 diff --git a/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/ddl/TableUpdater.kt b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/ddl/TableUpdater.kt index da7ecfc..4db363b 100644 --- a/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/ddl/TableUpdater.kt +++ b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/ddl/TableUpdater.kt @@ -3,7 +3,7 @@ package ru.landgrafhomyak.serdha.api.v0.ddl import ru.landgrafhomyak.serdha.api.v0.Expression -public interface TableUpdater : TableCreator { +public interface TableUpdater : TableCreator { public fun > keepColumn(c: Column): Column public fun > renameColumn(c: Column, newName: String): Column public fun > mapColumn(c: Column, newValue: Expression, where: Expression?) diff --git a/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/Database.kt b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/Database.kt new file mode 100644 index 0000000..852051a --- /dev/null +++ b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/Database.kt @@ -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() +} \ No newline at end of file diff --git a/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/ParametersSetter.kt b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/ParametersSetter.kt new file mode 100644 index 0000000..d947366 --- /dev/null +++ b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/ParametersSetter.kt @@ -0,0 +1,18 @@ +package ru.landgrafhomyak.serdha.api.v0.runtime + +import ru.landgrafhomyak.serdha.api.v0.ddl.Column + +public interface ParametersSetter { + public operator fun set(c: Column, value: RuntimeType) + + public operator fun set(c: Column, value: Byte): Unit = this.set(c, value) + public operator fun set(c: Column, value: UByte): Unit = this.set(c, value) + public operator fun set(c: Column, value: Short): Unit = this.set(c, value) + public operator fun set(c: Column, value: UShort): Unit = this.set(c, value) + public operator fun set(c: Column, value: Int): Unit = this.set(c, value) + public operator fun set(c: Column, value: UInt): Unit = this.set(c, value) + public operator fun set(c: Column, value: Long): Unit = this.set(c, value) + public operator fun set(c: Column, value: ULong): Unit = this.set(c, value) + public operator fun set(c: Column, value: Char): Unit = this.set(c, value) + public operator fun set(c: Column, value: Boolean): Unit = this.set(c, value) +} \ No newline at end of file diff --git a/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/ResultSet.kt b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/ResultSet.kt new file mode 100644 index 0000000..d02007a --- /dev/null +++ b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/ResultSet.kt @@ -0,0 +1,18 @@ +package ru.landgrafhomyak.serdha.api.v0.runtime + +import ru.landgrafhomyak.serdha.api.v0.ddl.Column + +public interface ResultSet { + public operator fun get(c: Column): RuntimeType + + public operator fun get(c: Column): Byte = this.get(c) + public operator fun get(c: Column): UByte = this.get(c) + public operator fun get(c: Column): Short = this.get(c) + public operator fun get(c: Column): UShort = this.get(c) + public operator fun get(c: Column): Int = this.get(c) + public operator fun get(c: Column): UInt = this.get(c) + public operator fun get(c: Column): Long = this.get(c) + public operator fun get(c: Column): ULong = this.get(c) + public operator fun get(c: Column): Char = this.get(c) + public operator fun get(c: Column): Boolean = this.get(c) +} \ No newline at end of file diff --git a/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/Synchronizer.kt b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/Synchronizer.kt new file mode 100644 index 0000000..8fd5b06 --- /dev/null +++ b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/Synchronizer.kt @@ -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 createTable(initializer: (TableCreator) -> TableUserWrapper): Table + public fun updateTable(oldTable: Table, initializer: (TableUpdater) -> TableNewUserWrapper): Table + public fun createSelect(initializer: (SelectCreator) -> QueryUserWrapper): Select + public fun createInsert(table: Table, initializer: (InsertCreator) -> QueryUserWrapper): Insert + public fun createUpdate(table: Table, initializer: (UpdateCreator) -> QueryUserWrapper): Update +} \ No newline at end of file diff --git a/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/Transaction.kt b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/Transaction.kt new file mode 100644 index 0000000..07250cf --- /dev/null +++ b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/Transaction.kt @@ -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 _select(compiledQuery: Select): _ParametersSetter> + + @Suppress("FunctionName") + @LowLevelApi + public fun _insert(compiledQuery: Insert): _ParametersSetter?> + + @Suppress("FunctionName") + @LowLevelApi + public fun _update(compiledQuery: Update): _ParametersSetter?> + + public suspend fun rollback() + + public suspend fun commit() + + @Suppress("FunctionName") + @LowLevelApi + public suspend fun _assertTransactionFinishedAndReleaseResources() +} \ No newline at end of file diff --git a/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/_ParametersSetter.kt b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/_ParametersSetter.kt new file mode 100644 index 0000000..2b4e204 --- /dev/null +++ b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/_ParametersSetter.kt @@ -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 : ParametersSetter { + @LowLevelApi + public suspend fun _execute(): Result + + @LowLevelApi + public suspend fun _abort() +} \ No newline at end of file diff --git a/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/_ResultSet.kt b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/_ResultSet.kt new file mode 100644 index 0000000..2becc7d --- /dev/null +++ b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/_ResultSet.kt @@ -0,0 +1,15 @@ +package ru.landgrafhomyak.serdha.api.v0.runtime + +import ru.landgrafhomyak.serdha.api.v0.LowLevelApi + +@Suppress("ClassName") +@LowLevelApi +public interface _ResultSet : ResultSet { + @Suppress("FunctionName") + @LowLevelApi + public suspend fun _next(): Boolean + + @Suppress("FunctionName") + @LowLevelApi + public suspend fun _close() +} \ No newline at end of file diff --git a/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/transaction_methods.kt b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/transaction_methods.kt new file mode 100644 index 0000000..5c47cd2 --- /dev/null +++ b/src/commonMain/kotlin/ru/landgrafhomyak/serdha/api/v0/runtime/transaction_methods.kt @@ -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 _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 _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 _wrapWithLambdas( + compiledQuery: Any, + queryGetter: () -> _ParametersSetter?>, + parameters: (ParametersSetter) -> Unit, + hasReturning: Boolean, + returning: (_ResultSet) -> 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 _wrapWithLambdasSingleOrNull( + compiledQuery: Any, + queryGetter: () -> _ParametersSetter?>, + parameters: (ParametersSetter) -> Unit, + hasReturning: Boolean, + returning: (ResultSet) -> 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 _wrapWithLambdasIterate( + compiledQuery: Any, + queryGetter: () -> _ParametersSetter?>, + parameters: (ParametersSetter) -> Unit, + hasReturning: Boolean, + returning: (ResultSet) -> 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 _wrapWithLambdasMap( + compiledQuery: Any, + queryGetter: () -> _ParametersSetter?>, + parameters: (ParametersSetter) -> Unit, + hasReturning: Boolean, + returning: (ResultSet) -> R, + queryWithoutErrorMessage: String +): List { + contract { + callsInPlace(queryGetter, InvocationKind.EXACTLY_ONCE) + callsInPlace(parameters, InvocationKind.EXACTLY_ONCE) + callsInPlace(returning, InvocationKind.UNKNOWN) + } + + _wrapWithLambdas( + compiledQuery, queryGetter, parameters, hasReturning, + { rs -> + val out = ArrayList() + while (rs._next()) + out.add(returning(rs)) + return@_wrapWithLambdasMap out + }, + queryWithoutErrorMessage + ) + throw QueryWithoutReturnError(queryWithoutErrorMessage, compiledQuery) +} + + +@OptIn(LowLevelApi::class) +public suspend inline fun Transaction.selectSingleOrNull( + compiledQuery: Select, + parameters: (ParametersSetter) -> Unit = {}, + rowsConsumer: (ResultSet) -> 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 Transaction.selectThenIterate( + compiledQuery: Select, + parameters: (ParametersSetter) -> Unit, + rowsConsumer: (ResultSet) -> 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 Transaction.selectThenMap( + compiledQuery: Select, + parameters: (ParametersSetter) -> Unit, + rowsConsumer: (ResultSet) -> R +): List { + 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 Transaction.insert( + compiledQuery: Insert, + parameters: (ParametersSetter) -> 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 Transaction.insertReturningSingleOrNull( + compiledQuery: Insert, + parameters: (ParametersSetter) -> Unit = {}, + rowsConsumer: (ResultSet) -> 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 Transaction.insertReturningIterate( + compiledQuery: Insert, + parameters: (ParametersSetter) -> Unit, + rowsConsumer: (ResultSet) -> 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 Transaction.insertReturningMap( + compiledQuery: Insert, + parameters: (ParametersSetter) -> Unit, + rowsConsumer: (ResultSet) -> R +): List { + 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 Transaction.update( + compiledQuery: Update, + parameters: (ParametersSetter) -> 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 Transaction.updateReturningSingleOrNull( + compiledQuery: Update, + parameters: (ParametersSetter) -> Unit = {}, + rowsConsumer: (ResultSet) -> 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 Transaction.updateReturningIterate( + compiledQuery: Update, + parameters: (ParametersSetter) -> Unit, + rowsConsumer: (ResultSet) -> 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 Transaction.updateReturningMap( + compiledQuery: Update, + parameters: (ParametersSetter) -> Unit, + rowsConsumer: (ResultSet) -> R +): List { + contract { + callsInPlace(parameters, InvocationKind.EXACTLY_ONCE) + callsInPlace(rowsConsumer, InvocationKind.UNKNOWN) + } + return _wrapWithLambdasMap( + compiledQuery = compiledQuery, + queryGetter = { this._update(compiledQuery) }, + parameters = parameters, + hasReturning = true, + returning = rowsConsumer, + "" + ) +} +