Skip to content
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7eb9012
Add async codegen and runtime
dellisd Apr 30, 2022
d52cff9
Split sync and async runtime modules
dellisd May 3, 2022
b9dabd0
Merge branch 'master' into async-drivers
May 4, 2022
ac736a9
Generate suspend functions for queries
dellisd May 4, 2022
a928067
Add runtime module tasks to gradle plugin tests
dellisd May 4, 2022
e18423c
Run spotless
dellisd May 4, 2022
bc032c3
Fix up broken async codegen tests
dellisd May 4, 2022
72fbdd5
Pull async gen flag from SqlDelightFile
dellisd May 4, 2022
83b0078
Add missing prop in intellij tests
dellisd May 4, 2022
68d65e9
Update async mysql integration test
dellisd May 4, 2022
a998844
Merge branch 'master' into async-drivers
dellisd May 4, 2022
8427d74
Add async runtime tests
dellisd May 4, 2022
183112a
Fix spotless errors
dellisd May 4, 2022
3d64593
Fix broken AsyncTest
dellisd May 4, 2022
7500c51
Fix js worker test
dellisd May 4, 2022
146ba97
Skip js web worker tests on node
dellisd May 5, 2022
4b34ce0
Add driver-async-test
dellisd May 5, 2022
87e3619
Disable mingwX86 on driver-async-test
dellisd May 5, 2022
bf84f44
Run spotless (again)
dellisd May 5, 2022
b2ab001
Apply suggestions from code review
dellisd May 5, 2022
a5a1959
Remove mingwX86 from runtime-async entirely
dellisd May 5, 2022
7fa7967
Clean things up
dellisd May 5, 2022
79ae9d6
Make Closeable.close() suspendable
dellisd May 5, 2022
6bccb9e
Add missing suspend modifier
dellisd May 5, 2022
e2415a9
Clean up driver-async-test to handle suspend funs
dellisd May 5, 2022
920e13a
Handle even more suspending test teardowns
dellisd May 5, 2022
1b25338
Run spotless
dellisd May 5, 2022
f96c407
Fix one last close function
dellisd May 5, 2022
76fae94
Fix up and enable remaining js worker tests
dellisd May 5, 2022
7fb0ae3
Remove driver-async-test
dellisd May 5, 2022
7155b04
Apply suggestions from code review
dellisd May 5, 2022
d4f5d34
Clean up async runtime
dellisd May 5, 2022
d510ace
Merge branch 'master' into async-drivers
dellisd May 5, 2022
289e86c
Delete async runtime tests
dellisd May 5, 2022
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
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ or join the [Jetbrains Platform Slack](https://blog.jetbrains.com/platform/2019/
If you're interested in creating your own driver, you can do so outside of the SQLDelight repository using the `runtime` artifact. To test the driver
you can depend on the `driver-test` and extend `DriverTest` and `TransactionTest` to ensure it works as SQLDelight would expect.

#### Asynchronous Drivers

Drivers that make asynchronous calls can be implemented by using the `runtime-async` artifact.

### Gradle

If you're encountering a gradle issue, start by creating a test fixture in `sqldelight-gradle-plugin/src/test` similar to the other folders there
Expand Down
2 changes: 1 addition & 1 deletion adapters/primitive-adapters/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ kotlin {
sourceSets {
commonMain {
dependencies {
api project(':runtime')
api project(':runtime:runtime')
}
}
commonTest {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package app.cash.sqldelight.dialects.hsql

import app.cash.sqldelight.dialect.api.RuntimeTypes
import app.cash.sqldelight.dialect.api.SqlDelightDialect
import app.cash.sqldelight.dialect.api.TypeResolver
import app.cash.sqldelight.dialects.hsql.grammar.HsqlParserUtil
Expand All @@ -13,9 +14,12 @@ import com.squareup.kotlinpoet.ClassName
* Base dialect for JDBC implementations.
*/
class HsqlDialect : SqlDelightDialect {
override val driverType = ClassName("app.cash.sqldelight.driver.jdbc", "JdbcDriver")
override val cursorType = ClassName("app.cash.sqldelight.driver.jdbc", "JdbcCursor")
override val preparedStatementType = ClassName("app.cash.sqldelight.driver.jdbc", "JdbcPreparedStatement")
override val runtimeTypes: RuntimeTypes = RuntimeTypes(
ClassName("app.cash.sqldelight.driver.jdbc", "JdbcDriver"),
ClassName("app.cash.sqldelight.driver.jdbc", "JdbcCursor"),
ClassName("app.cash.sqldelight.driver.jdbc", "JdbcPreparedStatement")
)

override val icon = AllIcons.Providers.Hsqldb

override fun setup() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package app.cash.sqldelight.dialects.mysql

import app.cash.sqldelight.dialect.api.ConnectionManager
import app.cash.sqldelight.dialect.api.RuntimeTypes
import app.cash.sqldelight.dialect.api.SqlDelightDialect
import app.cash.sqldelight.dialect.api.TypeResolver
import app.cash.sqldelight.dialects.mysql.grammar.MySqlParserUtil
Expand All @@ -16,9 +17,18 @@ import com.squareup.kotlinpoet.ClassName
* Base dialect for JDBC implementations.
*/
class MySqlDialect : SqlDelightDialect {
override val driverType = ClassName("app.cash.sqldelight.driver.jdbc", "JdbcDriver")
override val cursorType = ClassName("app.cash.sqldelight.driver.jdbc", "JdbcCursor")
override val preparedStatementType = ClassName("app.cash.sqldelight.driver.jdbc", "JdbcPreparedStatement")
override val runtimeTypes: RuntimeTypes = RuntimeTypes(
ClassName("app.cash.sqldelight.driver.jdbc", "JdbcDriver"),
ClassName("app.cash.sqldelight.driver.jdbc", "JdbcCursor"),
ClassName("app.cash.sqldelight.driver.jdbc", "JdbcPreparedStatement")
)

override val asyncRuntimeTypes: RuntimeTypes = RuntimeTypes(
ClassName("app.cash.sqldelight.driver.r2dbc", "R2dbcDriver"),
ClassName("app.cash.sqldelight.driver.r2dbc", "R2dbcCursor"),
ClassName("app.cash.sqldelight.driver.r2dbc", "R2dbcPreparedStatement"),
)

override val icon = AllIcons.Providers.Mysql
override val connectionManager: ConnectionManager by lazy { MySqlConnectionManager() }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package app.cash.sqldelight.dialects.postgresql

import app.cash.sqldelight.dialect.api.ConnectionManager
import app.cash.sqldelight.dialect.api.RuntimeTypes
import app.cash.sqldelight.dialect.api.SqlDelightDialect
import app.cash.sqldelight.dialect.api.TypeResolver
import app.cash.sqldelight.dialects.postgresql.grammar.PostgreSqlParserUtil
Expand All @@ -15,9 +16,18 @@ import com.squareup.kotlinpoet.ClassName
* Base dialect for JDBC implementations.
*/
class PostgreSqlDialect : SqlDelightDialect {
override val driverType = ClassName("app.cash.sqldelight.driver.jdbc", "JdbcDriver")
override val cursorType = ClassName("app.cash.sqldelight.driver.jdbc", "JdbcCursor")
override val preparedStatementType = ClassName("app.cash.sqldelight.driver.jdbc", "JdbcPreparedStatement")
override val runtimeTypes: RuntimeTypes = RuntimeTypes(
ClassName("app.cash.sqldelight.driver.jdbc", "JdbcDriver"),
ClassName("app.cash.sqldelight.driver.jdbc", "JdbcCursor"),
ClassName("app.cash.sqldelight.driver.jdbc", "JdbcPreparedStatement"),
)

override val asyncRuntimeTypes: RuntimeTypes = RuntimeTypes(
ClassName("app.cash.sqldelight.driver.r2dbc", "R2dbcDriver"),
ClassName("app.cash.sqldelight.driver.r2dbc", "R2dbcCursor"),
ClassName("app.cash.sqldelight.driver.r2dbc", "R2dbcPreparedStatement"),
)

override val allowsReferenceCycles = false
override val icon = AllIcons.Providers.Postgresql
override val connectionManager: ConnectionManager by lazy { PostgresConnectionManager() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,12 @@ import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.extensions.PluginId
import com.intellij.psi.stubs.StubElementTypeHolderEP
import com.squareup.kotlinpoet.ClassName
import timber.log.Timber

/**
* A dialect for SQLite.
*/
open class SqliteDialect : SqlDelightDialect {
override val driverType = ClassName("app.cash.sqldelight.db", "SqlDriver")
override val cursorType = ClassName("app.cash.sqldelight.db", "SqlCursor")
override val preparedStatementType = ClassName("app.cash.sqldelight.db", "SqlPreparedStatement")
override val isSqlite = true
override val icon = AllIcons.Providers.Sqlite
override val migrationStrategy = SqliteMigrationStrategy()
Expand Down
2 changes: 1 addition & 1 deletion drivers/android-driver/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ dependencies {
// workaround for https://youtrack.jetbrains.com/issue/KT-27059
configurations.all {
resolutionStrategy.dependencySubstitution {
substitute module("${project.property("GROUP")}:runtime-jvm:${project.property("VERSION_NAME")}") with project(':runtime')
substitute module("${project.property("GROUP")}:runtime-jvm:${project.property("VERSION_NAME")}") with project(':runtime:runtime')
}
}

Expand Down
2 changes: 1 addition & 1 deletion drivers/driver-test/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ kotlin {
sourceSets {
commonMain {
dependencies {
api project(':runtime')
api project(':runtime:runtime')

implementation deps.kotlin.test.common
implementation deps.kotlin.test.commonAnnotations
Expand Down
2 changes: 1 addition & 1 deletion drivers/jdbc-driver/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ plugins {
archivesBaseName = 'sqldelight-jdbc-driver'

dependencies {
api project(':runtime')
api project(':runtime:runtime')
}

apply from: "$rootDir/gradle/gradle-mvn-push.gradle"
2 changes: 1 addition & 1 deletion drivers/native-driver/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ kotlin {
sourceSets {
commonMain {
dependencies {
api project (':runtime')
api project (':runtime:runtime')
}
}
commonTest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import co.touchlab.sqliter.DatabaseFileContext
import co.touchlab.sqliter.JournalMode
import co.touchlab.testhelp.concurrency.currentTimeMillis
import co.touchlab.testhelp.concurrency.sleep
import platform.posix.sleep
import kotlin.native.concurrent.AtomicInt
import kotlin.native.concurrent.Worker
import kotlin.test.AfterTest
Expand Down
16 changes: 16 additions & 0 deletions drivers/r2dbc-driver/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
plugins {
alias(deps.plugins.kotlin.jvm)
alias(deps.plugins.publish)
alias(deps.plugins.dokka)
}

archivesBaseName = 'sqldelight-r2dbc-driver'

dependencies {
api project(':runtime:runtime-async')
implementation deps.r2dbc
implementation deps.kotlin.coroutines.core
implementation deps.kotlin.coroutines.reactive
}

apply from: "$rootDir/gradle/gradle-mvn-push.gradle"
4 changes: 4 additions & 0 deletions drivers/r2dbc-driver/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
POM_ARTIFACT_ID=r2dbc-driver
POM_NAME=SQLDelight R2DBC Driver
POM_DESCRIPTION=R2DBC driver for SQLDelight
POM_PACKAGING=jar
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package app.cash.sqldelight.driver.r2dbc

import app.cash.sqldelight.async.AsyncQuery
import app.cash.sqldelight.async.AsyncTransacter
import app.cash.sqldelight.async.db.AsyncSqlCursor
import app.cash.sqldelight.async.db.AsyncSqlDriver
import app.cash.sqldelight.async.db.AsyncSqlPreparedStatement
import io.r2dbc.spi.Connection
import io.r2dbc.spi.Statement
import kotlinx.coroutines.reactive.awaitLast
import kotlinx.coroutines.reactive.awaitSingle
import org.reactivestreams.Publisher
import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription

class R2dbcDriver(val connection: Connection) : AsyncSqlDriver {
override suspend fun <R> executeQuery(
identifier: Int?,
sql: String,
mapper: (AsyncSqlCursor) -> R,
parameters: Int,
binders: (AsyncSqlPreparedStatement.() -> Unit)?
): R {
val prepared = connection.createStatement(sql).also { statement ->
R2dbcPreparedStatement(statement).apply { if (binders != null) this.binders() }
}
val result = prepared.execute().awaitSingle()

val rowSet = mutableListOf<Map<Int, Any?>>()
result.map { row, rowMetadata ->
rowSet.add(rowMetadata.columnMetadatas.mapIndexed { index, _ -> index to row.get(index) }.toMap())
}.awaitLast()

return mapper(R2dbcCursor(rowSet))
}

override suspend fun execute(
identifier: Int?,
sql: String,
parameters: Int,
binders: (AsyncSqlPreparedStatement.() -> Unit)?
): Long {
val prepared = connection.createStatement(sql).also { statement ->
R2dbcPreparedStatement(statement).apply { if (binders != null) this.binders() }
}

val result = prepared.execute().awaitSingle()
// TODO: r2dbc-mysql emits a java.lang.Integer instead of a java.lang.Long, mysql driver needs to support latest r2dbc-spi
// return result.rowsUpdated.awaitSingle()
return 0L
}

private val transactions = ThreadLocal<Transaction>()
var transaction: Transaction?
get() = transactions.get()
set(value) {
transactions.set(value)
}

override suspend fun newTransaction(): AsyncTransacter.Transaction {
val enclosing = transaction
val transaction = Transaction(enclosing, this.connection)
connection.beginTransaction().awaitSingle()

return transaction
}

override fun currentTransaction(): AsyncTransacter.Transaction? = transaction

override fun addListener(listener: AsyncQuery.Listener, queryKeys: Array<String>) {
}

override fun removeListener(listener: AsyncQuery.Listener, queryKeys: Array<String>) {
}

override fun notifyListeners(queryKeys: Array<String>) {
}

override suspend fun close() {
connection.close().awaitSingle()
}

class Transaction(
override val enclosingTransaction: AsyncTransacter.Transaction?,
private val connection: Connection
) : AsyncTransacter.Transaction() {
override suspend fun endTransaction(successful: Boolean) {
if (enclosingTransaction == null) {
if (successful) connection.commitTransaction().awaitSingle()
} else {
connection.rollbackTransaction().awaitSingle()
}
}
}
}

open class R2dbcPreparedStatement(private val statement: Statement) : AsyncSqlPreparedStatement {
override fun bindBytes(index: Int, bytes: ByteArray?) {
if (bytes == null) {
statement.bindNull(index - 1, ByteArray::class.java)
} else {
statement.bind(index - 1, bytes)
}
}

override fun bindLong(index: Int, long: Long?) {
if (long == null) {
statement.bindNull(index - 1, Long::class.java)
} else {
statement.bind(index - 1, long)
}
}

override fun bindDouble(index: Int, double: Double?) {
if (double == null) {
statement.bindNull(index - 1, Double::class.java)
} else {
statement.bind(index - 1, double)
}
}

override fun bindString(index: Int, string: String?) {
if (string == null) {
statement.bindNull(index - 1, String::class.java)
} else {
statement.bind(index - 1, string)
}
}

override fun bindBoolean(index: Int, boolean: Boolean?) {
if (boolean == null) {
statement.bindNull(index - 1, Boolean::class.java)
} else {
statement.bind(index - 1, boolean)
}
}

fun bindObject(index: Int, any: Any?) {
if (any == null) {
statement.bindNull(index - 1, Any::class.java)
} else {
statement.bind(index - 1, any)
}
}
}

/**
* TODO: Write a better async cursor API
*/
open class R2dbcCursor(val rowSet: List<Map<Int, Any?>>) : AsyncSqlCursor {
var row = -1
private set

override fun next(): Boolean = ++row < rowSet.size

override fun getString(index: Int): String? = rowSet[row][index] as String?

override fun getLong(index: Int): Long? = (rowSet[row][index] as Number?)?.toLong()

override fun getBytes(index: Int): ByteArray? = rowSet[row][index] as ByteArray?

override fun getDouble(index: Int): Double? = rowSet[row][index] as Double?

override fun getBoolean(index: Int): Boolean? = rowSet[row][index] as Boolean?

inline fun <reified T : Any> getObject(index: Int): T? = rowSet[row][index] as T?

@Suppress("UNCHECKED_CAST")
fun <T> getArray(index: Int): Array<T>? = rowSet[row][index] as Array<T>?
}

private fun <T> Publisher<T>.subscribe(
next: (T) -> Unit = {},
error: (Throwable) -> Unit = {},
complete: () -> Unit = {},
) = subscribe(object : Subscriber<T> {
private var subscription: Subscription? = null
override fun onSubscribe(s: Subscription) {
subscription = s
s.request(Long.MAX_VALUE)
}

override fun onNext(t: T) {
next(t)
}

override fun onError(t: Throwable) {
error(t)
}

override fun onComplete() {
complete()
subscription?.cancel()
}
})
5 changes: 4 additions & 1 deletion drivers/sqljs-driver/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,15 @@ kotlin {
}

sourceSets["main"].dependencies {
api project(':runtime')
api project(':runtime:runtime')
api project(':runtime:runtime-async')
implementation deps.kotlin.coroutines.core
}
sourceSets["test"].dependencies {
implementation deps.kotlin.test.js
implementation npm('sql.js', deps.versions.sqljs.get())
implementation devNpm("copy-webpack-plugin", "9.1.0")
implementation deps.kotlin.coroutines.test
}
}

Expand Down
Loading