@@ -20,16 +20,20 @@ import com.google.firebase.dataconnect.DataSource
2020import com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType
2121import com.google.firebase.dataconnect.QueryRef
2222import com.google.firebase.dataconnect.core.DataConnectBidiConnectStream
23+ import com.google.firebase.dataconnect.core.DataConnectCache
2324import com.google.firebase.dataconnect.core.DataConnectGrpcClient
2425import com.google.firebase.dataconnect.core.DataConnectSerialization
2526import com.google.firebase.dataconnect.core.Logger
2627import com.google.firebase.dataconnect.core.LoggerGlobals.debug
28+ import com.google.firebase.dataconnect.core.QueryId
29+ import com.google.firebase.dataconnect.core.calculateQueryId
30+ import com.google.firebase.dataconnect.core.getEntityIdForPathFunction
31+ import com.google.firebase.dataconnect.sqlite.GetEntityIdForPathFunction
2732import com.google.firebase.dataconnect.util.CoroutineUtils.createChildSupervisorScope
2833import com.google.firebase.dataconnect.util.IdStringGenerator
29- import com.google.firebase.dataconnect.util.ImmutableByteArray
30- import com.google.firebase.dataconnect.util.ProtoUtil.calculateSha512
3134import com.google.firebase.dataconnect.util.update
3235import com.google.protobuf.Struct
36+ import java.lang.System.currentTimeMillis
3337import java.util.concurrent.atomic.AtomicReference
3438import kotlinx.coroutines.CoroutineName
3539import kotlinx.coroutines.CoroutineScope
@@ -40,6 +44,7 @@ import kotlinx.coroutines.awaitCancellation
4044import kotlinx.coroutines.flow.Flow
4145import kotlinx.coroutines.flow.emptyFlow
4246import kotlinx.coroutines.flow.map
47+ import kotlinx.coroutines.flow.onEach
4348import kotlinx.coroutines.job
4449import kotlinx.coroutines.launch
4550import kotlinx.coroutines.sync.Mutex
@@ -53,6 +58,7 @@ internal class RealtimeQueryManager(
5358 coroutineScope : CoroutineScope ,
5459 private val idStringGenerator : IdStringGenerator ,
5560 private val serialization : DataConnectSerialization ,
61+ private val cache : DataConnectCache ? ,
5662 private val logger : Logger ,
5763) {
5864
@@ -135,24 +141,19 @@ internal class RealtimeQueryManager(
135141 operationName : String ,
136142 variables : Struct ,
137143 ): Flow <DataConnectGrpcClient .OperationResult > {
138- // calculateSha512 () is a CPU intensive operation that should NOT be performed on the main
144+ // calculateQueryId () is a CPU intensive operation that should NOT be performed on the main
139145 // thread. This is the first reason why this method assumes it's running in this.coroutineScope.
140- val queryId = variables.calculateSha512(preamble = operationName)
146+ val queryId = calculateQueryId( operationName, variables )
141147
142148 // Acquiring the lock by an arbitrary thread could result in priority inversion. This is the
143149 // second reason why this method assumes it's running in this.coroutineScope: control over the
144150 // thread that acquires the lock.
145151 mutex.withLock {
146152 return flowByQueryId.getOrPut(queryId) {
147- val executeResponseFlow = stream.subscribe(requestId, operationName, variables)
148-
149- executeResponseFlow.map { executeResponse ->
150- DataConnectGrpcClient .OperationResult (
151- data = executeResponse.data,
152- errors = executeResponse.errors,
153- source = DataSource .SERVER ,
154- )
155- }
153+ stream
154+ .subscribe(requestId, operationName, variables)
155+ .updateCache(cache, queryId)
156+ .mapToOperationResponse()
156157 }
157158 }
158159 }
@@ -212,8 +213,7 @@ internal class RealtimeQueryManager(
212213
213214 class Connected (val stream : DataConnectBidiConnectStream ) : State {
214215 val mutex = Mutex ()
215- val flowByQueryId:
216- MutableMap <ImmutableByteArray , Flow <DataConnectGrpcClient .OperationResult >> =
216+ val flowByQueryId: MutableMap <QueryId , Flow <DataConnectGrpcClient .OperationResult >> =
217217 mutableMapOf ()
218218 override fun toString () = " Connected"
219219 }
@@ -242,3 +242,37 @@ internal suspend fun <Data, Variables> RealtimeQueryManager.subscribe(
242242 queryRef.dataSerializersModule,
243243 queryRef.variablesSerializersModule,
244244 )
245+
246+ private fun Flow<DataConnectBidiConnectStream.ExecuteResponse>.mapToOperationResponse ():
247+ Flow <DataConnectGrpcClient .OperationResult > = map { executeResponse ->
248+ DataConnectGrpcClient .OperationResult (
249+ data = executeResponse.data,
250+ errors = executeResponse.errors,
251+ source = DataSource .SERVER ,
252+ )
253+ }
254+
255+ private fun Flow<DataConnectBidiConnectStream.ExecuteResponse>.updateCache (
256+ cache : DataConnectCache ? ,
257+ queryId : QueryId
258+ ): Flow <DataConnectBidiConnectStream .ExecuteResponse > = onEach { response ->
259+ val cacheDb = cache?.open() ? : return @onEach
260+ val data = response.data ? : return @onEach // null indicates error
261+ cacheDb.insertQueryResult(
262+ response.authUid,
263+ queryId,
264+ data,
265+ cache.maxAgeProto,
266+ currentTimeMillis(),
267+ response.getEntityIdForPathFunction(),
268+ )
269+ }
270+
271+ @JvmName(" getEntityIdForPathFunction_DataConnectBidiConnectStream_ExecuteResponse" )
272+ private fun DataConnectBidiConnectStream.ExecuteResponse.getEntityIdForPathFunction ():
273+ GetEntityIdForPathFunction ? =
274+ if (extensions.isEmpty()) {
275+ null
276+ } else {
277+ extensions.getEntityIdForPathFunction()
278+ }
0 commit comments