Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
136e809
New unified driver interfaces
simolus3 Jul 22, 2025
435c7c5
Fix leaking statements
simolus3 Jul 24, 2025
522f4c6
Add raw connection API
simolus3 Jul 25, 2025
2359b48
Add changelog entry
simolus3 Jul 25, 2025
f4d98d5
Lease API that works better with Room
simolus3 Jul 25, 2025
eccb7f7
Notify updates from raw statements
simolus3 Jul 25, 2025
54829bc
Delete more driver stuff
simolus3 Aug 22, 2025
f532ba9
Fix deadlock in initialization
simolus3 Aug 22, 2025
c7adbab
Make addPowerSyncExtension public
simolus3 Aug 22, 2025
011b1da
Actually, use callbacks
simolus3 Aug 22, 2025
c87e079
merge main
simolus3 Sep 2, 2025
cb1373d
Add docs
simolus3 Sep 2, 2025
aa4e7bc
Fix lints
simolus3 Sep 2, 2025
03796e7
Add native sqlite driver
simolus3 Sep 5, 2025
a0682c2
Bring back static sqlite linking
simolus3 Sep 5, 2025
8f5f8cd
Fix linter errors
simolus3 Sep 5, 2025
f41b0a4
Fix Swift tests
simolus3 Sep 5, 2025
51521d8
Delete proguard rules
simolus3 Sep 5, 2025
fd04adc
grdb drivers
stevensJourney Sep 7, 2025
b32f7bf
wip: lease all connections
stevensJourney Sep 7, 2025
9008648
revert databasegroup changes.
stevensJourney Sep 8, 2025
d6697d9
Merge branch 'main' into grdb-drivers
stevensJourney Sep 16, 2025
a92930a
update after merging
stevensJourney Sep 16, 2025
ef4160c
revert test change
stevensJourney Sep 16, 2025
068d8ed
Merge remote-tracking branch 'origin/main' into grdb-drivers
stevensJourney Sep 19, 2025
c0bdde9
improve error handling
stevensJourney Sep 23, 2025
937d452
Use SQLite Session API for Swift updates.
stevensJourney Sep 25, 2025
59408b0
Code cleanup. Fix lint error.
stevensJourney Sep 25, 2025
07267c1
Merge remote-tracking branch 'origin/main' into grdb-drivers
stevensJourney Sep 25, 2025
587934c
cleanup APIs for sessions
stevensJourney Sep 29, 2025
5434a5f
Merge remote-tracking branch 'origin/main' into grdb-drivers
stevensJourney Oct 3, 2025
9f855e4
move Swift pool logic
stevensJourney Oct 3, 2025
11add5c
Add changelog entry
stevensJourney Oct 3, 2025
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: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

- Add `PowerSyncDatabase.inMemory` to create an in-memory SQLite database with PowerSync.
This may be useful for testing.
- The Supabase connector can now be subclassed to customize how rows are uploaded and how errors are handled.
- The Supabase connector can now be subclassed to customize how rows are uploaded and how errors are
handled.
- Experimental support for sync streams.
- [Swift] Added helpers for creating Swift SQLite connection pools.

## 1.6.1

Expand Down
2 changes: 1 addition & 1 deletion core/src/nativeMain/interop/sqlite3.def
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
headers = sqlite3.h

noStringConversion = sqlite3_prepare_v3
noStringConversion = sqlite3_prepare_v3,sqlite3session_create
71 changes: 69 additions & 2 deletions core/src/nativeMain/interop/sqlite3.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,49 @@

typedef struct sqlite3 sqlite3;
typedef struct sqlite3_stmt sqlite3_stmt;
typedef struct sqlite3_session sqlite3_session;
typedef struct sqlite3_changeset_iter sqlite3_changeset_iter;

int sqlite3_initialize();

int sqlite3_open_v2(char *filename, sqlite3 **ppDb, int flags,
char *zVfs);

int sqlite3_close_v2(sqlite3 *db);

// Error handling
int sqlite3_extended_result_codes(sqlite3 *db, int onoff);

int sqlite3_extended_errcode(sqlite3 *db);

char *sqlite3_errmsg(sqlite3 *db);

char *sqlite3_errstr(int code);

int sqlite3_error_offset(sqlite3 *db);

void sqlite3_free(void *ptr);

// Versions
char *sqlite3_libversion();

char *sqlite3_sourceid();

int sqlite3_libversion_number();

// Database
int sqlite3_get_autocommit(sqlite3 *db);

int sqlite3_db_config(sqlite3 *db, int op, ...);

int sqlite3_load_extension(
sqlite3 *db, /* Load the extension into this database connection */
const char *zFile, /* Name of the shared library containing extension */
const char *zProc, /* Entry point. Derived from zFile if 0 */
char **pzErrMsg /* Put error message here if not 0 */
);
int sqlite3_extended_result_codes(sqlite3*, int onoff);

int sqlite3_extended_result_codes(sqlite3 *, int onoff);

// Statements
int sqlite3_prepare16_v3(
Expand All @@ -43,27 +56,81 @@ int sqlite3_prepare16_v3(
sqlite3_stmt **ppStmt, /* OUT: Statement handle */
const void **pzTail /* OUT: Pointer to unused portion of zSql */
);

int sqlite3_finalize(sqlite3_stmt *pStmt);

int sqlite3_step(sqlite3_stmt *pStmt);

int sqlite3_reset(sqlite3_stmt *pStmt);
int sqlite3_clear_bindings(sqlite3_stmt*);

int sqlite3_clear_bindings(sqlite3_stmt *);

int sqlite3_column_count(sqlite3_stmt *pStmt);

int sqlite3_bind_parameter_count(sqlite3_stmt *pStmt);

char *sqlite3_column_name(sqlite3_stmt *pStmt, int N);

int sqlite3_bind_blob64(sqlite3_stmt *pStmt, int index, void *data,
uint64_t length, void *destructor);

int sqlite3_bind_double(sqlite3_stmt *pStmt, int index, double data);

int sqlite3_bind_int64(sqlite3_stmt *pStmt, int index, int64_t data);

int sqlite3_bind_null(sqlite3_stmt *pStmt, int index);

int sqlite3_bind_text16(sqlite3_stmt *pStmt, int index, char *data,
int length, void *destructor);

void *sqlite3_column_blob(sqlite3_stmt *pStmt, int iCol);

double sqlite3_column_double(sqlite3_stmt *pStmt, int iCol);

int64_t sqlite3_column_int64(sqlite3_stmt *pStmt, int iCol);

void *sqlite3_column_text16(sqlite3_stmt *pStmt, int iCol);

int sqlite3_column_bytes(sqlite3_stmt *pStmt, int iCol);

int sqlite3_column_bytes16(sqlite3_stmt *pStmt, int iCol);

int sqlite3_column_type(sqlite3_stmt *pStmt, int iCol);


int sqlite3session_create(
sqlite3 *db, /* Database handle */
const char *zDb, /* Name of db (e.g. "main") */
sqlite3_session **ppSession /* OUT: New session object */
);

int sqlite3session_attach(
sqlite3_session *pSession, /* Session object */
const char *zTab /* Table name or NULL for all */
);

int sqlite3session_changeset(
sqlite3_session *pSession, /* Session object */
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
void **ppChangeset /* OUT: Buffer containing changeset */
);

int sqlite3changeset_start(
sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */
int nChangeset, /* Size of changeset blob in bytes */
void *pChangeset /* Pointer to blob containing changeset */
);

int sqlite3changeset_op(
sqlite3_changeset_iter *pIter, /* Iterator object */
const char **pzTab, /* OUT: Pointer to table name */
int *pnCol, /* OUT: Number of columns in table */
int *pOp, /* OUT: SQLITE_INSERT, DELETE or UPDATE */
int *pbIndirect /* OUT: True for an 'indirect' change */
);

int sqlite3changeset_next(sqlite3_changeset_iter *pIter);

int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter);

void sqlite3session_delete(sqlite3_session *pSession);
33 changes: 19 additions & 14 deletions core/src/nativeMain/kotlin/com/powersync/sqlite/Database.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import kotlinx.cinterop.value
* [com.powersync.db.driver.InternalConnectionPool] and called from [kotlinx.coroutines.Dispatchers.IO]
* to make these APIs asynchronous.
*/
internal class Database(
public class Database(
private val ptr: CPointer<sqlite3>,
) : SQLiteConnection {
override fun inTransaction(): Boolean {
Expand All @@ -52,22 +52,27 @@ internal class Database(
Statement(sql, ptr, stmtPtr.value!!)
}

fun loadExtension(
internal fun loadExtension(
filename: String,
entrypoint: String,
) = memScoped {
val errorMessagePointer = alloc<CPointerVar<ByteVar>>()
val resultCode = sqlite3_load_extension(ptr, filename, entrypoint, errorMessagePointer.ptr)

if (resultCode != 0) {
val errorMessage = errorMessagePointer.value?.toKStringFromUtf8()
if (errorMessage != null) {
sqlite3_free(errorMessagePointer.value)
}
): Unit =
memScoped {
val errorMessagePointer = alloc<CPointerVar<ByteVar>>()
val resultCode =
sqlite3_load_extension(ptr, filename, entrypoint, errorMessagePointer.ptr)

if (resultCode != 0) {
val errorMessage = errorMessagePointer.value?.toKStringFromUtf8()
if (errorMessage != null) {
sqlite3_free(errorMessagePointer.value)
}

throw PowerSyncException("Could not load extension ($resultCode): ${errorMessage ?: "unknown error"}", null)
throw PowerSyncException(
"Could not load extension ($resultCode): ${errorMessage ?: "unknown error"}",
null,
)
}
}
}

override fun close() {
sqlite3_close_v2(ptr)
Expand All @@ -79,7 +84,7 @@ internal class Database(
}
}

companion object {
internal companion object {
fun open(
path: String,
flags: Int,
Expand Down
7 changes: 7 additions & 0 deletions internal/PowerSyncKotlin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ kotlin {
api(project(":core"))
implementation(libs.ktor.client.logging)
}

all {
languageSettings {
optIn("kotlinx.cinterop.ExperimentalForeignApi")
optIn("com.powersync.ExperimentalPowerSyncAPI")
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.powersync.pool

import androidx.sqlite.SQLiteStatement
import com.powersync.db.driver.SQLiteConnectionLease
import com.powersync.sqlite.Database

internal class RawConnectionLease(
lease: SwiftLeaseAdapter,
) : SQLiteConnectionLease {
private var isCompleted = false

private var db = Database(lease.pointer)

private fun checkNotCompleted() {
check(!isCompleted) { "Connection lease already closed" }
}

override suspend fun isInTransaction(): Boolean = isInTransactionSync()

override fun isInTransactionSync(): Boolean {
checkNotCompleted()
return db.inTransaction()
}

override suspend fun <R> usePrepared(
sql: String,
block: (SQLiteStatement) -> R,
): R = usePreparedSync(sql, block)

override fun <R> usePreparedSync(
sql: String,
block: (SQLiteStatement) -> R,
): R {
checkNotCompleted()
return db.prepare(sql).use(block)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.powersync.pool

import cnames.structs.sqlite3
import com.powersync.PowerSyncException
import com.powersync.sqlite.Database
import io.ktor.utils.io.CancellationException
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.ExperimentalForeignApi

/**
* The Swift lease will provide a SQLite connection pointer (sqlite3*) which is used in a [Database]
*/
public interface SwiftLeaseAdapter {
public val pointer: CPointer<sqlite3>
}

/**
* A small functional interface to provide a callback with a leased connection.
* We use this structure in order to annotate the callback with exceptions that can be thrown.
*/
public fun interface LeaseCallback {
@Throws(PowerSyncException::class, CancellationException::class)
public fun execute(lease: SwiftLeaseAdapter)
}

/**
* A small functional interface to provide a callback leases to all connections.
* We use this structure in order to annotate the callback with exceptions that can be thrown.
*/
public fun interface AllLeaseCallback {
@Throws(PowerSyncException::class, CancellationException::class)
public fun execute(
writeLease: SwiftLeaseAdapter,
readLeases: List<SwiftLeaseAdapter>,
)
}

/**
* We only allow synchronous callbacks on the Swift side for leased READ/WRITE connections.
* This adapter here uses synchronous callbacks.
* We also get a SQLite connection pointer (sqlite3*) from Swift side. which is used in a [Database]
* The adapter structure here is focused around easily integrating with a Swift Pool over SKIEE.
*/
public interface SwiftPoolAdapter {
@Throws(PowerSyncException::class, CancellationException::class)
public suspend fun leaseRead(callback: LeaseCallback)

@Throws(PowerSyncException::class, CancellationException::class)
public suspend fun leaseWrite(callback: LeaseCallback)

@Throws(PowerSyncException::class, CancellationException::class)
public suspend fun leaseAll(callback: AllLeaseCallback)

/**
* Links updates from external mutations to PowerSync.
*/
public fun linkExternalUpdates(callback: suspend (Set<String>) -> Unit)

/**
* Dispose any associated resources with the Pool and PowerSync.
* We don't manage the lifecycle of the pool.
*/
public suspend fun dispose()
}
Loading
Loading