1 /*
2  * Copyright (C) 2017 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 package androidx.room
17 
18 import android.annotation.SuppressLint
19 import androidx.annotation.IntDef
20 import androidx.annotation.RestrictTo
21 import androidx.annotation.VisibleForTesting
22 import androidx.sqlite.SQLiteStatement
23 import androidx.sqlite.db.SupportSQLiteProgram
24 import androidx.sqlite.db.SupportSQLiteQuery
25 import java.util.TreeMap
26 
27 /**
28  * This class is used as an intermediate place to keep binding arguments so that we can run Cursor
29  * queries with correct types rather than passing everything as a string.
30  *
31  * Because it is relatively a big object, they are pooled and must be released after each use.
32  */
33 @SuppressLint("WrongConstant")
34 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
35 class RoomSQLiteQuery private constructor(@field:VisibleForTesting val capacity: Int) :
36     SupportSQLiteQuery, SupportSQLiteProgram {
37     @Volatile private var query: String? = null
38 
39     @JvmField @VisibleForTesting val longBindings: LongArray
40 
41     @JvmField @VisibleForTesting val doubleBindings: DoubleArray
42 
43     @JvmField @VisibleForTesting val stringBindings: Array<String?>
44 
45     @JvmField @VisibleForTesting val blobBindings: Array<ByteArray?>
46 
47     @Binding private val bindingTypes: IntArray
48 
49     // number of arguments in the query
50     override var argCount = 0
51         private set
52 
initnull53     fun init(query: String, initArgCount: Int) {
54         this.query = query
55         argCount = initArgCount
56     }
57 
<lambda>null58     init {
59         // because, 1 based indices... we don't want to offsets everything with 1 all the time.
60         val limit = capacity + 1
61         bindingTypes = IntArray(limit)
62         longBindings = LongArray(limit)
63         doubleBindings = DoubleArray(limit)
64         stringBindings = arrayOfNulls(limit)
65         blobBindings = arrayOfNulls(limit)
66     }
67 
68     /**
69      * Releases the query back to the pool.
70      *
71      * After released, the statement might be returned when [.acquire] is called so you should never
72      * re-use it after releasing.
73      */
releasenull74     fun release() {
75         synchronized(queryPool) {
76             queryPool[capacity] = this
77             prunePoolLocked()
78         }
79     }
80 
81     /** Converts a SupportSQLiteStatement to a [RoomRawQuery]. */
toRoomRawQuerynull82     fun toRoomRawQuery(): RoomRawQuery {
83         return RoomRawQuery(sql = this.sql, onBindStatement = { this.bindTo(it) })
84     }
85 
86     override val sql: String
87         get() = checkNotNull(this.query)
88 
bindTonull89     override fun bindTo(statement: SupportSQLiteProgram) {
90         for (index in 1..argCount) {
91             when (bindingTypes[index]) {
92                 NULL -> statement.bindNull(index)
93                 LONG -> statement.bindLong(index, longBindings[index])
94                 DOUBLE -> statement.bindDouble(index, doubleBindings[index])
95                 STRING -> statement.bindString(index, requireNotNull(stringBindings[index]))
96                 BLOB -> statement.bindBlob(index, requireNotNull(blobBindings[index]))
97             }
98         }
99     }
100 
bindTonull101     fun bindTo(statement: SQLiteStatement) {
102         for (index in 1..argCount) {
103             when (bindingTypes[index]) {
104                 NULL -> statement.bindNull(index)
105                 LONG -> statement.bindLong(index, longBindings[index])
106                 DOUBLE -> statement.bindDouble(index, doubleBindings[index])
107                 STRING -> statement.bindText(index, requireNotNull(stringBindings[index]))
108                 BLOB -> statement.bindBlob(index, requireNotNull(blobBindings[index]))
109             }
110         }
111     }
112 
bindNullnull113     override fun bindNull(index: Int) {
114         bindingTypes[index] = NULL
115     }
116 
bindLongnull117     override fun bindLong(index: Int, value: Long) {
118         bindingTypes[index] = LONG
119         longBindings[index] = value
120     }
121 
bindDoublenull122     override fun bindDouble(index: Int, value: Double) {
123         bindingTypes[index] = DOUBLE
124         doubleBindings[index] = value
125     }
126 
bindStringnull127     override fun bindString(index: Int, value: String) {
128         bindingTypes[index] = STRING
129         stringBindings[index] = value
130     }
131 
bindTextnull132     fun bindText(index: Int, value: String) = bindString(index, value)
133 
134     override fun bindBlob(index: Int, value: ByteArray) {
135         bindingTypes[index] = BLOB
136         blobBindings[index] = value
137     }
138 
closenull139     override fun close() {
140         // no-op. not calling release because it is internal API.
141     }
142 
143     /**
144      * Copies arguments from another RoomSQLiteQuery into this query.
145      *
146      * @param other The other query, which holds the arguments to be copied.
147      */
copyArgumentsFromnull148     fun copyArgumentsFrom(other: RoomSQLiteQuery) {
149         val argCount = other.argCount + 1 // +1 for the binding offsets
150         System.arraycopy(other.bindingTypes, 0, bindingTypes, 0, argCount)
151         System.arraycopy(other.longBindings, 0, longBindings, 0, argCount)
152         System.arraycopy(other.stringBindings, 0, stringBindings, 0, argCount)
153         System.arraycopy(other.blobBindings, 0, blobBindings, 0, argCount)
154         System.arraycopy(other.doubleBindings, 0, doubleBindings, 0, argCount)
155     }
156 
clearBindingsnull157     override fun clearBindings() {
158         bindingTypes.fill(NULL)
159         stringBindings.fill(null)
160         blobBindings.fill(null)
161         query = null
162         // no need to clear others
163     }
164 
165     @Retention(AnnotationRetention.SOURCE)
166     @IntDef(NULL, LONG, DOUBLE, STRING, BLOB)
167     internal annotation class Binding
168 
169     companion object {
170         // Maximum number of queries we'll keep cached.
171         @VisibleForTesting const val POOL_LIMIT = 15
172 
173         // Once we hit POOL_LIMIT, we'll bring the pool size back to the desired number. We always
174         // clear the bigger queries (# of arguments).
175         @VisibleForTesting const val DESIRED_POOL_SIZE = 10
176 
177         @JvmField @VisibleForTesting val queryPool = TreeMap<Int, RoomSQLiteQuery>()
178 
179         /**
180          * Copies the given SupportSQLiteQuery and converts it into RoomSQLiteQuery.
181          *
182          * @param supportSQLiteQuery The query to copy from
183          * @return A new query copied from the provided one.
184          */
185         @JvmStatic
copyFromnull186         fun copyFrom(supportSQLiteQuery: SupportSQLiteQuery): RoomSQLiteQuery {
187             val query = acquire(supportSQLiteQuery.sql, supportSQLiteQuery.argCount)
188 
189             supportSQLiteQuery.bindTo(object : SupportSQLiteProgram by query {})
190             return query
191         }
192 
193         /**
194          * Returns a new RoomSQLiteQuery that can accept the given number of arguments and holds the
195          * given query.
196          *
197          * @param query The query to prepare
198          * @param argumentCount The number of query arguments
199          * @return A RoomSQLiteQuery that holds the given query and has space for the given number
200          *   of arguments.
201          */
202         @JvmStatic
acquirenull203         fun acquire(query: String, argumentCount: Int): RoomSQLiteQuery {
204             synchronized(queryPool) {
205                 val entry = queryPool.ceilingEntry(argumentCount)
206                 if (entry != null) {
207                     queryPool.remove(entry.key)
208                     val sqliteQuery = entry.value
209                     sqliteQuery.init(query, argumentCount)
210                     return sqliteQuery
211                 }
212             }
213             val sqLiteQuery = RoomSQLiteQuery(argumentCount)
214             sqLiteQuery.init(query, argumentCount)
215             return sqLiteQuery
216         }
217 
prunePoolLockednull218         internal fun prunePoolLocked() {
219             if (queryPool.size > POOL_LIMIT) {
220                 var toBeRemoved = queryPool.size - DESIRED_POOL_SIZE
221                 val iterator = queryPool.descendingKeySet().iterator()
222                 while (toBeRemoved-- > 0) {
223                     iterator.next()
224                     iterator.remove()
225                 }
226             }
227         }
228 
229         private const val NULL = 1
230         private const val LONG = 2
231         private const val DOUBLE = 3
232         private const val STRING = 4
233         private const val BLOB = 5
234     }
235 }
236