Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ When using JDBC, add the following to your `build.gradle.kts` file:

```kotlin
plugins {
kotlin("jvm") version "2.1.0" // Currently the plugin is only available for Kotlin-JVM
id("io.exoquery.terpal-plugin") version "2.1.0-2.0.0.PL"
kotlin("plugin.serialization") version "2.1.0"
kotlin("jvm") version "2.2.0" // Currently the plugin is only available for Kotlin-JVM
id("io.exoquery.terpal-plugin") version "2.2.0-2.0.0.PL"
kotlin("plugin.serialization") version "2.2.0"
}

dependencies {
Expand Down Expand Up @@ -116,9 +116,9 @@ For Android development, add the following to your `build.gradle.kts` file:

```kotlin
plugins {
kotlin("android") version "2.1.0"
id("io.exoquery.terpal-plugin") version "2.1.0-2.0.0.PL"
kotlin("plugin.serialization") version "2.1.0"
kotlin("android") version "2.2.0"
id("io.exoquery.terpal-plugin") version "2.2.0-2.0.0.PL"
kotlin("plugin.serialization") version "2.2.0"
}

dependencies {
Expand Down Expand Up @@ -192,9 +192,9 @@ val person: List<Person> = Sql("SELECT * FROM Person").queryOf<Person>().runOn(c
For iOS, OSX, Linux and Windows development, with Kotlin Multiplatform, add the following to your `build.gradle.kts` file:
```kotlin
plugins {
kotlin("multiplatform") version "2.1.0"
id("io.exoquery.terpal-plugin") version "2.1.0-2.0.0.PL"
kotlin("plugin.serialization") version "2.1.0"
kotlin("multiplatform") version "2.2.0"
id("io.exoquery.terpal-plugin") version "2.2.0-2.0.0.PL"
kotlin("plugin.serialization") version "2.2.0"
}

kotlin {
Expand Down
10 changes: 5 additions & 5 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
plugins {
`maven-publish`
signing
kotlin("jvm") version "2.1.0" apply false
kotlin("jvm") version "2.2.0" apply false
id("io.github.gradle-nexus.publish-plugin") version "1.1.0" apply false
kotlin("multiplatform") version "2.1.0" apply false
kotlin("multiplatform") version "2.2.0" apply false
id("com.android.library") version "8.2.0" apply false
id("org.jetbrains.dokka") version "1.9.10" apply false
}
Expand Down Expand Up @@ -85,9 +85,9 @@ subprojects {
artifact(javadocJar)

pom {
name.set("decomat")
description.set("DecoMat - Deconstructive Pattern Matching for Kotlin")
url.set("https://github.com/exoquery/decomat")
name.set("terpal-sql")
description.set("Safe and fun SQL buliding with interpolated strings in Kotlin")
url.set("https://github.com/exoquery/terpal-sql")

licenses {
license {
Expand Down
8 changes: 5 additions & 3 deletions controller-android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins {
id("conventions")
kotlin("multiplatform")
id("com.android.library")
kotlin("plugin.serialization") version "2.1.0"
kotlin("plugin.serialization") version "2.2.0"
// Already on the classpath
//id("org.jetbrains.kotlin.android") version "1.9.23"
}
Expand Down Expand Up @@ -39,8 +39,10 @@ kotlin {

androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = "17"
compileTaskProvider {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
}
}
publishLibraryVariants("release", "debug")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ class AndroidDatabaseController internal constructor(
}

// Is there an open writer?
override fun CoroutineContext.hasOpenConnection(): Boolean {
override suspend fun CoroutineContext.hasOpenConnection(): Boolean {
val session = get(sessionKey)?.session
return session != null && session.isWriter && !isClosedSession(session)
}
Expand Down Expand Up @@ -401,7 +401,7 @@ interface WithReadOnlyVerbs: RequiresSession<Connection, SupportSQLiteStatement,
prepareSession(pool.borrowReader())

// Check if there is at least a reader on th context, if it has a writer that's fine too
fun CoroutineContext.hasOpenReadOrWriteConnection(): Boolean {
suspend fun CoroutineContext.hasOpenReadOrWriteConnection(): Boolean {
val session = get(sessionKey)?.session
return session != null && !isClosedSession(session)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ interface HasSessionAndroid: RequiresSession<Connection, SupportSQLiteStatement,

fun prepareSession(session: Connection): Connection

override fun closeSession(session: Connection): Unit = session.close()
override fun isClosedSession(session: Connection): Boolean = !session.isOpen()
override suspend fun closeSession(session: Connection): Unit = session.close()
override suspend fun isClosedSession(session: Connection): Boolean = !session.isOpen()

override suspend fun <R> accessStmtReturning(
sql: String,
Expand Down Expand Up @@ -64,7 +64,7 @@ interface HasSessionAndroid: RequiresSession<Connection, SupportSQLiteStatement,
// reader-needs-writer,writer-needs-reader scenario since the the coroutine that has
// the writer session will use it as the reader (see hasOpenReadOnlyConnection which
// doesn't care where the thing it has is a reader or writer).
override fun CoroutineContext.hasOpenConnection(): Boolean {
override suspend fun CoroutineContext.hasOpenConnection(): Boolean {
val session = get(sessionKey)?.session
return session != null && session.isWriter && !isClosedSession(session)
}
Expand Down
2 changes: 1 addition & 1 deletion controller-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
id("conventions")
kotlin("multiplatform")
kotlin("plugin.serialization") version "2.1.0"
kotlin("plugin.serialization") version "2.2.0"
id("nativebuild")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,12 @@ interface RequiresSession<Session, Stmt, ExecutionOpts> {
// Methods that implementors need to provide
val sessionKey: CoroutineContext.Key<CoroutineSession<Session>>
abstract suspend fun newSession(executionOptions: ExecutionOpts): Session
abstract fun closeSession(session: Session): Unit
abstract fun isClosedSession(session: Session): Boolean
abstract suspend fun closeSession(session: Session): Unit
abstract suspend fun isClosedSession(session: Session): Boolean
suspend fun <R> accessStmt(sql: String, conn: Session, block: suspend (Stmt) -> R): R
suspend fun <R> accessStmtReturning(sql: String, conn: Session, options: ExecutionOpts, returningColumns: List<String>, block: suspend (Stmt) -> R): R

fun CoroutineContext.hasOpenConnection(): Boolean {
suspend fun CoroutineContext.hasOpenConnection(): Boolean {
val session = get(sessionKey)?.session
return session != null && !isClosedSession(session)
}
Expand All @@ -102,7 +102,10 @@ interface RequiresSession<Session, Stmt, ExecutionOpts> {
} else {
val session = newSession(executionOptions)
try {
withContext(CoroutineSession(session, sessionKey) + Dispatchers.IO) { block() }
withContext(CoroutineSession(session, sessionKey) + Dispatchers.IO) {
val output = block()
output
}
} finally { closeSession(session) }
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.exoquery.controller

class ControllerError(message: String, cause: Throwable? = null) : Exception(message, cause)
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package io.exoquery.controller

import io.exoquery.controller.SqlDecoder
import io.exoquery.controller.SqlEncoder
import io.exoquery.controller.SqlEncoding
import io.exoquery.controller.TimeEncoding
import java.time.*
import java.math.BigDecimal
import java.util.Date
Expand Down
4 changes: 2 additions & 2 deletions controller-jdbc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask

plugins {
id("conventions")
kotlin("multiplatform")
kotlin("plugin.serialization") version "2.1.0"
kotlin("plugin.serialization") version "2.2.0"
}

version = extra["controllerVersion"].toString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ interface HasSessionJdbc: RequiresSession<Connection, PreparedStatement, JdbcExe
conn
}

override open fun closeSession(session: Connection): Unit = session.close()
override open fun isClosedSession(session: Connection): Boolean = session.isClosed
override open suspend fun closeSession(session: Connection): Unit = session.close()
override open suspend fun isClosedSession(session: Connection): Boolean = session.isClosed

override open suspend fun <R> accessStmtReturning(sql: String, conn: Connection, options: JdbcExecutionOptions, returningColumns: List<String>, block: suspend (PreparedStatement) -> R): R {
val stmt =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,10 @@ package io.exoquery.controller.jdbc

import io.exoquery.controller.*
import kotlinx.coroutines.flow.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.modules.SerializersModule
import javax.sql.DataSource
import kotlinx.datetime.TimeZone
import java.sql.*


/**
* Most constructions will want to specify default values from AdditionalJdbcEncoding for additionalEncoders/decoders,
* and they should have a simple construction JdbcEncodingConfig(...). Use `Empty` to make a config that does not
* include these defaults. For this reason the real constructor is private.
*/
data class JdbcEncodingConfig private constructor(
override val additionalEncoders: Set<SqlEncoder<Connection, PreparedStatement, out Any>>,
override val additionalDecoders: Set<SqlDecoder<Connection, ResultSet, out Any>>,
override val json: Json,
// If you want to use any primitive-wrapped contextual encoders you need to add them here
override val module: SerializersModule,
override val timezone: TimeZone, override val debugMode: Boolean
): EncodingConfig<Connection, PreparedStatement, ResultSet> {
companion object {
val Default get() =
Default(
AdditionalJdbcEncoding.encoders,
AdditionalJdbcEncoding.decoders
)

fun Default(
additionalEncoders: Set<SqlEncoder<Connection, PreparedStatement, out Any>> = setOf(),
additionalDecoders: Set<SqlDecoder<Connection, ResultSet, out Any>> = setOf(),
json: Json = Json,
module: SerializersModule = EmptySerializersModule(),
timezone: TimeZone = TimeZone.currentSystemDefault(),
debugMode: Boolean = false
) = JdbcEncodingConfig(
additionalEncoders + AdditionalJdbcEncoding.encoders,
additionalDecoders + AdditionalJdbcEncoding.decoders,
json,
module,
timezone,
debugMode
)

operator fun invoke(
additionalEncoders: Set<SqlEncoder<Connection, PreparedStatement, out Any>> = setOf(),
additionalDecoders: Set<SqlDecoder<Connection, ResultSet, out Any>> = setOf(),
json: Json = Json,
module: SerializersModule = EmptySerializersModule(),
timezone: TimeZone = TimeZone.currentSystemDefault()
) = Default(additionalEncoders, additionalDecoders, json, module, timezone)

fun Empty(
additionalEncoders: Set<SqlEncoder<Connection, PreparedStatement, out Any>> = setOf(),
additionalDecoders: Set<SqlDecoder<Connection, ResultSet, out Any>> = setOf(),
json: Json = Json,
module: SerializersModule = EmptySerializersModule(),
timezone: TimeZone = TimeZone.currentSystemDefault()
) = JdbcEncodingConfig(additionalEncoders, additionalDecoders, json, module, timezone)
}
}

/**
* This is a Terpal Driver, NOT a JDBC driver! It is the base class for all JDBC-based implementations of the
* Terpal Driver base class `io.exoquery.sql.Driver`. This naming follows the conventions of SQL Delight
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package io.exoquery.controller.jdbc

import io.exoquery.controller.EncodingConfig
import io.exoquery.controller.SqlDecoder
import io.exoquery.controller.SqlEncoder
import kotlinx.datetime.TimeZone
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.modules.SerializersModule
import java.sql.Connection
import java.sql.PreparedStatement
import java.sql.ResultSet

/**
* Most constructions will want to specify default values from AdditionalJdbcEncoding for additionalEncoders/decoders,
* and they should have a simple construction JdbcEncodingConfig(...). Use `Empty` to make a config that does not
* include these defaults. For this reason the real constructor is private.
*/
data class JdbcEncodingConfig private constructor(
override val additionalEncoders: Set<SqlEncoder<Connection, PreparedStatement, out Any>>,
override val additionalDecoders: Set<SqlDecoder<Connection, ResultSet, out Any>>,
override val json: Json,
// If you want to use any primitive-wrapped contextual encoders you need to add them here
override val module: SerializersModule,
override val timezone: TimeZone, override val debugMode: Boolean
): EncodingConfig<Connection, PreparedStatement, ResultSet> {
companion object {
val Default get() =
Default(
AdditionalJdbcEncoding.encoders,
AdditionalJdbcEncoding.decoders
)

fun Default(
additionalEncoders: Set<SqlEncoder<Connection, PreparedStatement, out Any>> = setOf(),
additionalDecoders: Set<SqlDecoder<Connection, ResultSet, out Any>> = setOf(),
json: Json = Json.Default,
module: SerializersModule = EmptySerializersModule(),
timezone: TimeZone = TimeZone.Companion.currentSystemDefault(),
debugMode: Boolean = false
) = JdbcEncodingConfig(
additionalEncoders + AdditionalJdbcEncoding.encoders,
additionalDecoders + AdditionalJdbcEncoding.decoders,
json,
module,
timezone,
debugMode
)

operator fun invoke(
additionalEncoders: Set<SqlEncoder<Connection, PreparedStatement, out Any>> = setOf(),
additionalDecoders: Set<SqlDecoder<Connection, ResultSet, out Any>> = setOf(),
json: Json = Json.Default,
module: SerializersModule = EmptySerializersModule(),
timezone: TimeZone = TimeZone.Companion.currentSystemDefault()
) = Default(additionalEncoders, additionalDecoders, json, module, timezone)

fun Empty(
additionalEncoders: Set<SqlEncoder<Connection, PreparedStatement, out Any>> = setOf(),
additionalDecoders: Set<SqlDecoder<Connection, ResultSet, out Any>> = setOf(),
json: Json = Json.Default,
module: SerializersModule = EmptySerializersModule(),
timezone: TimeZone = TimeZone.Companion.currentSystemDefault()
) = JdbcEncodingConfig(additionalEncoders, additionalDecoders, json, module, timezone)
}
}
2 changes: 1 addition & 1 deletion controller-native/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import org.jetbrains.kotlin.konan.target.HostManager
plugins {
id("conventions")
kotlin("multiplatform")
kotlin("plugin.serialization") version "2.1.0"
kotlin("plugin.serialization") version "2.2.0"
id("nativebuild")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ interface HasSessionNative: RequiresSession<Connection, Statement, UnusedOpts> {
// Use this for the transactor pool (that's what the RequiresTransactionality interface is for)
// for reader connections we borrow readers
override suspend fun newSession(options: UnusedOpts): Connection = pool.borrowWriter()
override fun closeSession(session: Connection): Unit = session.close()
override fun isClosedSession(session: Connection): Boolean = !session.isOpen()
override suspend fun closeSession(session: Connection): Unit = session.close()
override suspend fun isClosedSession(session: Connection): Boolean = !session.isOpen()

override suspend fun <R> accessStmtReturning(sql: String, conn: Connection, options: UnusedOpts, returningColumns: List<String>, block: suspend (Statement) -> R): R {
val stmt = conn.value.createStatement(sql)
Expand Down Expand Up @@ -60,7 +60,7 @@ interface HasSessionNative: RequiresSession<Connection, Statement, UnusedOpts> {
// reader-needs-writer,writer-needs-reader scenario since the the coroutine that has
// the writer session will use it as the reader (see hasOpenReadOnlyConnection which
// doesn't care where the thing it has is a reader or writer).
override fun CoroutineContext.hasOpenConnection(): Boolean {
override suspend fun CoroutineContext.hasOpenConnection(): Boolean {
val session = get(sessionKey)?.session
return session != null && session.isWriter && !isClosedSession(session)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ class NativeDatabaseController internal constructor(
}

// Is there an open writer?
override fun CoroutineContext.hasOpenConnection(): Boolean {
override suspend fun CoroutineContext.hasOpenConnection(): Boolean {
val session = get(sessionKey)?.session
//if (session != null)
// println("--------- (${currentThreadId()}) Found session: ${if (session.isWriter) "WRITER" else "JUST READER, needs promotion" } - isClosed: ${isClosedSession(session)}")
Expand Down Expand Up @@ -326,7 +326,7 @@ interface WithReadOnlyVerbs: RequiresSession<Connection, Statement, UnusedOpts>
suspend fun newReadOnlySession(): Connection = pool.borrowReader()

// Check if there is at least a reader on th context, if it has a writer that's fine too
fun CoroutineContext.hasOpenReadOrWriteConnection(): Boolean {
suspend fun CoroutineContext.hasOpenReadOrWriteConnection(): Boolean {
val session = get(sessionKey)?.session
return session != null && !isClosedSession(session)
}
Expand Down
Loading