From b757b4c589056d9d22c9e22e006aa1b9696088e9 Mon Sep 17 00:00:00 2001 From: Andrew Golovashevich Date: Mon, 19 Aug 2024 05:23:00 +0300 Subject: [PATCH] [history/cw-adjutant] Namespaces and table names bound to JDBC SQLite connection --- .../db/raw_sql_skeleton/namespaces_sqlite.kt | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/jvmMain/kotlin/ru/landgrafhomyak/db/raw_sql_skeleton/namespaces_sqlite.kt diff --git a/src/jvmMain/kotlin/ru/landgrafhomyak/db/raw_sql_skeleton/namespaces_sqlite.kt b/src/jvmMain/kotlin/ru/landgrafhomyak/db/raw_sql_skeleton/namespaces_sqlite.kt new file mode 100644 index 0000000..dfcc9ae --- /dev/null +++ b/src/jvmMain/kotlin/ru/landgrafhomyak/db/raw_sql_skeleton/namespaces_sqlite.kt @@ -0,0 +1,102 @@ +@file:JvmName("NamespacesSqliteKt") + +package ru.landgrafhomyak.db.raw_sql_skeleton + +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import java.sql.Connection +import java.sql.DriverManager +import java.util.concurrent.Callable +import java.util.concurrent.Executors.newFixedThreadPool +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + + +public class SqliteConnection private constructor( + internal val jdbcConnection: Connection, +) { + private val executor = newFixedThreadPool(1) + + private val mutex = Mutex() + + private inner class Task( + private val continuation: Continuation, + private val task: (connection: Connection) -> R, + ) : Callable { + override fun call() { + try { + this.continuation.resume(this.task(this@SqliteConnection.jdbcConnection)) + } catch (e: InterruptedException) { + this.continuation.resumeWithException(RuntimeException("runner interrupted")) + throw e + } catch (e: Throwable) { + this.continuation.resumeWithException(e) + } + } + } + + public suspend fun transaction(body: (connection: Connection) -> R): R = this.raw { connection -> + return@raw connection.transaction(body) + } + + public suspend fun raw(action: (Connection) -> R): R = this.mutex.withLock { + return@withLock suspendCoroutine { continuation -> + this.executor.submit(this.Task(continuation, action)) + } + } + + public val rootNamespace: SqliteNamespace = SqliteNamespace(this, emptyArray()) + + public companion object { + @JvmStatic + public suspend fun wrap7prepare(rawConnection: Connection): SqliteConnection { + val w = SqliteConnection(rawConnection) + w.raw { connection -> + connection.autoCommit = false + connection.prepareStatement("ROLLBACK TRANSACTION") { ps -> ps.execute() } + connection.prepareStatement("PRAGMA foreign_keys=TRUE") { ps -> ps.execute() } + } + return w + } + + @JvmStatic + public suspend fun connect(url: String): SqliteConnection = this.wrap7prepare(DriverManager.getConnection(url)) + + @JvmStatic + public fun connectBlocking(url: String): SqliteConnection = runBlocking { this@Companion.connect(url) } + } +} + +public class SqliteNamespace( + private val owner: SqliteConnection, + private val path: Array, +) { + private val usedNames = HashSet() + + public fun subNamespace(name: String): SqliteNamespace { + if (name in this.usedNames) + throw IllegalArgumentException("Name is already used: $name") + this.usedNames.add(name) + return SqliteNamespace(this.owner, this.path + name) + } + + public fun table(name: String): SqliteTableName { + if (name in this.usedNames) + throw IllegalArgumentException("Name is already used: $name") + this.usedNames.add(name) + + return SqliteTableName(this.formatTableName(name)) + } + + + private fun formatTableName(name: String) = this.path.joinToString(prefix = "\"::", separator = "::", postfix = "::${name}\"") +} + + +@JvmInline +public value class SqliteTableName(private val value: String) { + override fun toString(): String = this.value +}