Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds an optional Source argument to DocumentReference and Query #491

Merged
merged 2 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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)
}
Loading