1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 @file:JvmMultifileClass
18 @file:JvmName("RelationUtil")
19 
20 package androidx.room.util
21 
22 import androidx.annotation.RestrictTo
23 import androidx.collection.LongSparseArray
24 import kotlin.jvm.JvmMultifileClass
25 import kotlin.jvm.JvmName
26 
27 /**
28  * Utility function used in generated code to recursively fetch relationships when the amount of
29  * keys exceed [MAX_BIND_PARAMETER_CNT].
30  *
31  * @param map - The map containing the relationship keys to fill-in.
32  * @param isRelationCollection - True if [V] is a [Collection] which means it is non null.
33  * @param fetchBlock - A lambda for calling the generated _fetchRelationship function.
34  */
35 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
recursiveFetchMapnull36 fun <K : Any, V> recursiveFetchMap(
37     map: MutableMap<K, V>,
38     isRelationCollection: Boolean,
39     fetchBlock: (MutableMap<K, V>) -> Unit
40 ) {
41     val tmpMap = mutableMapOf<K, V>()
42     var count = 0
43     for (key in map.keys) {
44         // Safe because `V` is a nullable type arg when isRelationCollection == false and vice versa
45         @Suppress("UNCHECKED_CAST")
46         if (isRelationCollection) {
47             tmpMap[key] = map[key] as V
48         } else {
49             tmpMap[key] = null as V
50         }
51         count++
52         if (count == MAX_BIND_PARAMETER_CNT) {
53             // recursively load that batch
54             fetchBlock(tmpMap)
55             // for non collection relation, put the loaded batch in the original map,
56             // not needed when dealing with collections since references are passed
57             if (!isRelationCollection) {
58                 map.putAll(tmpMap)
59             }
60             tmpMap.clear()
61             count = 0
62         }
63     }
64     if (count > 0) {
65         // load the last batch
66         fetchBlock(tmpMap)
67         // for non collection relation, put the last batch in the original map
68         if (!isRelationCollection) {
69             map.putAll(tmpMap)
70         }
71     }
72 }
73 
74 /** Same as [recursiveFetchMap] but for [LongSparseArray]. */
75 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
recursiveFetchLongSparseArraynull76 fun <V> recursiveFetchLongSparseArray(
77     map: LongSparseArray<V>,
78     isRelationCollection: Boolean,
79     fetchBlock: (LongSparseArray<V>) -> Unit
80 ) {
81     val tmpMap = LongSparseArray<V>(MAX_BIND_PARAMETER_CNT)
82     var count = 0
83     var mapIndex = 0
84     val limit = map.size()
85     while (mapIndex < limit) {
86         if (isRelationCollection) {
87             tmpMap.put(map.keyAt(mapIndex), map.valueAt(mapIndex))
88         } else {
89             // Safe because `V` is a nullable type arg when isRelationCollection == false
90             @Suppress("UNCHECKED_CAST") tmpMap.put(map.keyAt(mapIndex), null as V)
91         }
92         mapIndex++
93         count++
94         if (count == MAX_BIND_PARAMETER_CNT) {
95             fetchBlock(tmpMap)
96             if (!isRelationCollection) {
97                 map.putAll(tmpMap)
98             }
99             tmpMap.clear()
100             count = 0
101         }
102     }
103     if (count > 0) {
104         fetchBlock(tmpMap)
105         if (!isRelationCollection) {
106             map.putAll(tmpMap)
107         }
108     }
109 }
110 
111 /** Unfortunately, we cannot read this value so we are only setting it to the SQLite default. */
112 internal const val MAX_BIND_PARAMETER_CNT = 999
113