Skip to content

Commit e15540a

Browse files
chore: improve watch query (#120)
* chore: improve watch query * test * remove false comment * test watched queries * use combine * fix debounce * zip flows * fix watched queries * chore: change execute * fix: index issue --------- Co-authored-by: stevensJourney <[email protected]>
1 parent 1a0bee7 commit e15540a

File tree

8 files changed

+201
-104
lines changed

8 files changed

+201
-104
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ let package = Package(
1717
targets: [
1818
.binaryTarget(
1919
name: packageName,
20-
path: "./PowerSyncKotlin/build/XCFrameworks/debug/PowerSyncKotlin.xcframework"
20+
path: "./PowerSyncKotlin/build/XCFrameworks/debug/\(packageName).xcframework"
2121
)
2222
,
2323
]

core/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ public actual class DatabaseDriverFactory(
2424
@Suppress("unused")
2525
private fun onTransactionCommit(success: Boolean) {
2626
driver?.also { driver ->
27-
if (success) {
28-
driver.fireTableUpdates()
29-
} else {
27+
// Only clear updates if a rollback happened
28+
// We manually fire updates when transactions are completed
29+
if (!success) {
3030
driver.clearTableUpdates()
3131
}
3232
}
@@ -42,38 +42,38 @@ public actual class DatabaseDriverFactory(
4242
PsSqlDriver(
4343
scope = scope,
4444
driver =
45-
AndroidSqliteDriver(
46-
context = context,
47-
schema = schema,
48-
name = dbFilename,
49-
factory =
50-
RequerySQLiteOpenHelperFactory(
51-
listOf(
52-
RequerySQLiteOpenHelperFactory.ConfigurationOptions { config ->
53-
config.customExtensions.add(
54-
SQLiteCustomExtension(
55-
"libpowersync",
56-
"sqlite3_powersync_init",
57-
),
58-
)
59-
config.customExtensions.add(
60-
SQLiteCustomExtension(
61-
"libpowersync-sqlite",
62-
"powersync_init",
63-
),
64-
)
65-
config
66-
},
67-
),
68-
),
69-
callback =
70-
object : AndroidSqliteDriver.Callback(schema) {
71-
override fun onConfigure(db: SupportSQLiteDatabase) {
72-
db.enableWriteAheadLogging()
73-
super.onConfigure(db)
74-
}
45+
AndroidSqliteDriver(
46+
context = context,
47+
schema = schema,
48+
name = dbFilename,
49+
factory =
50+
RequerySQLiteOpenHelperFactory(
51+
listOf(
52+
RequerySQLiteOpenHelperFactory.ConfigurationOptions { config ->
53+
config.customExtensions.add(
54+
SQLiteCustomExtension(
55+
"libpowersync",
56+
"sqlite3_powersync_init",
57+
),
58+
)
59+
config.customExtensions.add(
60+
SQLiteCustomExtension(
61+
"libpowersync-sqlite",
62+
"powersync_init",
63+
),
64+
)
65+
config
7566
},
67+
),
7668
),
69+
callback =
70+
object : AndroidSqliteDriver.Callback(schema) {
71+
override fun onConfigure(db: SupportSQLiteDatabase) {
72+
db.enableWriteAheadLogging()
73+
super.onConfigure(db)
74+
}
75+
},
76+
),
7777
)
7878
setupSqliteBinding()
7979
return this.driver as PsSqlDriver

core/src/commonMain/kotlin/com/powersync/db/internal/InternalDatabaseImpl.kt

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ import kotlinx.coroutines.FlowPreview
2020
import kotlinx.coroutines.IO
2121
import kotlinx.coroutines.flow.Flow
2222
import kotlinx.coroutines.flow.debounce
23+
import kotlinx.coroutines.flow.filter
2324
import kotlinx.coroutines.flow.onEach
2425
import kotlinx.coroutines.launch
26+
import kotlinx.coroutines.sync.Mutex
27+
import kotlinx.coroutines.sync.withLock
2528
import kotlinx.coroutines.withContext
2629
import kotlinx.serialization.encodeToString
2730

@@ -33,6 +36,12 @@ internal class InternalDatabaseImpl(
3336
override val transactor: PsDatabase = PsDatabase(driver)
3437
override val queries: PowersyncQueries = transactor.powersyncQueries
3538

39+
// Register callback for table updates
40+
private fun tableUpdates(): Flow<List<String>> = driver.tableUpdates()
41+
42+
// Debounced by transaction completion
43+
private val tableUpdatesMutex = Mutex()
44+
3645
// Could be scope.coroutineContext, but the default is GlobalScope, which seems like a bad idea. To discuss.
3746
private val dbContext = Dispatchers.IO
3847
private val transaction =
@@ -62,30 +71,44 @@ internal class InternalDatabaseImpl(
6271
}
6372

6473
companion object {
65-
const val POWERSYNC_TABLE_MATCH: String = "(^ps_data__|^ps_data_local__)"
66-
const val DEFAULT_WATCH_THROTTLE_MS: Long = 30L
74+
const val POWERSYNC_TABLE_MATCH = "(^ps_data__|^ps_data_local__)"
75+
const val DEFAULT_WATCH_THROTTLE_MS = 30L
6776
}
6877

6978
init {
7079
scope.launch {
7180
val accumulatedUpdates = mutableSetOf<String>()
81+
// Store table changes in an accumulated array which will be (debounced) emitted on transaction end
7282
tableUpdates()
73-
// Debounce will discard any events which occur inside the debounce window
74-
// This will accumulate those table updates
75-
.onEach { tables -> accumulatedUpdates.addAll(tables) }
83+
.onEach { tables ->
84+
val dataTables =
85+
tables
86+
.map { toFriendlyTableName(it) }
87+
.filter { it.isNotBlank() }
88+
tableUpdatesMutex.withLock {
89+
accumulatedUpdates.addAll(dataTables)
90+
}
91+
}
92+
// debounce ignores events inside the throttle. Debouncing needs to be done after accumulation
7693
.debounce(DEFAULT_WATCH_THROTTLE_MS)
77-
.collect {
78-
val dataTables = accumulatedUpdates.map { toFriendlyTableName(it) }.filter { it.isNotBlank() }
79-
driver.notifyListeners(queryKeys = dataTables.toTypedArray())
80-
accumulatedUpdates.clear()
94+
.collect { _ ->
95+
tableUpdatesMutex.withLock {
96+
driver.notifyListeners(queryKeys = accumulatedUpdates.toTypedArray())
97+
accumulatedUpdates.clear()
98+
}
8199
}
82100
}
83101
}
84102

85103
override suspend fun execute(
86104
sql: String,
87105
parameters: List<Any?>?,
88-
): Long = withContext(dbContext) { executeSync(sql, parameters) }
106+
): Long =
107+
withContext(dbContext) {
108+
val r = executeSync(sql, parameters)
109+
driver.fireTableUpdates()
110+
r
111+
}
89112

90113
private fun executeSync(
91114
sql: String,
@@ -233,20 +256,21 @@ internal class InternalDatabaseImpl(
233256

234257
override suspend fun <R> writeTransaction(callback: ThrowableTransactionCallback<R>): R =
235258
withContext(dbContext) {
236-
transactor.transactionWithResult(noEnclosing = true) {
237-
runWrapped {
238-
val result = callback.execute(transaction)
239-
if (result is PowerSyncException) {
240-
throw result
259+
val r =
260+
transactor.transactionWithResult(noEnclosing = true) {
261+
runWrapped {
262+
val result = callback.execute(transaction)
263+
if (result is PowerSyncException) {
264+
throw result
265+
}
266+
result
241267
}
242-
result
243268
}
244-
}
269+
// Trigger watched queries
270+
driver.fireTableUpdates()
271+
r
245272
}
246273

247-
// Register callback for table updates
248-
private fun tableUpdates(): Flow<List<String>> = driver.tableUpdates()
249-
250274
// Register callback for table updates on a specific table
251275
override fun updatesOnTable(tableName: String): Flow<Unit> = driver.updatesOnTable(tableName)
252276

core/src/iosMain/kotlin/com/powersync/DatabaseDriverFactory.ios.kt

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ public actual class DatabaseDriverFactory {
4040

4141
private fun onTransactionCommit(success: Boolean) {
4242
driver?.also { driver ->
43-
if (success) {
44-
driver.fireTableUpdates()
45-
} else {
43+
// Only clear updates on rollback
44+
// We manually fire updates when a transaction ended
45+
if (!success) {
4646
driver.clearTableUpdates()
4747
}
4848
}
@@ -63,7 +63,8 @@ public actual class DatabaseDriverFactory {
6363
override fun eWrite(
6464
message: String,
6565
exception: Throwable?,
66-
) {}
66+
) {
67+
}
6768

6869
override fun trace(message: String) {}
6970

@@ -74,27 +75,27 @@ public actual class DatabaseDriverFactory {
7475
PsSqlDriver(
7576
scope = scope,
7677
driver =
77-
NativeSqliteDriver(
78-
configuration =
79-
DatabaseConfiguration(
80-
name = dbFilename,
81-
version = schema.version.toInt(),
82-
create = { connection -> wrapConnection(connection) { schema.create(it) } },
83-
loggingConfig = Logging(logger = sqlLogger),
84-
lifecycleConfig =
85-
DatabaseConfiguration.Lifecycle(
86-
onCreateConnection = { connection ->
87-
setupSqliteBinding(connection)
88-
wrapConnection(connection) { driver ->
89-
schema.create(driver)
90-
}
91-
},
92-
onCloseConnection = { connection ->
93-
deregisterSqliteBinding(connection)
94-
},
95-
),
96-
),
78+
NativeSqliteDriver(
79+
configuration =
80+
DatabaseConfiguration(
81+
name = dbFilename,
82+
version = schema.version.toInt(),
83+
create = { connection -> wrapConnection(connection) { schema.create(it) } },
84+
loggingConfig = Logging(logger = sqlLogger),
85+
lifecycleConfig =
86+
DatabaseConfiguration.Lifecycle(
87+
onCreateConnection = { connection ->
88+
setupSqliteBinding(connection)
89+
wrapConnection(connection) { driver ->
90+
schema.create(driver)
91+
}
92+
},
93+
onCloseConnection = { connection ->
94+
deregisterSqliteBinding(connection)
95+
},
96+
),
9797
),
98+
),
9899
)
99100
return this.driver as PsSqlDriver
100101
}

core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ public actual class DatabaseDriverFactory {
2121
@Suppress("unused")
2222
private fun onTransactionCommit(success: Boolean) {
2323
driver?.also { driver ->
24-
if (success) {
25-
driver.fireTableUpdates()
26-
} else {
24+
// Only clear updates on rollback
25+
// We manually fire updates when a transaction ended
26+
if (!success) {
2727
driver.clearTableUpdates()
2828
}
2929
}

demos/hello-powersync/composeApp/src/androidMain/AndroidManifest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
android:label="@string/app_name"
1010
android:roundIcon="@mipmap/ic_launcher_round"
1111
android:supportsRtl="true"
12+
android:networkSecurityConfig="@xml/network_security_config"
1213
android:theme="@android:style/Theme.Material.Light.NoActionBar">
14+
1315
<activity
1416
android:exported="true"
1517
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|mnc|colorMode|density|fontScale|fontWeightAdjustment|keyboard|layoutDirection|locale|mcc|navigation|smallestScreenSize|touchscreen|uiMode"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<network-security-config>
3+
<domain-config cleartextTrafficPermitted="true">
4+
<domain includeSubdomains="true">localhost</domain>
5+
</domain-config>
6+
</network-security-config>

0 commit comments

Comments
 (0)