blob: a8defd48f42e4d1221443c8a326bff2a98ee73f0 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.arch.persistence.room;
import android.arch.persistence.db.SupportSQLiteProgram;
import android.arch.persistence.db.SupportSQLiteQuery;
import android.support.annotation.IntDef;
import android.support.annotation.RestrictTo;
import android.support.annotation.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
/**
* This class is used as an intermediate place to keep binding arguments so that we can run
* Cursor queries with correct types rather than passing everything as a string.
* <p>
* Because it is relatively a big object, they are pooled and must be released after each use.
*
* @hide
*/
@SuppressWarnings("unused")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class RoomSQLiteQuery implements SupportSQLiteQuery, SupportSQLiteProgram {
@SuppressWarnings("WeakerAccess")
@VisibleForTesting
// Maximum number of queries we'll keep cached.
static final int POOL_LIMIT = 15;
@SuppressWarnings("WeakerAccess")
@VisibleForTesting
// Once we hit POOL_LIMIT, we'll bring the pool size back to the desired number. We always
// clear the bigger queries (# of arguments).
static final int DESIRED_POOL_SIZE = 10;
private volatile String mQuery;
@SuppressWarnings("WeakerAccess")
@VisibleForTesting
final long[] mLongBindings;
@SuppressWarnings("WeakerAccess")
@VisibleForTesting
final double[] mDoubleBindings;
@SuppressWarnings("WeakerAccess")
@VisibleForTesting
final String[] mStringBindings;
@SuppressWarnings("WeakerAccess")
@VisibleForTesting
final byte[][] mBlobBindings;
@Binding
private final int[] mBindingTypes;
@SuppressWarnings("WeakerAccess")
@VisibleForTesting
final int mCapacity;
// number of arguments in the query
@SuppressWarnings("WeakerAccess")
@VisibleForTesting
int mArgCount;
@SuppressWarnings("WeakerAccess")
@VisibleForTesting
static final TreeMap<Integer, RoomSQLiteQuery> sQueryPool = new TreeMap<>();
/**
* Returns a new RoomSQLiteQuery that can accept the given number of arguments and holds the
* given query.
*
* @param query The query to prepare
* @param argumentCount The number of query arguments
* @return A RoomSQLiteQuery that holds the given query and has space for the given number of
* arguments.
*/
@SuppressWarnings("WeakerAccess")
public static RoomSQLiteQuery acquire(String query, int argumentCount) {
synchronized (sQueryPool) {
final Map.Entry<Integer, RoomSQLiteQuery> entry =
sQueryPool.ceilingEntry(argumentCount);
if (entry != null) {
sQueryPool.remove(entry.getKey());
final RoomSQLiteQuery sqliteQuery = entry.getValue();
sqliteQuery.init(query, argumentCount);
return sqliteQuery;
}
}
RoomSQLiteQuery sqLiteQuery = new RoomSQLiteQuery(argumentCount);
sqLiteQuery.init(query, argumentCount);
return sqLiteQuery;
}
private RoomSQLiteQuery(int capacity) {
mCapacity = capacity;
// because, 1 based indices... we don't want to offsets everything with 1 all the time.
int limit = capacity + 1;
//noinspection WrongConstant
mBindingTypes = new int[limit];
mLongBindings = new long[limit];
mDoubleBindings = new double[limit];
mStringBindings = new String[limit];
mBlobBindings = new byte[limit][];
}
@SuppressWarnings("WeakerAccess")
void init(String query, int argCount) {
mQuery = query;
mArgCount = argCount;
}
/**
* Releases the query back to the pool.
* <p>
* After released, the statement might be returned when {@link #acquire(String, int)} is called
* so you should never re-use it after releasing.
*/
@SuppressWarnings("WeakerAccess")
public void release() {
synchronized (sQueryPool) {
sQueryPool.put(mCapacity, this);
prunePoolLocked();
}
}
private static void prunePoolLocked() {
if (sQueryPool.size() > POOL_LIMIT) {
int toBeRemoved = sQueryPool.size() - DESIRED_POOL_SIZE;
final Iterator<Integer> iterator = sQueryPool.descendingKeySet().iterator();
while (toBeRemoved-- > 0) {
iterator.next();
iterator.remove();
}
}
}
@Override
public String getSql() {
return mQuery;
}
public int getArgCount() {
return mArgCount;
}
@Override
public void bindTo(SupportSQLiteProgram program) {
for (int index = 1; index <= mArgCount; index++) {
switch (mBindingTypes[index]) {
case NULL:
program.bindNull(index);
break;
case LONG:
program.bindLong(index, mLongBindings[index]);
break;
case DOUBLE:
program.bindDouble(index, mDoubleBindings[index]);
break;
case STRING:
program.bindString(index, mStringBindings[index]);
break;
case BLOB:
program.bindBlob(index, mBlobBindings[index]);
break;
}
}
}
@Override
public void bindNull(int index) {
mBindingTypes[index] = NULL;
}
@Override
public void bindLong(int index, long value) {
mBindingTypes[index] = LONG;
mLongBindings[index] = value;
}
@Override
public void bindDouble(int index, double value) {
mBindingTypes[index] = DOUBLE;
mDoubleBindings[index] = value;
}
@Override
public void bindString(int index, String value) {
mBindingTypes[index] = STRING;
mStringBindings[index] = value;
}
@Override
public void bindBlob(int index, byte[] value) {
mBindingTypes[index] = BLOB;
mBlobBindings[index] = value;
}
@Override
public void close() throws Exception {
// no-op. not calling release because it is internal API.
}
/**
* Copies arguments from another RoomSQLiteQuery into this query.
*
* @param other The other query, which holds the arguments to be copied.
*/
public void copyArgumentsFrom(RoomSQLiteQuery other) {
int argCount = other.getArgCount() + 1; // +1 for the binding offsets
System.arraycopy(other.mBindingTypes, 0, mBindingTypes, 0, argCount);
System.arraycopy(other.mLongBindings, 0, mLongBindings, 0, argCount);
System.arraycopy(other.mStringBindings, 0, mStringBindings, 0, argCount);
System.arraycopy(other.mBlobBindings, 0, mBlobBindings, 0, argCount);
System.arraycopy(other.mDoubleBindings, 0, mDoubleBindings, 0, argCount);
}
@Override
public void clearBindings() {
Arrays.fill(mBindingTypes, NULL);
Arrays.fill(mStringBindings, null);
Arrays.fill(mBlobBindings, null);
mQuery = null;
// no need to clear others
}
private static final int NULL = 1;
private static final int LONG = 2;
private static final int DOUBLE = 3;
private static final int STRING = 4;
private static final int BLOB = 5;
@Retention(RetentionPolicy.SOURCE)
@IntDef({NULL, LONG, DOUBLE, STRING, BLOB})
@interface Binding {
}
}