Skip to content

Commit e7a938c

Browse files
committed
Add typed variants of CRUD data
1 parent a7863e8 commit e7a938c

File tree

5 files changed

+111
-11
lines changed

5 files changed

+111
-11
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
* Added the ability to log PowerSync service HTTP request information via specifying a
66
`SyncClientConfiguration` in the `SyncOptions.clientConfiguration` parameter used in
77
`PowerSyncDatabase.connect()` calls.
8+
* `CrudEntry`: Add `data` and `typedPreviousValues` fields as typed variants of
9+
`opData` and `previousValues`, respectively.
810

911
## 1.3.1
1012

core/src/commonIntegrationTest/kotlin/com/powersync/CrudTest.kt

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,59 @@ class CrudTest {
9090
val batch = database.getNextCrudTransaction()
9191
batch shouldBe null
9292
}
93+
94+
@Test
95+
fun typedUpdates() =
96+
databaseTest {
97+
database.updateSchema(
98+
Schema(
99+
Table(
100+
"foo",
101+
listOf(
102+
Column.text("a"),
103+
Column.integer("b"),
104+
Column.integer("c"),
105+
),
106+
trackPreviousValues = TrackPreviousValuesOptions(onlyWhenChanged = true),
107+
),
108+
),
109+
)
110+
111+
database.writeTransaction { tx ->
112+
tx.execute(
113+
"INSERT INTO foo (id,a,b,c) VALUES (uuid(), ?, ?, ?)",
114+
listOf(
115+
"text",
116+
42,
117+
13.37,
118+
),
119+
)
120+
tx.execute(
121+
"UPDATE foo SET a = ?, b = NULL",
122+
listOf(
123+
"te\"xt",
124+
),
125+
)
126+
}
127+
128+
val batch = database.getNextCrudTransaction()!!
129+
batch.crud[0].data shouldBe
130+
mapOf(
131+
"a" to "text",
132+
"b" to 42,
133+
"c" to 13.37,
134+
)
135+
batch.crud[0].typedPreviousValues shouldBe null
136+
137+
batch.crud[1].data shouldBe
138+
mapOf(
139+
"a" to "te\"xt",
140+
"b" to null,
141+
)
142+
batch.crud[1].typedPreviousValues shouldBe
143+
mapOf(
144+
"a" to "text",
145+
"b" to 42,
146+
)
147+
}
93148
}

core/src/commonMain/kotlin/com/powersync/db/crud/CrudEntry.kt

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
package com.powersync.db.crud
22

3-
import com.powersync.PowerSyncDatabase
43
import com.powersync.db.schema.Table
54
import com.powersync.utils.JsonUtil
6-
import kotlinx.serialization.json.contentOrNull
5+
import kotlinx.serialization.json.JsonElement
6+
import kotlinx.serialization.json.JsonNull
77
import kotlinx.serialization.json.jsonObject
88
import kotlinx.serialization.json.jsonPrimitive
99

1010
/**
1111
* A single client-side change.
1212
*/
13-
public data class CrudEntry(
13+
@ConsistentCopyVisibility
14+
public data class CrudEntry internal constructor(
1415
/**
1516
* ID of the changed row.
1617
*/
@@ -57,35 +58,75 @@ public data class CrudEntry(
5758
*
5859
* For DELETE, this is null.
5960
*/
61+
@Deprecated("Use data instead", replaceWith = ReplaceWith("data"))
6062
val opData: Map<String, String?>?,
63+
/**
64+
* Data associated with the change.
65+
*
66+
* For PUT, this is contains all non-null columns of the row.
67+
*
68+
* For PATCH, this is contains the columns that changed.
69+
*
70+
* For DELETE, this is null.
71+
*/
72+
val data: Map<String, Any?>?,
6173
/**
6274
* Previous values before this change.
6375
*
6476
* These values can be tracked for `UPDATE` statements when [Table.trackPreviousValues] is
6577
* enabled.
6678
*/
79+
@Deprecated("Use typedPreviousValues instead", replaceWith = ReplaceWith("typedPreviousValues"))
6780
val previousValues: Map<String, String?>? = null,
81+
/**
82+
* Previous values before this change.
83+
*
84+
* These values can be tracked for `UPDATE` statements when [Table.trackPreviousValues] is
85+
* enabled.
86+
*/
87+
val typedPreviousValues: Map<String, Any?>? = null,
6888
) {
6989
public companion object {
7090
public fun fromRow(row: CrudRow): CrudEntry {
7191
val data = JsonUtil.json.parseToJsonElement(row.data).jsonObject
92+
val opData = data["data"]?.asData()
93+
val previousValues = data["old"]?.asData()
94+
7295
return CrudEntry(
7396
id = data["id"]!!.jsonPrimitive.content,
7497
clientId = row.id.toInt(),
7598
op = UpdateType.fromJsonChecked(data["op"]!!.jsonPrimitive.content),
76-
opData =
77-
data["data"]?.jsonObject?.mapValues { (_, value) ->
78-
value.jsonPrimitive.contentOrNull
79-
},
99+
opData = opData?.toStringMap(),
100+
data = opData,
80101
table = data["type"]!!.jsonPrimitive.content,
81102
transactionId = row.txId,
82103
metadata = data["metadata"]?.jsonPrimitive?.content,
83-
previousValues =
84-
data["old"]?.jsonObject?.mapValues { (_, value) ->
85-
value.jsonPrimitive.contentOrNull
86-
},
104+
typedPreviousValues = previousValues,
105+
previousValues = previousValues?.toStringMap(),
87106
)
88107
}
108+
109+
private fun JsonElement.asData(): Map<String, Any?> =
110+
jsonObject.mapValues { (_, value) ->
111+
val primitive = value.jsonPrimitive
112+
if (primitive === JsonNull) {
113+
null
114+
} else if (primitive.isString) {
115+
primitive.content
116+
} else {
117+
primitive.content.jsonNumberOrBoolean()
118+
}
119+
}
120+
121+
private fun String.jsonNumberOrBoolean(): Any =
122+
when {
123+
this == "true" -> true
124+
this == "false" -> false
125+
this.any { char -> char == '.' || char == 'e' || char == 'E' } -> this.toDouble()
126+
else -> this.toInt()
127+
}
128+
129+
private fun Map<String, Any?>.toStringMap(): Map<String, String> = mapValues { (_, v) -> v.toString() }
89130
}
90131

91132
override fun toString(): String = "CrudEntry<$transactionId/$clientId ${op.toJson()} $table/$id $opData>"

core/src/commonTest/kotlin/com/powersync/bucket/BucketStorageTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ class BucketStorageTest {
7979
mapOf(
8080
"key" to "value",
8181
),
82+
data = mapOf("key" to "value"),
8283
)
8384
mockDb =
8485
mock<InternalDatabase> {

core/src/commonTest/kotlin/com/powersync/sync/SyncStreamTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ class SyncStreamTest {
106106
mapOf(
107107
"key" to "value",
108108
),
109+
data = mapOf("key" to "value"),
109110
)
110111
bucketStorage =
111112
mock<BucketStorage> {

0 commit comments

Comments
 (0)