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