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 17 package androidx.room; 18 19 import androidx.annotation.IntDef; 20 import androidx.annotation.RestrictTo; 21 import androidx.annotation.VisibleForTesting; 22 import androidx.sqlite.db.SupportSQLiteProgram; 23 import androidx.sqlite.db.SupportSQLiteQuery; 24 25 import java.lang.annotation.Retention; 26 import java.lang.annotation.RetentionPolicy; 27 import java.util.Arrays; 28 import java.util.Iterator; 29 import java.util.Map; 30 import java.util.TreeMap; 31 32 /** 33 * This class is used as an intermediate place to keep binding arguments so that we can run 34 * Cursor queries with correct types rather than passing everything as a string. 35 * <p> 36 * Because it is relatively a big object, they are pooled and must be released after each use. 37 * 38 * @hide 39 */ 40 @SuppressWarnings("unused") 41 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 42 public class RoomSQLiteQuery implements SupportSQLiteQuery, SupportSQLiteProgram { 43 @SuppressWarnings("WeakerAccess") 44 @VisibleForTesting 45 // Maximum number of queries we'll keep cached. 46 static final int POOL_LIMIT = 15; 47 @SuppressWarnings("WeakerAccess") 48 @VisibleForTesting 49 // Once we hit POOL_LIMIT, we'll bring the pool size back to the desired number. We always 50 // clear the bigger queries (# of arguments). 51 static final int DESIRED_POOL_SIZE = 10; 52 private volatile String mQuery; 53 @SuppressWarnings("WeakerAccess") 54 @VisibleForTesting 55 final long[] mLongBindings; 56 @SuppressWarnings("WeakerAccess") 57 @VisibleForTesting 58 final double[] mDoubleBindings; 59 @SuppressWarnings("WeakerAccess") 60 @VisibleForTesting 61 final String[] mStringBindings; 62 @SuppressWarnings("WeakerAccess") 63 @VisibleForTesting 64 final byte[][] mBlobBindings; 65 66 @Binding 67 private final int[] mBindingTypes; 68 @SuppressWarnings("WeakerAccess") 69 @VisibleForTesting 70 final int mCapacity; 71 // number of arguments in the query 72 @SuppressWarnings("WeakerAccess") 73 @VisibleForTesting 74 int mArgCount; 75 76 77 @SuppressWarnings("WeakerAccess") 78 @VisibleForTesting 79 static final TreeMap<Integer, RoomSQLiteQuery> sQueryPool = new TreeMap<>(); 80 81 /** 82 * Copies the given SupportSQLiteQuery and converts it into RoomSQLiteQuery. 83 * 84 * @param supportSQLiteQuery The query to copy from 85 * @return A new query copied from the provided one. 86 */ 87 public static RoomSQLiteQuery copyFrom(SupportSQLiteQuery supportSQLiteQuery) { 88 final RoomSQLiteQuery query = RoomSQLiteQuery.acquire( 89 supportSQLiteQuery.getSql(), 90 supportSQLiteQuery.getArgCount()); 91 supportSQLiteQuery.bindTo(new SupportSQLiteProgram() { 92 @Override 93 public void bindNull(int index) { 94 query.bindNull(index); 95 } 96 97 @Override 98 public void bindLong(int index, long value) { 99 query.bindLong(index, value); 100 } 101 102 @Override 103 public void bindDouble(int index, double value) { 104 query.bindDouble(index, value); 105 } 106 107 @Override 108 public void bindString(int index, String value) { 109 query.bindString(index, value); 110 } 111 112 @Override 113 public void bindBlob(int index, byte[] value) { 114 query.bindBlob(index, value); 115 } 116 117 @Override 118 public void clearBindings() { 119 query.clearBindings(); 120 } 121 122 @Override 123 public void close() { 124 // ignored. 125 } 126 }); 127 return query; 128 } 129 130 /** 131 * Returns a new RoomSQLiteQuery that can accept the given number of arguments and holds the 132 * given query. 133 * 134 * @param query The query to prepare 135 * @param argumentCount The number of query arguments 136 * @return A RoomSQLiteQuery that holds the given query and has space for the given number of 137 * arguments. 138 */ 139 @SuppressWarnings("WeakerAccess") 140 public static RoomSQLiteQuery acquire(String query, int argumentCount) { 141 synchronized (sQueryPool) { 142 final Map.Entry<Integer, RoomSQLiteQuery> entry = 143 sQueryPool.ceilingEntry(argumentCount); 144 if (entry != null) { 145 sQueryPool.remove(entry.getKey()); 146 final RoomSQLiteQuery sqliteQuery = entry.getValue(); 147 sqliteQuery.init(query, argumentCount); 148 return sqliteQuery; 149 } 150 } 151 RoomSQLiteQuery sqLiteQuery = new RoomSQLiteQuery(argumentCount); 152 sqLiteQuery.init(query, argumentCount); 153 return sqLiteQuery; 154 } 155 156 private RoomSQLiteQuery(int capacity) { 157 mCapacity = capacity; 158 // because, 1 based indices... we don't want to offsets everything with 1 all the time. 159 int limit = capacity + 1; 160 //noinspection WrongConstant 161 mBindingTypes = new int[limit]; 162 mLongBindings = new long[limit]; 163 mDoubleBindings = new double[limit]; 164 mStringBindings = new String[limit]; 165 mBlobBindings = new byte[limit][]; 166 } 167 168 @SuppressWarnings("WeakerAccess") 169 void init(String query, int argCount) { 170 mQuery = query; 171 mArgCount = argCount; 172 } 173 174 /** 175 * Releases the query back to the pool. 176 * <p> 177 * After released, the statement might be returned when {@link #acquire(String, int)} is called 178 * so you should never re-use it after releasing. 179 */ 180 @SuppressWarnings("WeakerAccess") 181 public void release() { 182 synchronized (sQueryPool) { 183 sQueryPool.put(mCapacity, this); 184 prunePoolLocked(); 185 } 186 } 187 188 private static void prunePoolLocked() { 189 if (sQueryPool.size() > POOL_LIMIT) { 190 int toBeRemoved = sQueryPool.size() - DESIRED_POOL_SIZE; 191 final Iterator<Integer> iterator = sQueryPool.descendingKeySet().iterator(); 192 while (toBeRemoved-- > 0) { 193 iterator.next(); 194 iterator.remove(); 195 } 196 } 197 } 198 199 @Override 200 public String getSql() { 201 return mQuery; 202 } 203 204 @Override 205 public int getArgCount() { 206 return mArgCount; 207 } 208 209 @Override 210 public void bindTo(SupportSQLiteProgram program) { 211 for (int index = 1; index <= mArgCount; index++) { 212 switch (mBindingTypes[index]) { 213 case NULL: 214 program.bindNull(index); 215 break; 216 case LONG: 217 program.bindLong(index, mLongBindings[index]); 218 break; 219 case DOUBLE: 220 program.bindDouble(index, mDoubleBindings[index]); 221 break; 222 case STRING: 223 program.bindString(index, mStringBindings[index]); 224 break; 225 case BLOB: 226 program.bindBlob(index, mBlobBindings[index]); 227 break; 228 } 229 } 230 } 231 232 @Override 233 public void bindNull(int index) { 234 mBindingTypes[index] = NULL; 235 } 236 237 @Override 238 public void bindLong(int index, long value) { 239 mBindingTypes[index] = LONG; 240 mLongBindings[index] = value; 241 } 242 243 @Override 244 public void bindDouble(int index, double value) { 245 mBindingTypes[index] = DOUBLE; 246 mDoubleBindings[index] = value; 247 } 248 249 @Override 250 public void bindString(int index, String value) { 251 mBindingTypes[index] = STRING; 252 mStringBindings[index] = value; 253 } 254 255 @Override 256 public void bindBlob(int index, byte[] value) { 257 mBindingTypes[index] = BLOB; 258 mBlobBindings[index] = value; 259 } 260 261 @Override 262 public void close() { 263 // no-op. not calling release because it is internal API. 264 } 265 266 /** 267 * Copies arguments from another RoomSQLiteQuery into this query. 268 * 269 * @param other The other query, which holds the arguments to be copied. 270 */ 271 public void copyArgumentsFrom(RoomSQLiteQuery other) { 272 int argCount = other.getArgCount() + 1; // +1 for the binding offsets 273 System.arraycopy(other.mBindingTypes, 0, mBindingTypes, 0, argCount); 274 System.arraycopy(other.mLongBindings, 0, mLongBindings, 0, argCount); 275 System.arraycopy(other.mStringBindings, 0, mStringBindings, 0, argCount); 276 System.arraycopy(other.mBlobBindings, 0, mBlobBindings, 0, argCount); 277 System.arraycopy(other.mDoubleBindings, 0, mDoubleBindings, 0, argCount); 278 } 279 280 @Override 281 public void clearBindings() { 282 Arrays.fill(mBindingTypes, NULL); 283 Arrays.fill(mStringBindings, null); 284 Arrays.fill(mBlobBindings, null); 285 mQuery = null; 286 // no need to clear others 287 } 288 289 private static final int NULL = 1; 290 private static final int LONG = 2; 291 private static final int DOUBLE = 3; 292 private static final int STRING = 4; 293 private static final int BLOB = 5; 294 295 @Retention(RetentionPolicy.SOURCE) 296 @IntDef({NULL, LONG, DOUBLE, STRING, BLOB}) 297 @interface Binding { 298 } 299 } 300