Skip to content

Commit

Permalink
commonize WebWorkerDriver. rename createWebWorkerDriver() to `creat…
Browse files Browse the repository at this point in the history
…eDefaultWebWorkerDriver()`. remove unnecessary `jsCommon` group from web-worker-driver.
  • Loading branch information
IlyaGulya committed May 26, 2024
1 parent c56ae6e commit ece537a
Show file tree
Hide file tree
Showing 41 changed files with 624 additions and 475 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class MultiplatformConventions : Plugin<Project> {
project.plugins.apply("org.jetbrains.kotlin.multiplatform")

(project.kotlinExtension as KotlinMultiplatformExtension).apply {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions {
this.freeCompilerArgs.addAll(
"-Xexpect-actual-classes",
Expand Down
18 changes: 12 additions & 6 deletions drivers/web-worker-driver/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation

plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.publish)
Expand All @@ -16,20 +18,18 @@ kotlin {
}
applyDefaultHierarchyTemplate {
it.common {
it.group("jsCommon") {
it.withJs()
it.withWasm()
}
it.withJs()
it.withWasm()
}
}

sourceSets {
jsCommonMain.dependencies {
commonMain.dependencies {
api projects.runtime
implementation libs.kotlin.coroutines.core
}

jsCommonTest.dependencies {
commonTest.dependencies {
implementation libs.kotlin.test
implementation npm("sql.js", libs.versions.sqljs.get())
implementation npm("@cashapp/sqldelight-sqljs-worker", project(":drivers:web-worker-driver:sqljs").projectDir)
Expand All @@ -40,6 +40,12 @@ kotlin {
}
}

tasks.withType(KotlinCompilation.class).configureEach {
compilerOptions {
freeCompilerArgs.add("-Xexpect-actual-classes")
}
}

apply from: "$rootDir/gradle/gradle-mvn-push.gradle"

tasks.named("dokkaHtmlMultiModule") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ package app.cash.sqldelight.driver.worker

import app.cash.sqldelight.db.SqlDriver

expect fun createWebWorkerDriver(): SqlDriver
expect fun createDefaultWebWorkerDriver(): SqlDriver
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package app.cash.sqldelight.driver.worker

import app.cash.sqldelight.Query
import app.cash.sqldelight.Transacter
import app.cash.sqldelight.db.QueryResult
import app.cash.sqldelight.db.SqlCursor
import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.db.SqlPreparedStatement
import app.cash.sqldelight.driver.worker.api.WorkerAction
import app.cash.sqldelight.driver.worker.api.WorkerActions
import app.cash.sqldelight.driver.worker.api.WorkerResultWithRowCount
import app.cash.sqldelight.driver.worker.api.WorkerWrapperRequest
import app.cash.sqldelight.driver.worker.expected.JsWorkerSqlCursor
import app.cash.sqldelight.driver.worker.expected.JsWorkerSqlPreparedStatement
import app.cash.sqldelight.driver.worker.expected.Worker
import app.cash.sqldelight.driver.worker.expected.checkWorkerResults

/**
* A [SqlDriver] implementation for interacting with SQL databases running in a Web Worker.
*
* This driver is dialect-agnostic and is instead dependent on the Worker script's implementation
* to handle queries and send results back from the Worker.
*
* @property worker The Worker running a SQL implementation that this driver communicates with.
* @see [WebWorkerDriver.fromScriptUrl]
*/
class WebWorkerDriver(private val worker: Worker) : SqlDriver {
private val listeners = mutableMapOf<String, MutableSet<Query.Listener>>()
private var messageCounter = 0
private var transaction: Transacter.Transaction? = null
private val wrapper = WorkerWrapper(worker)

override fun <R> executeQuery(
identifier: Int?,
sql: String,
mapper: (SqlCursor) -> QueryResult<R>,
parameters: Int,
binders: (SqlPreparedStatement.() -> Unit)?,
): QueryResult<R> {
val bound = JsWorkerSqlPreparedStatement()
binders?.invoke(bound)

return QueryResult.AsyncValue {
val response = wrapper.sendMessage(
action = WorkerActions.exec,
sql = sql,
statement = bound,
)

return@AsyncValue mapper(JsWorkerSqlCursor(checkWorkerResults(response.result))).await()
}
}

override fun execute(
identifier: Int?,
sql: String,
parameters: Int,
binders: (SqlPreparedStatement.() -> Unit)?,
): QueryResult<Long> {
val bound = JsWorkerSqlPreparedStatement()
binders?.invoke(bound)

return QueryResult.AsyncValue {
val response = wrapper.sendMessage(
action = WorkerActions.exec,
sql = sql,
statement = bound,
)
checkWorkerResults(response.result)
return@AsyncValue response.rowCount
}
}

override fun addListener(vararg queryKeys: String, listener: Query.Listener) {
queryKeys.forEach {
listeners.getOrPut(it) { mutableSetOf() }.add(listener)
}
}

override fun removeListener(vararg queryKeys: String, listener: Query.Listener) {
queryKeys.forEach {
listeners[it]?.remove(listener)
}
}

override fun notifyListeners(vararg queryKeys: String) {
queryKeys.flatMap { listeners[it].orEmpty() }
.distinct()
.forEach(Query.Listener::queryResultsChanged)
}

override fun close() = wrapper.terminate()

override fun newTransaction(): QueryResult<Transacter.Transaction> = QueryResult.AsyncValue {
val enclosing = transaction
val transaction = Transaction(enclosing)
this.transaction = transaction
if (enclosing == null) {
wrapper.sendMessage(WorkerActions.beginTransaction)
}

return@AsyncValue transaction
}

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

private inner class Transaction(
override val enclosingTransaction: Transacter.Transaction?,
) : Transacter.Transaction() {
override fun endTransaction(successful: Boolean): QueryResult<Unit> = QueryResult.AsyncValue {
if (enclosingTransaction == null) {
if (successful) {
wrapper.sendMessage(WorkerActions.endTransaction)
} else {
wrapper.sendMessage(WorkerActions.rollbackTransaction)
}
}
transaction = enclosingTransaction
}
}

private suspend fun WorkerWrapper.sendMessage(
action: WorkerAction,
sql: String? = null,
statement: JsWorkerSqlPreparedStatement? = null,
): WorkerResultWithRowCount {
val id = messageCounter++

println("beforeExecute")

return execute(
WorkerWrapperRequest(
id = id,
action = action,
sql = sql,
statement = statement,
),
)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package app.cash.sqldelight.driver.worker

import app.cash.sqldelight.driver.worker.api.WorkerResultWithRowCount
import app.cash.sqldelight.driver.worker.api.WorkerWrapperRequest
import app.cash.sqldelight.driver.worker.expected.Worker

internal expect class WorkerWrapper(worker: Worker) {
suspend fun execute(
request: WorkerWrapperRequest,
): WorkerResultWithRowCount

fun terminate()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package app.cash.sqldelight.driver.worker.api

internal expect interface WorkerResult
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package app.cash.sqldelight.driver.worker.api

internal interface WorkerResultWithRowCount {
val result: WorkerResult
val rowCount: Long
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package app.cash.sqldelight.driver.worker.api

import app.cash.sqldelight.driver.worker.expected.JsWorkerSqlPreparedStatement

/**
* Messages sent by the SQLDelight driver to the worker.
*/
internal data class WorkerWrapperRequest(
/**
* A unique identifier used to identify responses to this message
* @see WorkerResponse.id
*/
val id: Int,
/**
* The action that the worker should run.
* @see WorkerAction
*/
val action: WorkerAction,
/**
* The SQL to execute
*/
var sql: String?,

/**
* SQL parameters to bind to the given [sql]
*/
var statement: JsWorkerSqlPreparedStatement?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package app.cash.sqldelight.driver.worker.expected

import app.cash.sqldelight.driver.worker.api.WorkerResult

internal expect fun checkWorkerResults(results: WorkerResult?): WorkerResult
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package app.cash.sqldelight.driver.worker.expected

import app.cash.sqldelight.db.SqlCursor
import app.cash.sqldelight.driver.worker.api.WorkerResult

internal expect class JsWorkerSqlCursor(result: WorkerResult) : SqlCursor
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package app.cash.sqldelight.driver.worker.expected

import app.cash.sqldelight.db.SqlPreparedStatement

internal expect class JsWorkerSqlPreparedStatement() : SqlPreparedStatement
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package app.cash.sqldelight.driver.worker.expected

expect class Worker
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.db.SqlPreparedStatement
import app.cash.sqldelight.db.SqlSchema
import app.cash.sqldelight.driver.worker.WebWorkerException
import app.cash.sqldelight.driver.worker.createWebWorkerDriver
import app.cash.sqldelight.driver.worker.createDefaultWebWorkerDriver
import kotlin.test.Test
import kotlin.test.assertContains
import kotlin.test.assertEquals
Expand Down Expand Up @@ -62,7 +62,7 @@ class WebWorkerDriverTest {

private fun runTest(block: suspend (SqlDriver) -> Unit) = kotlinx.coroutines.test.runTest {
val driver =
createWebWorkerDriver()
createDefaultWebWorkerDriver()
.also { schema.awaitCreate(it) }
block(driver)
driver.close()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import app.cash.sqldelight.db.AfterVersion
import app.cash.sqldelight.db.QueryResult
import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.db.SqlSchema
import app.cash.sqldelight.driver.worker.createWebWorkerDriver
import app.cash.sqldelight.driver.worker.createDefaultWebWorkerDriver
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
Expand All @@ -28,10 +28,15 @@ class WebWorkerTransacterTest {

private fun runTest(block: suspend (SqlDriver, SuspendingTransacter) -> Unit) =
kotlinx.coroutines.test.runTest {
println("-2")
val driver =
createWebWorkerDriver()
.also { schema.awaitCreate(it) }
createDefaultWebWorkerDriver()
.also {
println("-1")
schema.awaitCreate(it)
}

println("0")
val transacter = object : SuspendingTransacterImpl(driver) {}
block(driver, transacter)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ package app.cash.sqldelight.driver.worker
import app.cash.sqldelight.db.SqlDriver
import org.w3c.dom.Worker

actual fun createWebWorkerDriver(): SqlDriver {
actual fun createDefaultWebWorkerDriver(): SqlDriver {
return WebWorkerDriver(Worker(js("""new URL("@cashapp/sqldelight-sqljs-worker/sqljs.worker.js", import.meta.url)""")))
}
Loading

0 comments on commit ece537a

Please sign in to comment.