Skip to content

Commit

Permalink
Merge pull request #491 from Qw4z1/set-source
Browse files Browse the repository at this point in the history
Adds an optional Source argument to DocumentReference and Query
  • Loading branch information
nbransby authored Apr 18, 2024
2 parents 90e1bd6 + 8d43e47 commit ad577b3
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package dev.gitlive.firebase.firestore
import com.google.firebase.firestore.MetadataChanges
import dev.gitlive.firebase.Firebase
import dev.gitlive.firebase.FirebaseApp
import dev.gitlive.firebase.firestore.Source.*
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
Expand Down Expand Up @@ -171,8 +172,8 @@ internal actual class NativeDocumentReference actual constructor(actual val nati

actual fun collection(collectionPath: String) = NativeCollectionReference(android.collection(collectionPath))

actual suspend fun get() =
NativeDocumentSnapshot(android.get().await())
actual suspend fun get(source: Source) =
NativeDocumentSnapshot(android.get(source.toAndroidSource()).await())

actual suspend fun setEncoded(encodedData: Any, setOptions: SetOptions) {
val task = (setOptions.android?.let {
Expand Down Expand Up @@ -230,7 +231,7 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) {

open val android = nativeQuery.android

actual suspend fun get() = QuerySnapshot(android.get().await())
actual suspend fun get(source: Source) = QuerySnapshot(android.get(source.toAndroidSource()).await())

actual fun limit(limit: Number) = Query(NativeQuery(android.limit(limit.toLong())))

Expand Down Expand Up @@ -428,3 +429,11 @@ actual class FieldPath private constructor(val android: com.google.firebase.fire
}

actual typealias EncodedFieldPath = com.google.firebase.firestore.FieldPath

internal typealias NativeSource = com.google.firebase.firestore.Source

private fun Source.toAndroidSource() = when(this) {
CACHE -> NativeSource.CACHE
SERVER -> NativeSource.SERVER
DEFAULT -> NativeSource.DEFAULT
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ expect open class Query internal constructor(nativeQuery: NativeQuery) {
fun limit(limit: Number): Query
val snapshots: Flow<QuerySnapshot>
fun snapshots(includeMetadataChanges: Boolean = false): Flow<QuerySnapshot>
suspend fun get(): QuerySnapshot
suspend fun get(source: Source = Source.DEFAULT): QuerySnapshot

internal fun where(filter: Filter): Query

Expand Down Expand Up @@ -327,7 +327,7 @@ internal expect class NativeDocumentReference(nativeValue: NativeDocumentReferen
fun snapshots(includeMetadataChanges: Boolean = false): Flow<NativeDocumentSnapshot>

fun collection(collectionPath: String): NativeCollectionReference
suspend fun get(): NativeDocumentSnapshot
suspend fun get(source: Source = Source.DEFAULT): NativeDocumentSnapshot
suspend fun setEncoded(encodedData: Any, setOptions: SetOptions)
suspend fun updateEncoded(encodedData: Any)
suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List<Pair<String, Any?>>)
Expand All @@ -348,7 +348,7 @@ data class DocumentReference internal constructor(@PublishedApi internal val nat
fun snapshots(includeMetadataChanges: Boolean = false): Flow<DocumentSnapshot> = native.snapshots(includeMetadataChanges).map(::DocumentSnapshot)

fun collection(collectionPath: String): CollectionReference = CollectionReference(native.collection(collectionPath))
suspend fun get(): DocumentSnapshot = DocumentSnapshot(native.get())
suspend fun get(source: Source = Source.DEFAULT): DocumentSnapshot = DocumentSnapshot(native.get(source))

@Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(data, merge) { this.encodeDefaults = encodeDefaults }"))
suspend inline fun <reified T> set(data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(data, merge) {
Expand Down Expand Up @@ -553,3 +553,9 @@ expect class FieldPath(vararg fieldNames: String) {
}

expect class EncodedFieldPath

enum class Source {
CACHE,
SERVER,
DEFAULT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package dev.gitlive.firebase.firestore

import dev.gitlive.firebase.*
import kotlin.test.*

/**
* These tests are separated from other tests because
* testing Firestore Source requires toggling persistence settings per test.
*/
class FirestoreSourceTest {
lateinit var firestore: FirebaseFirestore

companion object {
val testDoc = FirebaseFirestoreTest.FirestoreTest(
"aaa",
0.0,
1,
listOf("a", "aa", "aaa"),
"notNull",
)
}

private suspend fun setDoc() {
firestore.collection("testFirestoreQuerying").document("one").set(testDoc)
}

private fun initializeFirebase(persistenceEnabled: Boolean = false) {
val app = Firebase.apps(context).firstOrNull() ?: Firebase.initialize(
context,
FirebaseOptions(
applicationId = "1:846484016111:ios:dd1f6688bad7af768c841a",
apiKey = "AIzaSyCK87dcMFhzCz_kJVs2cT2AVlqOTLuyWV0",
databaseUrl = "https://fir-kotlin-sdk.firebaseio.com",
storageBucket = "fir-kotlin-sdk.appspot.com",
projectId = "fir-kotlin-sdk",
gcmSenderId = "846484016111"
)
)

firestore = Firebase.firestore(app).apply {
useEmulator(emulatorHost, 8080)
setSettings(persistenceEnabled = persistenceEnabled)
}
}

@AfterTest
fun deinitializeFirebase() = runBlockingTest {
Firebase.apps(context).forEach {
it.delete()
}
}

@Test
fun testGetFromServer_withPersistence() = runTest {
initializeFirebase(persistenceEnabled = true)
setDoc()
val doc = firestore.collection("testFirestoreQuerying").document("one").get(Source.SERVER)
assertTrue(doc.exists)
assertFalse(doc.native.metadata.isFromCache)
}

@Test
fun testGetFromServer_withoutPersistence() = runTest {
initializeFirebase(persistenceEnabled = false)
setDoc()
val doc = firestore.collection("testFirestoreQuerying").document("one").get(Source.SERVER)
assertTrue(doc.exists)
assertFalse(doc.native.metadata.isFromCache)
}

@Test
fun testGetFromCache() = runTest {
initializeFirebase(persistenceEnabled = true)

// Warm up cache by setting a document
setDoc()

val cachedDoc = firestore.collection("testFirestoreQuerying").document("one").get(Source.CACHE)
assertTrue(cachedDoc.exists)
assertTrue(cachedDoc.native.metadata.isFromCache)
}

@Test
fun testGetFromCache_withoutPersistence() = runTest {
initializeFirebase(persistenceEnabled = false)
setDoc()
assertFailsWith(FirebaseFirestoreException::class) {
firestore.collection("testFirestoreQuerying").document("one").get(Source.CACHE)
}
}

@Test
fun testGetDefault_withPersistence() = runTest {
initializeFirebase(persistenceEnabled = false)
val doc = firestore.collection("testFirestoreQuerying").document("one").get(Source.DEFAULT)
assertTrue(doc.exists)
assertFalse(doc.native.metadata.isFromCache)
}
@Test
fun testGet() = runTest {
initializeFirebase(persistenceEnabled = false)
val doc = firestore.collection("testFirestoreQuerying").document("one").get()
assertTrue(doc.exists)
assertFalse(doc.native.metadata.isFromCache)
}

@Test
fun testGetDefault_withoutPersistence() = runTest {
initializeFirebase(persistenceEnabled = true)
setDoc()
val doc = firestore.collection("testFirestoreQuerying").document("one").get(Source.DEFAULT)
assertTrue(doc.exists)
// Firebase defaults to first fetching from server
assertFalse(doc.native.metadata.isFromCache)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ internal actual class NativeDocumentReference actual constructor(actual val nati

actual fun collection(collectionPath: String) = NativeCollectionReference(ios.collectionWithPath(collectionPath))

actual suspend fun get() =
NativeDocumentSnapshot(awaitResult { ios.getDocumentWithCompletion(it) })
actual suspend fun get(source: Source) =
NativeDocumentSnapshot(awaitResult { ios.getDocumentWithSource(source.toIosSource(), it) })

actual suspend fun setEncoded(encodedData: Any, setOptions: SetOptions) = await {
when (setOptions) {
Expand Down Expand Up @@ -235,7 +235,7 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) {

open val ios: FIRQuery = nativeQuery.ios

actual suspend fun get() = QuerySnapshot(awaitResult { ios.getDocumentsWithCompletion(it) })
actual suspend fun get(source: Source) = QuerySnapshot(awaitResult { ios.getDocumentsWithSource(source.toIosSource(),it) })

actual fun limit(limit: Number) = Query(ios.queryLimitedTo(limit.toLong()).native)

Expand Down Expand Up @@ -485,3 +485,9 @@ suspend inline fun <T> await(function: (callback: (NSError?) -> Unit) -> T): T {
job.await()
return result
}

private fun Source.toIosSource() = when (this) {
Source.CACHE -> FIRFirestoreSource.FIRFirestoreSourceCache
Source.SERVER -> FIRFirestoreSource.FIRFirestoreSourceServer
Source.DEFAULT -> FIRFirestoreSource.FIRFirestoreSourceDefault
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,20 @@ external fun getDoc(
options: Any? = definedExternally
): Promise<DocumentSnapshot>

external fun getDocFromCache(
reference: DocumentReference,
): Promise<DocumentSnapshot>

external fun getDocFromServer(
reference: DocumentReference,
): Promise<DocumentSnapshot>

external fun getDocs(query: Query): Promise<QuerySnapshot>

external fun getDocsFromCache(query: Query): Promise<QuerySnapshot>

external fun getDocsFromServer(query: Query): Promise<QuerySnapshot>

external fun getFirestore(app: FirebaseApp? = definedExternally): Firestore

external fun increment(n: Int): FieldValue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,8 @@ package dev.gitlive.firebase.firestore
import dev.gitlive.firebase.Firebase
import dev.gitlive.firebase.FirebaseApp
import dev.gitlive.firebase.FirebaseException
import dev.gitlive.firebase.firestore.externals.Firestore
import dev.gitlive.firebase.firestore.externals.QueryConstraint
import dev.gitlive.firebase.firestore.externals.addDoc
import dev.gitlive.firebase.firestore.externals.and
import dev.gitlive.firebase.firestore.externals.clearIndexedDbPersistence
import dev.gitlive.firebase.firestore.externals.connectFirestoreEmulator
import dev.gitlive.firebase.firestore.externals.deleteDoc
import dev.gitlive.firebase.firestore.externals.doc
import dev.gitlive.firebase.firestore.externals.*
import dev.gitlive.firebase.firestore.externals.documentId as jsDocumentId
import dev.gitlive.firebase.firestore.externals.enableIndexedDbPersistence
import dev.gitlive.firebase.firestore.externals.getDoc
import dev.gitlive.firebase.firestore.externals.getDocs
import dev.gitlive.firebase.firestore.externals.getFirestore
import dev.gitlive.firebase.firestore.externals.initializeFirestore
import dev.gitlive.firebase.firestore.externals.onSnapshot
import dev.gitlive.firebase.firestore.externals.or
import dev.gitlive.firebase.firestore.externals.orderBy
import dev.gitlive.firebase.firestore.externals.query
import dev.gitlive.firebase.firestore.externals.refEqual
import dev.gitlive.firebase.firestore.externals.setDoc
import dev.gitlive.firebase.firestore.externals.setLogLevel
import dev.gitlive.firebase.firestore.externals.writeBatch
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.await
import kotlinx.coroutines.channels.awaitClose
Expand Down Expand Up @@ -218,7 +198,7 @@ internal actual class NativeDocumentReference actual constructor(actual val nati

actual fun collection(collectionPath: String) = rethrow { NativeCollectionReference(jsCollection(js, collectionPath)) }

actual suspend fun get() = rethrow { NativeDocumentSnapshot( getDoc(js).await()) }
actual suspend fun get(source: Source) = rethrow { NativeDocumentSnapshot( js.get(source).await()) }

actual val snapshots: Flow<NativeDocumentSnapshot> get() = snapshots()

Expand Down Expand Up @@ -276,7 +256,7 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) {

open val js: JsQuery = nativeQuery.js

actual suspend fun get() = rethrow { QuerySnapshot(getDocs(js).await()) }
actual suspend fun get(source: Source) = rethrow { QuerySnapshot(js.get(source).await()) }

actual fun limit(limit: Number) = Query(query(js, jsLimit(limit)))

Expand Down Expand Up @@ -543,3 +523,15 @@ fun entriesOf(jsObject: dynamic): List<Pair<String, Any?>> =
// from: https://discuss.kotlinlang.org/t/how-to-access-native-js-object-as-a-map-string-any/509/8
fun mapOf(jsObject: dynamic): Map<String, Any?> =
entriesOf(jsObject).toMap()

private fun NativeDocumentReferenceType.get(source: Source) = when (source) {
Source.DEFAULT -> getDoc(this)
Source.CACHE -> getDocFromCache(this)
Source.SERVER -> getDocFromServer(this)
}

private fun JsQuery.get(source: Source) = when (source) {
Source.DEFAULT -> getDocs(this)
Source.CACHE -> getDocsFromCache(this)
Source.SERVER -> getDocsFromServer(this)
}

0 comments on commit ad577b3

Please sign in to comment.