package org.wikipedia.database;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import org.wikipedia.database.column.Column;
import org.wikipedia.util.ArrayUtils;
import org.wikipedia.util.log.L;
public abstract class DatabaseTable<T> {
protected static final int INITIAL_DB_VERSION = 1;
@NonNull private final String tableName;
@NonNull private final Uri baseContentURI;
public DatabaseTable(@NonNull String tableName, @NonNull Uri baseContentURI) {
this.tableName = tableName;
this.baseContentURI = baseContentURI;
}
public abstract T fromCursor(Cursor c);
protected abstract ContentValues toContentValues(T obj);
@NonNull public String getTableName() {
return tableName;
}
@NonNull public Column<?>[] getColumnsAdded(int version) {
return new Column<?>[0];
}
public ContentProviderClient acquireClient(@NonNull Context context) {
return context.getContentResolver().acquireContentProviderClient(getBaseContentURI());
}
/**
* Get the db query string to be passed to the content provider where selecting for a null
* value (including, notably, the main namespace) may be necessary.
* @param obj The object on which the formatting of the string depends.
* @return A SQL WHERE clause formatted for the content provider.
*/
protected String getPrimaryKeySelection(@NonNull T obj, @NonNull String[] selectionKeys) {
String primaryKeySelection = "";
String[] args = getUnfilteredPrimaryKeySelectionArgs(obj);
for (int i = 0; i < args.length; i++) {
primaryKeySelection += (selectionKeys[i] + (args[i] == null ? " IS NULL" : " = ?"));
if (i < (args.length - 1)) {
primaryKeySelection += " AND ";
}
}
return primaryKeySelection;
}
/**
* Get the selection arguments to be bound to the db query string.
* @param obj The object from which selection args are derived.
* @return The array of selection arguments with null values removed. (Null arguments are
* replaced with "IS NULL" in getPrimaryKeySelection(T obj, String[] selectionKeys).)
*/
public String[] getPrimaryKeySelectionArgs(@NonNull T obj) {
return ArrayUtils.removeAllOccurences(getUnfilteredPrimaryKeySelectionArgs(obj), null);
}
/**
* Override to provide full list of selection arguments, including those which may have null
* values, for use in constructing the SQL query string.
* @param obj Object from which selection args are to be derived.
* @return Array of selection arguments (including null values).
*/
protected abstract String[] getUnfilteredPrimaryKeySelectionArgs(@NonNull T obj);
protected abstract int getDBVersionIntroducedAt();
protected int getDBVersionDroppedAt() {
return 0;
}
public void upgradeSchema(@NonNull SQLiteDatabase db, int fromVersion, int toVersion) {
if (fromVersion < getDBVersionIntroducedAt()) {
createTables(db);
}
for (int ver = Math.max(getDBVersionIntroducedAt(), fromVersion) + 1; ver <= toVersion; ++ver) {
L.i("ver=" + ver);
if (ver == getDBVersionDroppedAt()) {
dropTable(db);
break;
}
for (Column<?> column : getColumnsAdded(ver)) {
String alterTableString = "ALTER TABLE " + tableName + " ADD COLUMN " + column;
L.i(alterTableString);
db.execSQL(alterTableString);
}
upgradeSchema(db, ver);
}
}
@NonNull public Uri getBaseContentURI() {
return baseContentURI;
}
protected void upgradeSchema(@NonNull SQLiteDatabase db, int toVersion) {
}
private void createTables(@NonNull SQLiteDatabase db) {
L.i("Creating table=" + getTableName());
Column<?>[] cols = getColumnsAdded(getDBVersionIntroducedAt());
db.execSQL("CREATE TABLE " + getTableName() + " ( " + TextUtils.join(", ", cols) + " )");
}
private void dropTable(@NonNull SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + getTableName());
L.i("Dropped table=" + getTableName());
}
}