Skip to content

Commit

Permalink
Fixed Database runTransaction
Browse files Browse the repository at this point in the history
  • Loading branch information
Daeda88 committed Jan 22, 2024
1 parent 4ad4d13 commit 46d7c17
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,17 @@ data class EncodeSettings internal constructor(
val shouldEncodeElementDefault: Boolean,
override val serializersModule: SerializersModule,
) : EncodeDecodeSettings() {
class Builder {
var shouldEncodeElementDefault: Boolean = true
var serializersModule: SerializersModule = EmptySerializersModule()

@PublishedApi
internal fun build() = EncodeSettings(shouldEncodeElementDefault, serializersModule)
interface Builder {
var shouldEncodeElementDefault: Boolean
var serializersModule: SerializersModule

}

@PublishedApi
internal class BuilderImpl : Builder {
override var shouldEncodeElementDefault: Boolean = true
override var serializersModule: SerializersModule = EmptySerializersModule()
}
}

Expand All @@ -40,9 +45,26 @@ data class DecodeSettings internal constructor(
override val serializersModule: SerializersModule = EmptySerializersModule(),
) : EncodeDecodeSettings() {

class Builder {
var serializersModule: SerializersModule = EmptySerializersModule()
interface Builder {
var serializersModule: SerializersModule
}

fun build() = DecodeSettings(serializersModule)
@PublishedApi
internal class BuilderImpl : Builder {
override var serializersModule: SerializersModule = EmptySerializersModule()
}
}

interface EncodeDecodeSettingsBuilder : EncodeSettings.Builder, DecodeSettings.Builder

@PublishedApi
internal class EncodeDecodeSettingsBuilderImpl : EncodeDecodeSettingsBuilder {

override var shouldEncodeElementDefault: Boolean = true
override var serializersModule: SerializersModule = EmptySerializersModule()
}

@PublishedApi
internal fun EncodeSettings.Builder.buildEncodeSettings(): EncodeSettings = EncodeSettings(shouldEncodeElementDefault, serializersModule)
@PublishedApi
internal fun DecodeSettings.Builder.buildDecodeSettings(): DecodeSettings = DecodeSettings(serializersModule)
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,22 @@ import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.serializer

inline fun <reified T> decode(value: Any?): T = decode(value) {}
inline fun <reified T> decode(value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T {
inline fun <reified T> decode(value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T =
decode(value, DecodeSettings.BuilderImpl().apply(buildSettings).buildDecodeSettings())

@PublishedApi
internal inline fun <reified T> decode(value: Any?, decodeSettings: DecodeSettings): T {
val strategy = serializer<T>()
return decode(strategy as DeserializationStrategy<T>, value, buildSettings)
return decode(strategy as DeserializationStrategy<T>, value, decodeSettings)
}
fun <T> decode(strategy: DeserializationStrategy<T>, value: Any?): T = decode(strategy, value) {}
inline fun <T> decode(strategy: DeserializationStrategy<T>, value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T {
inline fun <T> decode(strategy: DeserializationStrategy<T>, value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T =
decode(strategy, value, DecodeSettings.BuilderImpl().apply(buildSettings).buildDecodeSettings())

@PublishedApi
internal fun <T> decode(strategy: DeserializationStrategy<T>, value: Any?, decodeSettings: DecodeSettings): T {
require(value != null || strategy.descriptor.isNullable) { "Value was null for non-nullable type ${strategy.descriptor.serialName}" }
return FirebaseDecoder(value, DecodeSettings.Builder().apply(buildSettings).build()).decodeSerializableValue(strategy)
return FirebaseDecoder(value, decodeSettings).decodeSerializableValue(strategy)
}
expect fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder
expect fun getPolymorphicType(value: Any?, discriminator: String): String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,24 @@ fun <T> encode(strategy: SerializationStrategy<T>, value: T, shouldEncodeElement
this.shouldEncodeElementDefault = shouldEncodeElementDefault
}

inline fun <T> encode(strategy: SerializationStrategy<T>, value: T, buildSettings: EncodeSettings.Builder.() -> Unit): Any? =
FirebaseEncoder(EncodeSettings.Builder().apply(buildSettings).build()).apply { encodeSerializableValue(strategy, value) }.value
inline fun <T> encode(strategy: SerializationStrategy<T>, value: T, buildSettings: EncodeSettings.Builder.() -> Unit) =
encode(strategy, value, EncodeSettings.BuilderImpl().apply(buildSettings).buildEncodeSettings())

@PublishedApi
internal inline fun <T> encode(strategy: SerializationStrategy<T>, value: T, encodeSettings: EncodeSettings): Any? =
FirebaseEncoder(encodeSettings).apply { encodeSerializableValue(strategy, value) }.value

@Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("encode(value) { this.shouldEncodeElementDefault = shouldEncodeElementDefault }"))
inline fun <reified T> encode(value: T, shouldEncodeElementDefault: Boolean): Any? = encode(value) {
this.shouldEncodeElementDefault = shouldEncodeElementDefault
}
inline fun <reified T> encode(value: T, buildSettings: EncodeSettings.Builder.() -> Unit): Any? = value?.let {
FirebaseEncoder(EncodeSettings.Builder().apply(buildSettings).build()).apply {

inline fun <reified T> encode(value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) =
encode(value, EncodeSettings.BuilderImpl().apply(buildSettings).buildEncodeSettings())

@PublishedApi
internal inline fun <reified T> encode(value: T, encodeSettings: EncodeSettings): Any? = value?.let {
FirebaseEncoder(encodeSettings).apply {
if (it is ValueWithSerializer<*> && it.value is T) {
@Suppress("UNCHECKED_CAST")
(it as ValueWithSerializer<T>).let {
Expand All @@ -49,7 +58,7 @@ class FirebaseEncoder(
) : Encoder {

constructor(shouldEncodeElementDefault: Boolean) : this(
EncodeSettings.Builder().apply { this.shouldEncodeElementDefault = shouldEncodeElementDefault }.build()
EncodeSettings.BuilderImpl().apply { this.shouldEncodeElementDefault = shouldEncodeElementDefault }.buildEncodeSettings()
)

var value: Any? = null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package dev.gitlive.firebase

import kotlinx.serialization.KSerializer

inline fun <reified T> reencodeTransformation(value: Any?, builder: EncodeDecodeSettingsBuilder.() -> Unit, transform: (T) -> T): Any? {
val encodeDecodeSettingsBuilder = EncodeDecodeSettingsBuilderImpl().apply(builder)
val oldValue: T = decode(value, encodeDecodeSettingsBuilder.buildDecodeSettings())
return encode(transform(oldValue), encodeDecodeSettingsBuilder.buildEncodeSettings())
}

inline fun <T> reencodeTransformation(strategy: KSerializer<T>, value: Any?, builder: EncodeDecodeSettingsBuilder.() -> Unit, transform: (T) -> T): Any? {
val encodeDecodeSettingsBuilder = EncodeDecodeSettingsBuilderImpl().apply(builder)
val oldValue: T = decode(strategy, value, encodeDecodeSettingsBuilder.buildDecodeSettings())
return encode(strategy, transform(oldValue), encodeDecodeSettingsBuilder.buildEncodeSettings())
}
Original file line number Diff line number Diff line change
Expand Up @@ -192,14 +192,12 @@ actual class DatabaseReference internal constructor(
.run { if(persistenceEnabled) await() else awaitWhileOnline(database) }
.run { Unit }

actual suspend fun <T> runTransaction(strategy: KSerializer<T>, buildSettings: DecodeSettings.Builder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot {
actual suspend fun <T> runTransaction(strategy: KSerializer<T>, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot {
val deferred = CompletableDeferred<DataSnapshot>()
android.runTransaction(object : Transaction.Handler {

override fun doTransaction(currentData: MutableData): Transaction.Result {
currentData.value = currentData.value?.let {
transactionUpdate(decode(strategy, it, buildSettings))
}
currentData.value = reencodeTransformation(strategy, currentData.value, buildSettings, transactionUpdate)
return Transaction.success(currentData)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package dev.gitlive.firebase.database

import dev.gitlive.firebase.DecodeSettings
import dev.gitlive.firebase.EncodeDecodeSettingsBuilder
import dev.gitlive.firebase.EncodeSettings
import dev.gitlive.firebase.Firebase
import dev.gitlive.firebase.FirebaseApp
Expand Down Expand Up @@ -108,7 +109,7 @@ expect class DatabaseReference : BaseDatabaseReference {

suspend fun removeValue()

suspend fun <T> runTransaction(strategy: KSerializer<T>, buildSettings: DecodeSettings.Builder.() -> Unit = {}, transactionUpdate: (currentData: T) -> T): DataSnapshot
suspend fun <T> runTransaction(strategy: KSerializer<T>, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit = {}, transactionUpdate: (currentData: T) -> T): DataSnapshot
}

expect class DataSnapshot {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,51 +79,47 @@ class FirebaseDatabaseTest {
assertEquals(3, firebaseDatabaseChildCount)
}

// @Test
// fun testBasicIncrementTransaction() = runTest {
// val data = DatabaseTest("PostOne", 2)
// val userRef = database.reference("users/user_1/post_id_1")
// setupDatabase(userRef, data, DatabaseTest.serializer())
//
// // Check database before transaction
// val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer())
// assertEquals(data.title, userDocBefore.title)
// assertEquals(data.likes, userDocBefore.likes)
//
// // Run transaction
// val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { DatabaseTest(data.title, it.likes + 1) }
// val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer())
//
// // Check the database after transaction
// assertEquals(data.title, userDocAfter.title)
// assertEquals(data.likes + 1, userDocAfter.likes)
//
// // cleanUp Firebase
// cleanUp()
// }
//
// @Test
// fun testBasicDecrementTransaction() = runTest {
// val data = DatabaseTest("PostTwo", 2)
// val userRef = database.reference("users/user_1/post_id_2")
// setupDatabase(userRef, data, DatabaseTest.serializer())
//
// // Check database before transaction
// val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer())
// assertEquals(data.title, userDocBefore.title)
// assertEquals(data.likes, userDocBefore.likes)
//
// // Run transaction
// val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { DatabaseTest(data.title, it.likes - 1) }
// val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer())
//
// // Check the database after transaction
// assertEquals(data.title, userDocAfter.title)
// assertEquals(data.likes - 1, userDocAfter.likes)
//
// // cleanUp Firebase
// cleanUp()
// }
@Test
fun testBasicIncrementTransaction() = runTest {
ensureDatabaseConnected()
val data = DatabaseTest("PostOne", 2)
val userRef = database.reference("users/user_1/post_id_1")
setupDatabase(userRef, data, DatabaseTest.serializer())

// Check database before transaction
val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer())
assertEquals(data.title, userDocBefore.title)
assertEquals(data.likes, userDocBefore.likes)

// Run transaction
val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { DatabaseTest(data.title, it.likes + 1) }
val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer())

// Check the database after transaction
assertEquals(data.title, userDocAfter.title)
assertEquals(data.likes + 1, userDocAfter.likes)
}

@Test
fun testBasicDecrementTransaction() = runTest {
ensureDatabaseConnected()
val data = DatabaseTest("PostTwo", 2)
val userRef = database.reference("users/user_1/post_id_2")
setupDatabase(userRef, data, DatabaseTest.serializer())

// Check database before transaction
val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer())
assertEquals(data.title, userDocBefore.title)
assertEquals(data.likes, userDocBefore.likes)

// Run transaction
val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { DatabaseTest(data.title, it.likes - 1) }
val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer())

// Check the database after transaction
assertEquals(data.title, userDocAfter.title)
assertEquals(data.likes - 1, userDocAfter.likes)
}

@Test
fun testSetServerTimestamp() = runTest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,13 +159,11 @@ actual class DatabaseReference internal constructor(
ios.await(persistenceEnabled) { removeValueWithCompletionBlock(it) }
}

actual suspend fun <T> runTransaction(strategy: KSerializer<T>, buildSettings: DecodeSettings.Builder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot {
actual suspend fun <T> runTransaction(strategy: KSerializer<T>, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot {
val deferred = CompletableDeferred<DataSnapshot>()
ios.runTransactionBlock(
block = { firMutableData ->
firMutableData?.value = firMutableData?.value?.let {
transactionUpdate(decode(strategy, it, buildSettings))
}
firMutableData?.value = reencodeTransformation(strategy, firMutableData?.value, buildSettings, transactionUpdate)
FIRTransactionResult.successWithValue(firMutableData!!)
},
andCompletionBlock = { error, _, snapshot ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,10 @@ actual class DatabaseReference internal constructor(
rethrow { update(js, encodedUpdate ?: json()).awaitWhileOnline(database) }


actual suspend fun <T> runTransaction(strategy: KSerializer<T>, buildSettings: DecodeSettings.Builder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot {
return DataSnapshot(jsRunTransaction(js, transactionUpdate).awaitWhileOnline(database).snapshot, database)
actual suspend fun <T> runTransaction(strategy: KSerializer<T>, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot {
return DataSnapshot(jsRunTransaction<Any?>(js, transactionUpdate = { currentData ->
reencodeTransformation(strategy, currentData ?: json(), buildSettings, transactionUpdate)
}).awaitWhileOnline(database).snapshot, database)
}
}

Expand All @@ -173,7 +175,7 @@ actual class DataSnapshot internal constructor(
actual inline fun <reified T> value() =
rethrow { decode<T>(value = js.`val`()) }

actual fun <T> value(strategy: DeserializationStrategy<T>, buildSettings: DecodeSettings.Builder.() -> Unit) =
actual inline fun <T> value(strategy: DeserializationStrategy<T>, buildSettings: DecodeSettings.Builder.() -> Unit) =
rethrow { decode(strategy, js.`val`(), buildSettings) }

actual val exists get() = rethrow { js.exists() }
Expand Down

0 comments on commit 46d7c17

Please sign in to comment.