[history/cw-adjutant] Namespaces and table names bound to JDBC SQLite connection

This commit is contained in:
Andrew Golovashevich 2024-08-19 05:23:00 +03:00
commit b757b4c589

View File

@ -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<R>(
private val continuation: Continuation<R>,
private val task: (connection: Connection) -> R,
) : Callable<Unit> {
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 <R> transaction(body: (connection: Connection) -> R): R = this.raw { connection ->
return@raw connection.transaction(body)
}
public suspend fun <R> 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<String>,
) {
private val usedNames = HashSet<String>()
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
}