package droidkit.sqlite; import android.annotation.SuppressLint; import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; import java.util.ArrayList; import java.util.List; import droidkit.io.IOUtils; /** * @author Daniel Serdyukov */ public class SQLiteQuery<T> { private static final String ASC = " ASC"; private static final String DESC = " DESC"; private static final String AND = " AND "; private static final String OR = " OR "; private static final String COMMA = ", "; private static final String EQ = " = ?"; private static final String NOT_EQ = " <> ?"; private static final String LT = " < ?"; private static final String LT_OR_EQ = " <= ?"; private static final String GT = " > ?"; private static final String GT_OR_EQ = " >= ?"; private static final String BETWEEN = " BETWEEN ? AND ?"; private static final String LIKE = " LIKE ?"; private static final String MAX = "MAX"; private static final String MIN = "MIN"; private static final String SUM = "SUM"; private static final String COUNT = "COUNT"; private static final String DISTINCT = "DISTINCT "; private static final String TRUE = "1"; private static final String FALSE = "0"; private static final SQLiteFunc<Integer> INT_FUNC = new SQLiteFunc<Integer>() { @Override public Integer apply(@NonNull Cursor cursor, @NonNull String column) { return cursor.getInt(0); } }; private static final SQLiteFunc<Long> LONG_FUNC = new SQLiteFunc<Long>() { @Override public Long apply(@NonNull Cursor cursor, @NonNull String column) { return cursor.getLong(0); } }; private static final SQLiteFunc<Double> DOUBLE_FUNC = new SQLiteFunc<Double>() { @Override public Double apply(@NonNull Cursor cursor, @NonNull String column) { return cursor.getDouble(0); } }; private final ContentResolver mDb; private final Uri mUri; private final SQLiteTable<T> mTable; private final StringBuilder mWhere = new StringBuilder(); private final List<String> mOrderBy = new ArrayList<>(); private final List<String> mWhereArgs = new ArrayList<>(); private String mGroupBy; private String mHaving; private long mLimit; private boolean mHasGroupBayOrHavingOrLimit; SQLiteQuery(@NonNull ContentResolver db, @NonNull Uri uri, @NonNull SQLiteTable<T> table) { mDb = db; mUri = uri; mTable = table; } @NonNull public SQLiteQuery<T> groupBy(@NonNull String column) { mGroupBy = column; mHasGroupBayOrHavingOrLimit = true; return this; } @NonNull public SQLiteQuery<T> having(@NonNull String value) { mHaving = value; mHasGroupBayOrHavingOrLimit = true; return this; } @NonNull public SQLiteQuery<T> orderBy(@NonNull String column) { return orderBy(column, true); } @NonNull public SQLiteQuery<T> orderBy(@NonNull String column, boolean ascending) { mOrderBy.add(column + (ascending ? ASC : DESC)); return this; } @NonNull public SQLiteQuery<T> limit(long limit) { mLimit = limit; mHasGroupBayOrHavingOrLimit = true; return this; } @NonNull public SQLiteQuery<T> equalTo(@NonNull String column, @NonNull Object value) { return appendWhere(column, EQ, value); } @NonNull public SQLiteQuery<T> notEqualTo(@NonNull String column, @NonNull Object value) { return appendWhere(column, NOT_EQ, value); } @NonNull public SQLiteQuery<T> lessThan(@NonNull String column, @NonNull Object value) { return appendWhere(column, LT, value); } @NonNull public SQLiteQuery<T> lessThanOrEqualTo(@NonNull String column, @NonNull Object value) { return appendWhere(column, LT_OR_EQ, value); } @NonNull public SQLiteQuery<T> greaterThan(@NonNull String column, @NonNull Object value) { return appendWhere(column, GT, value); } @NonNull public SQLiteQuery<T> greaterThanOrEqualTo(@NonNull String column, @NonNull Object value) { return appendWhere(column, GT_OR_EQ, value); } @NonNull public SQLiteQuery<T> between(@NonNull String column, @NonNull Object value1, @NonNull Object value2) { return appendWhere(column, BETWEEN, value1, value2); } @NonNull public SQLiteQuery<T> like(@NonNull String column, @NonNull Object value) { return appendWhere(column, LIKE, value); } @NonNull public SQLiteQuery<T> inSelect(@NonNull String column, @NonNull String select) { return appendWhere(column, " IN(" + select + ")"); } @NonNull public SQLiteQuery<T> and() { mWhere.append(AND); return this; } @NonNull public SQLiteQuery<T> or() { mWhere.append(OR); return this; } @NonNull public SQLiteResult<T> all() { @SuppressLint("Recycle") final Cursor cursor = mDb.query(makeQueryUri(), null, where(), bindArgs(), TextUtils.join(COMMA, mOrderBy)); cursor.setNotificationUri(mDb, mUri); return new SQLiteResult<>(mTable, cursor); } @Nullable public T first() { final SQLiteResult<T> result = all(); try { if (!result.isEmpty()) { return result.get(0); } } finally { IOUtils.closeQuietly(result); } return null; } @Nullable public T last() { final SQLiteResult<T> result = all(); try { if (!result.isEmpty()) { return result.get(result.size() - 1); } } finally { IOUtils.closeQuietly(result); } return null; } public int remove() { return mDb.delete(makeQueryUri(), where(), bindArgs()); } public int maxInt(@NonNull String column) { return applyFunc(INT_FUNC, MAX, column, 0); } public long maxLong(@NonNull String column) { return applyFunc(LONG_FUNC, MAX, column, 0L); } public double maxDouble(@NonNull String column) { return applyFunc(DOUBLE_FUNC, MAX, column, 0.0); } public int minInt(@NonNull String column) { return applyFunc(INT_FUNC, MIN, column, 0); } public long minLong(@NonNull String column) { return applyFunc(LONG_FUNC, MIN, column, 0L); } public double minDouble(@NonNull String column) { return applyFunc(DOUBLE_FUNC, MIN, column, 0.0); } public int sumInt(@NonNull String column) { return applyFunc(INT_FUNC, SUM, column, 0); } public long sumLong(@NonNull String column) { return applyFunc(LONG_FUNC, SUM, column, 0L); } public double sumDouble(@NonNull String column) { return applyFunc(DOUBLE_FUNC, SUM, column, 0.0); } public long count(@NonNull String column) { return applyFunc(LONG_FUNC, COUNT, column, 0L); } public long countDistinct(@NonNull String column) { return applyFunc(LONG_FUNC, COUNT, DISTINCT + column, 0L); } @NonNull public Uri getUri() { return mUri; } @NonNull private Uri makeQueryUri() { Uri uri = mUri; if (mHasGroupBayOrHavingOrLimit) { final Uri.Builder builder = mUri.buildUpon(); if (mGroupBy != null) { builder.appendQueryParameter(SQLiteProvider.GROUP_BY, mGroupBy); } if (mHaving != null) { builder.appendQueryParameter(SQLiteProvider.HAVING, mHaving); } if (mLimit > 0) { builder.appendQueryParameter(SQLiteProvider.LIMIT, String.valueOf(mLimit)); } uri = builder.build(); } return uri; } private SQLiteQuery<T> appendWhere(@NonNull String column, @NonNull String operand, @NonNull Object... values) { mWhere.append(column).append(operand); for (final Object value : values) { if (value instanceof Boolean) { mWhereArgs.add(((boolean) value) ? TRUE : FALSE); } else { mWhereArgs.add(String.valueOf(value)); } } return this; } @Nullable private String where() { if (mWhere.length() > 0) { return mWhere.toString(); } return null; } @Nullable private String[] bindArgs() { if (!mWhereArgs.isEmpty()) { return mWhereArgs.toArray(new String[mWhereArgs.size()]); } return null; } @NonNull private <F> F applyFunc(@NonNull SQLiteFunc<F> func, @NonNull String function, @NonNull String column, @NonNull F defaultValue) { final Cursor cursor = mDb.query(makeQueryUri(), new String[]{function + "(" + column + ")"}, where(), bindArgs(), TextUtils.join(COMMA, mOrderBy)); try { if (cursor.moveToFirst()) { return func.apply(cursor, column); } } finally { IOUtils.closeQuietly(cursor); } return defaultValue; } }