Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ Pods/
dialect/bin
.build
.vscode
kotlin-js-store
21 changes: 17 additions & 4 deletions common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ kotlin {
powersyncTargets(
android = {
namespace = "com.powersync.common"
}
},
web = true
)

targets.withType<KotlinNativeTarget> {
Expand Down Expand Up @@ -195,8 +196,17 @@ kotlin {
dependsOn(commonTest.get())
}

val commonJava by creating {
val commonNonWeb by creating {
dependsOn(commonMain.get())

dependencies {
implementation(libs.rsocket.core)
implementation(libs.rsocket.transport.websocket)
}
}

val commonJava by creating {
dependsOn(commonNonWeb)
}

commonMain.configure {
Expand All @@ -206,6 +216,7 @@ kotlin {

dependencies {
api(libs.androidx.sqlite.sqlite)
implementation(libs.androidx.sqlite.async)

implementation(libs.uuid)
implementation(libs.kotlin.stdlib)
Expand All @@ -215,8 +226,6 @@ kotlin {
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.datetime)
implementation(libs.stately.concurrency)
implementation(libs.rsocket.core)
implementation(libs.rsocket.transport.websocket)
api(libs.ktor.client.core)
api(libs.kermit)
}
Expand All @@ -233,6 +242,10 @@ kotlin {
dependsOn(commonJava)
}

nativeMain {
dependsOn(commonNonWeb)
}

commonTest.dependencies {
implementation(projects.internal.testutils)
implementation(libs.kotlin.test)
Expand Down
30 changes: 0 additions & 30 deletions common/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,15 @@ import com.powersync.db.Queries
import com.powersync.db.crud.CrudBatch
import com.powersync.db.crud.CrudTransaction
import com.powersync.db.driver.SQLiteConnectionPool
import com.powersync.db.driver.SingleConnectionPool
import com.powersync.db.schema.Schema
import com.powersync.sync.SyncOptions
import com.powersync.sync.SyncStatus
import com.powersync.sync.SyncStream
import com.powersync.utils.JsonParam
import com.powersync.utils.generateLogger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlin.coroutines.cancellation.CancellationException
import kotlin.native.HiddenFromObjC

/**
* A PowerSync managed database.
Expand Down Expand Up @@ -265,33 +262,6 @@ public interface PowerSyncDatabase : Queries {
return openedWithGroup(pool, scope, schema, logger, group)
}

/**
* Creates a PowerSync database backed by a single in-memory database connection opened from the
* [InMemoryConnectionFactory].
*
* This can be useful for writing tests relying on PowerSync databases.
*/
public fun openInMemory(
factory: InMemoryConnectionFactory,
schema: Schema,
scope: CoroutineScope,
logger: Logger? = null,
): PowerSyncDatabase {
val logger = generateLogger(logger)
// Since this returns a fresh in-memory database every time, use a fresh group to avoid warnings about the
// same database being opened multiple times.
val collection =
ActiveDatabaseGroup.GroupsCollection().referenceDatabase(logger, "test")

return openedWithGroup(
SingleConnectionPool(factory.openInMemoryConnection()),
scope,
schema,
logger,
collection,
)
}

internal fun openedWithGroup(
pool: SQLiteConnectionPool,
scope: CoroutineScope,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ internal interface BucketStorage {

suspend fun nextCrudItem(): CrudEntry?

fun nextCrudItem(transaction: PowerSyncTransaction): CrudEntry?
suspend fun nextCrudItem(transaction: PowerSyncTransaction): CrudEntry?

suspend fun hasCrud(): Boolean

fun hasCrud(transaction: PowerSyncTransaction): Boolean
suspend fun hasCrud(transaction: PowerSyncTransaction): Boolean

fun mapCrudEntry(row: SqlCursor): CrudEntry

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ internal class BucketStorageImpl(

override suspend fun nextCrudItem(): CrudEntry? = db.getOptional(sql = nextCrudQuery, mapper = ::mapCrudEntry)

override fun nextCrudItem(transaction: PowerSyncTransaction): CrudEntry? =
transaction.getOptional(sql = nextCrudQuery, mapper = ::mapCrudEntry)
override suspend fun nextCrudItem(transaction: PowerSyncTransaction): CrudEntry? =
transaction.getOptionalAsync(sql = nextCrudQuery, mapper = ::mapCrudEntry)

private val nextCrudQuery = "SELECT id, tx_id, data FROM ${InternalTable.CRUD} ORDER BY id ASC LIMIT 1"

Expand All @@ -52,8 +52,8 @@ internal class BucketStorageImpl(
return res == 1L
}

override fun hasCrud(transaction: PowerSyncTransaction): Boolean {
val res = transaction.getOptional(sql = hasCrudQuery, mapper = hasCrudMapper)
override suspend fun hasCrud(transaction: PowerSyncTransaction): Boolean {
val res = transaction.getOptionalAsync(sql = hasCrudQuery, mapper = hasCrudMapper)
return res == 1L
}

Expand Down Expand Up @@ -81,14 +81,14 @@ internal class BucketStorageImpl(

logger.i { "[updateLocalTarget] Updating target to checkpoint $opId" }

return db.writeTransaction { tx ->
return db.writeTransactionAsync { tx ->
if (hasCrud(tx)) {
logger.w { "[updateLocalTarget] ps crud is not empty" }
return@writeTransaction false
return@writeTransactionAsync false
}

val seqAfter =
tx.getOptional("SELECT seq FROM main.sqlite_sequence WHERE name = '${InternalTable.CRUD}'") {
tx.getOptionalAsync("SELECT seq FROM main.sqlite_sequence WHERE name = '${InternalTable.CRUD}'") {
it.getLong(0)!!
}
?: // assert isNotEmpty
Expand All @@ -97,15 +97,15 @@ internal class BucketStorageImpl(
if (seqAfter != seqBefore) {
logger.d("seqAfter != seqBefore seqAfter: $seqAfter seqBefore: $seqBefore")
// New crud data may have been uploaded since we got the checkpoint. Abort.
return@writeTransaction false
return@writeTransactionAsync false
}

tx.execute(
tx.executeAsync(
"UPDATE ${InternalTable.BUCKETS} SET target_op = CAST(? as INTEGER) WHERE name='\$local'",
listOf(opId),
)

return@writeTransaction true
return@writeTransactionAsync true
}
}

Expand Down Expand Up @@ -138,10 +138,10 @@ internal class BucketStorageImpl(
}

override suspend fun control(args: PowerSyncControlArguments): List<Instruction> =
db.writeTransaction { tx ->
db.writeTransactionAsync { tx ->
logger.v { "powersync_control: $args" }

val (op: String, data: Any?) = args.sqlArguments
tx.get("SELECT powersync_control(?, ?) AS r", listOf(op, data), ::handleControlResult)
tx.getAsync("SELECT powersync_control(?, ?) AS r", listOf(op, data), ::handleControlResult)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import com.powersync.db.crud.CrudRow
import com.powersync.db.crud.CrudTransaction
import com.powersync.db.driver.SQLiteConnectionLease
import com.powersync.db.driver.SQLiteConnectionPool
import com.powersync.db.internal.ConnectionContext
import com.powersync.db.internal.InternalDatabaseImpl
import com.powersync.db.internal.InternalTable
import com.powersync.db.internal.PowerSyncTransaction
import com.powersync.db.internal.PowerSyncVersion
import com.powersync.db.schema.Schema
import com.powersync.sync.CoreSyncStatus
Expand Down Expand Up @@ -111,8 +113,8 @@ internal class PowerSyncDatabaseImpl(
checkVersion(powerSyncVersion)
logger.d { "PowerSyncVersion: $powerSyncVersion" }

internalDb.writeTransaction { tx ->
tx.getOptional("SELECT powersync_init()") {}
internalDb.writeTransactionAsync { tx ->
tx.getOptionalAsync("SELECT powersync_init()") {}
}

updateSchemaInternal(schema)
Expand Down Expand Up @@ -393,26 +395,6 @@ internal class PowerSyncDatabaseImpl(
emitAll(internalDb.watch(sql, parameters, throttleMs, mapper))
}

override suspend fun <R> readLock(callback: ThrowableLockCallback<R>): R {
waitReady()
return internalDb.readLock(callback)
}

override suspend fun <R> readTransaction(callback: ThrowableTransactionCallback<R>): R {
waitReady()
return internalDb.writeTransaction(callback)
}

override suspend fun <R> writeLock(callback: ThrowableLockCallback<R>): R {
waitReady()
return internalDb.writeLock(callback)
}

override suspend fun <R> writeTransaction(callback: ThrowableTransactionCallback<R>): R {
waitReady()
return internalDb.writeTransaction(callback)
}

override suspend fun execute(
sql: String,
parameters: List<Any?>?,
Expand All @@ -425,19 +407,19 @@ internal class PowerSyncDatabaseImpl(
lastTransactionId: Int,
writeCheckpoint: String?,
) {
internalDb.writeTransaction { transaction ->
transaction.execute(
internalDb.writeTransactionAsync { transaction ->
transaction.executeAsync(
"DELETE FROM ps_crud WHERE id <= ?",
listOf(lastTransactionId.toLong()),
)

if (writeCheckpoint != null && !bucketStorage.hasCrud(transaction)) {
transaction.execute(
transaction.executeAsync(
"UPDATE ps_buckets SET target_op = CAST(? AS INTEGER) WHERE name='\$local'",
listOf(writeCheckpoint),
)
} else {
transaction.execute(
transaction.executeAsync(
"UPDATE ps_buckets SET target_op = CAST(? AS INTEGER) WHERE name='\$local'",
listOf(bucketStorage.getMaxOpId()),
)
Expand Down Expand Up @@ -483,8 +465,8 @@ internal class PowerSyncDatabaseImpl(
flags = flags or 2 // MASK_SOFT_CLEAR
}

internalDb.writeTransaction { tx ->
tx.getOptional("SELECT powersync_clear(?)", listOf(flags)) {}
internalDb.writeTransactionAsync { tx ->
tx.getOptionalAsync("SELECT powersync_clear(?)", listOf(flags)) {}
}
currentStatus.update { copy(lastSyncedAt = null, hasSynced = false) }
}
Expand Down
50 changes: 42 additions & 8 deletions common/src/commonMain/kotlin/com/powersync/db/Queries.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import com.powersync.PowerSyncException
import com.powersync.db.driver.SQLiteConnectionLease
import com.powersync.db.internal.ConnectionContext
import com.powersync.db.internal.PowerSyncTransaction
import com.powersync.db.internal.asContext
import com.powersync.db.internal.runTransaction
import kotlinx.coroutines.flow.Flow
import kotlin.coroutines.cancellation.CancellationException
import kotlin.native.HiddenFromObjC
Expand Down Expand Up @@ -42,7 +44,7 @@ public interface Queries {
public suspend fun execute(
sql: String,
parameters: List<Any?>? = listOf(),
): Long
): Long = writeLockAsync { connection -> connection.executeAsync(sql, parameters) }

/**
* Executes a read-only (SELECT) query and returns a single result.
Expand All @@ -59,7 +61,7 @@ public interface Queries {
sql: String,
parameters: List<Any?>? = listOf(),
mapper: (SqlCursor) -> RowType,
): RowType
): RowType = readLockAsync { connection -> connection.getAsync(sql, parameters, mapper) }

/**
* Executes a read-only (SELECT) query and returns all results.
Expand All @@ -76,7 +78,7 @@ public interface Queries {
sql: String,
parameters: List<Any?>? = listOf(),
mapper: (SqlCursor) -> RowType,
): List<RowType>
): List<RowType> = readLockAsync { connection -> connection.getAllAsync(sql, parameters, mapper) }

/**
* Executes a read-only (SELECT) query and returns a single optional result.
Expand All @@ -93,7 +95,7 @@ public interface Queries {
sql: String,
parameters: List<Any?>? = listOf(),
mapper: (SqlCursor) -> RowType,
): RowType?
): RowType? = readLockAsync { connection -> connection.getOptionalAsync(sql, parameters, mapper) }

/**
* Returns a [Flow] that emits whenever the source tables are modified.
Expand Down Expand Up @@ -144,7 +146,15 @@ public interface Queries {
* @throws CancellationException If the operation is cancelled.
*/
@Throws(PowerSyncException::class, CancellationException::class)
public suspend fun <R> writeLock(callback: ThrowableLockCallback<R>): R
public suspend fun <R> writeLock(callback: ThrowableLockCallback<R>): R =
useConnection(readOnly = false) { connection ->
callback.execute(connection.asContext())
}

public suspend fun <R> writeLockAsync(callback: suspend (tx: ConnectionContext) -> R): R =
useConnection(readOnly = false) { connection ->
callback(connection.asContext())
}

/**
* Opens a read-write transaction.
Expand All @@ -159,7 +169,15 @@ public interface Queries {
* @throws CancellationException If the operation is cancelled.
*/
@Throws(PowerSyncException::class, CancellationException::class)
public suspend fun <R> writeTransaction(callback: ThrowableTransactionCallback<R>): R
public suspend fun <R> writeTransaction(callback: ThrowableTransactionCallback<R>): R =
useConnection(readOnly = false) { connection ->
connection.runTransaction { callback.execute(it) }
}

public suspend fun <R> writeTransactionAsync(callback: suspend (tx: PowerSyncTransaction) -> R): R =
useConnection(readOnly = false) { connection ->
connection.runTransaction { callback(it) }
}

/**
* Takes a read lock without starting a transaction.
Expand All @@ -172,7 +190,15 @@ public interface Queries {
* @throws CancellationException If the operation is cancelled.
*/
@Throws(PowerSyncException::class, CancellationException::class)
public suspend fun <R> readLock(callback: ThrowableLockCallback<R>): R
public suspend fun <R> readLock(callback: ThrowableLockCallback<R>): R =
useConnection(readOnly = true) { connection ->
callback.execute(connection.asContext())
}

public suspend fun <R> readLockAsync(callback: suspend (tx: ConnectionContext) -> R): R =
useConnection(readOnly = true) { connection ->
callback(connection.asContext())
}

/**
* Opens a read-only transaction.
Expand All @@ -185,7 +211,15 @@ public interface Queries {
* @throws CancellationException If the operation is cancelled.
*/
@Throws(PowerSyncException::class, CancellationException::class)
public suspend fun <R> readTransaction(callback: ThrowableTransactionCallback<R>): R
public suspend fun <R> readTransaction(callback: ThrowableTransactionCallback<R>): R =
useConnection(readOnly = true) { connection ->
connection.runTransaction { callback.execute(it) }
}

public suspend fun <R> readTransactionAsync(callback: suspend (tx: PowerSyncTransaction) -> R): R =
useConnection(readOnly = true) { connection ->
connection.runTransaction { callback(it) }
}

/**
* Obtains a connection from the read pool or an exclusive reference on the write connection.
Expand Down
Loading
Loading