Home | History | Annotate | Download | only in room
      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