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 17package android.arch.persistence.room; 18 19import android.arch.persistence.db.SupportSQLiteProgram; 20import android.arch.persistence.db.SupportSQLiteQuery; 21import android.support.annotation.IntDef; 22import android.support.annotation.RestrictTo; 23import android.support.annotation.VisibleForTesting; 24 25import java.lang.annotation.Retention; 26import java.lang.annotation.RetentionPolicy; 27import java.util.Arrays; 28import java.util.Iterator; 29import java.util.Map; 30import 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) 42public 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 * Returns a new RoomSQLiteQuery that can accept the given number of arguments and holds the 83 * given query. 84 * 85 * @param query The query to prepare 86 * @param argumentCount The number of query arguments 87 * @return A RoomSQLiteQuery that holds the given query and has space for the given number of 88 * arguments. 89 */ 90 @SuppressWarnings("WeakerAccess") 91 public static RoomSQLiteQuery acquire(String query, int argumentCount) { 92 synchronized (sQueryPool) { 93 final Map.Entry<Integer, RoomSQLiteQuery> entry = 94 sQueryPool.ceilingEntry(argumentCount); 95 if (entry != null) { 96 sQueryPool.remove(entry.getKey()); 97 final RoomSQLiteQuery sqliteQuery = entry.getValue(); 98 sqliteQuery.init(query, argumentCount); 99 return sqliteQuery; 100 } 101 } 102 RoomSQLiteQuery sqLiteQuery = new RoomSQLiteQuery(argumentCount); 103 sqLiteQuery.init(query, argumentCount); 104 return sqLiteQuery; 105 } 106 107 private RoomSQLiteQuery(int capacity) { 108 mCapacity = capacity; 109 // because, 1 based indices... we don't want to offsets everything with 1 all the time. 110 int limit = capacity + 1; 111 //noinspection WrongConstant 112 mBindingTypes = new int[limit]; 113 mLongBindings = new long[limit]; 114 mDoubleBindings = new double[limit]; 115 mStringBindings = new String[limit]; 116 mBlobBindings = new byte[limit][]; 117 } 118 119 @SuppressWarnings("WeakerAccess") 120 void init(String query, int argCount) { 121 mQuery = query; 122 mArgCount = argCount; 123 } 124 125 /** 126 * Releases the query back to the pool. 127 * <p> 128 * After released, the statement might be returned when {@link #acquire(String, int)} is called 129 * so you should never re-use it after releasing. 130 */ 131 @SuppressWarnings("WeakerAccess") 132 public void release() { 133 synchronized (sQueryPool) { 134 sQueryPool.put(mCapacity, this); 135 prunePoolLocked(); 136 } 137 } 138 139 private static void prunePoolLocked() { 140 if (sQueryPool.size() > POOL_LIMIT) { 141 int toBeRemoved = sQueryPool.size() - DESIRED_POOL_SIZE; 142 final Iterator<Integer> iterator = sQueryPool.descendingKeySet().iterator(); 143 while (toBeRemoved-- > 0) { 144 iterator.next(); 145 iterator.remove(); 146 } 147 } 148 } 149 150 @Override 151 public String getSql() { 152 return mQuery; 153 } 154 155 public int getArgCount() { 156 return mArgCount; 157 } 158 159 @Override 160 public void bindTo(SupportSQLiteProgram program) { 161 for (int index = 1; index <= mArgCount; index++) { 162 switch (mBindingTypes[index]) { 163 case NULL: 164 program.bindNull(index); 165 break; 166 case LONG: 167 program.bindLong(index, mLongBindings[index]); 168 break; 169 case DOUBLE: 170 program.bindDouble(index, mDoubleBindings[index]); 171 break; 172 case STRING: 173 program.bindString(index, mStringBindings[index]); 174 break; 175 case BLOB: 176 program.bindBlob(index, mBlobBindings[index]); 177 break; 178 } 179 } 180 } 181 182 @Override 183 public void bindNull(int index) { 184 mBindingTypes[index] = NULL; 185 } 186 187 @Override 188 public void bindLong(int index, long value) { 189 mBindingTypes[index] = LONG; 190 mLongBindings[index] = value; 191 } 192 193 @Override 194 public void bindDouble(int index, double value) { 195 mBindingTypes[index] = DOUBLE; 196 mDoubleBindings[index] = value; 197 } 198 199 @Override 200 public void bindString(int index, String value) { 201 mBindingTypes[index] = STRING; 202 mStringBindings[index] = value; 203 } 204 205 @Override 206 public void bindBlob(int index, byte[] value) { 207 mBindingTypes[index] = BLOB; 208 mBlobBindings[index] = value; 209 } 210 211 @Override 212 public void close() throws Exception { 213 // no-op. not calling release because it is internal API. 214 } 215 216 /** 217 * Copies arguments from another RoomSQLiteQuery into this query. 218 * 219 * @param other The other query, which holds the arguments to be copied. 220 */ 221 public void copyArgumentsFrom(RoomSQLiteQuery other) { 222 int argCount = other.getArgCount() + 1; // +1 for the binding offsets 223 System.arraycopy(other.mBindingTypes, 0, mBindingTypes, 0, argCount); 224 System.arraycopy(other.mLongBindings, 0, mLongBindings, 0, argCount); 225 System.arraycopy(other.mStringBindings, 0, mStringBindings, 0, argCount); 226 System.arraycopy(other.mBlobBindings, 0, mBlobBindings, 0, argCount); 227 System.arraycopy(other.mDoubleBindings, 0, mDoubleBindings, 0, argCount); 228 } 229 230 @Override 231 public void clearBindings() { 232 Arrays.fill(mBindingTypes, NULL); 233 Arrays.fill(mStringBindings, null); 234 Arrays.fill(mBlobBindings, null); 235 mQuery = null; 236 // no need to clear others 237 } 238 239 private static final int NULL = 1; 240 private static final int LONG = 2; 241 private static final int DOUBLE = 3; 242 private static final int STRING = 4; 243 private static final int BLOB = 5; 244 245 @Retention(RetentionPolicy.SOURCE) 246 @IntDef({NULL, LONG, DOUBLE, STRING, BLOB}) 247 @interface Binding { 248 } 249} 250