package org.wordpress.android.util; import android.database.Cursor; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDoneException; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteStatement; import org.wordpress.android.util.AppLog.T; import java.util.ArrayList; import java.util.List; public class SqlUtils { private SqlUtils() { throw new AssertionError(); } /* * SQLite doesn't have a boolean datatype, so booleans are stored as 0=false, 1=true */ public static long boolToSql(boolean value) { return (value ? 1 : 0); } public static boolean sqlToBool(int value) { return (value != 0); } public static void closeStatement(SQLiteStatement stmt) { if (stmt != null) { stmt.close(); } } public static void closeCursor(Cursor c) { if (c != null && !c.isClosed()) { c.close(); } } /* * wrapper for DatabaseUtils.longForQuery() which returns 0 if query returns no rows */ public static long longForQuery(SQLiteDatabase db, String query, String[] selectionArgs) { try { return DatabaseUtils.longForQuery(db, query, selectionArgs); } catch (SQLiteDoneException e) { return 0; } } public static int intForQuery(SQLiteDatabase db, String query, String[] selectionArgs) { long value = longForQuery(db, query, selectionArgs); return (int)value; } public static boolean boolForQuery(SQLiteDatabase db, String query, String[] selectionArgs) { long value = longForQuery(db, query, selectionArgs); return sqlToBool((int) value); } /* * wrapper for DatabaseUtils.stringForQuery(), returns "" if query returns no rows */ public static String stringForQuery(SQLiteDatabase db, String query, String[] selectionArgs) { try { return DatabaseUtils.stringForQuery(db, query, selectionArgs); } catch (SQLiteDoneException e) { return ""; } } /* * returns the number of rows in the passed table */ public static long getRowCount(SQLiteDatabase db, String tableName) { return DatabaseUtils.queryNumEntries(db, tableName); } /* * removes all rows from the passed table */ public static void deleteAllRowsInTable(SQLiteDatabase db, String tableName) { db.delete(tableName, null, null); } /* * drop all tables from the passed SQLiteDatabase - make sure to pass a * writable database */ public static boolean dropAllTables(SQLiteDatabase db) throws SQLiteException { if (db == null) { return false; } if (db.isReadOnly()) { throw new SQLiteException("can't drop tables from a read-only database"); } List<String> tableNames = new ArrayList<String>(); Cursor cursor = db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'", null); if (cursor.moveToFirst()) { do { String tableName = cursor.getString(0); if (!tableName.equals("android_metadata") && !tableName.equals("sqlite_sequence")) { tableNames.add(tableName); } } while (cursor.moveToNext()); } db.beginTransaction(); try { for (String tableName: tableNames) { db.execSQL("DROP TABLE IF EXISTS " + tableName); } db.setTransactionSuccessful(); return true; } finally { db.endTransaction(); closeCursor(cursor); } } /* * Android's CursorWindow has a max size of 2MB per row which can be exceeded * with a very large text column, causing an IllegalStateException when the * row is read - prevent this by limiting the amount of text that's stored in * the text column. * https://github.com/android/platform_frameworks_base/blob/b77bc869241644a662f7e615b0b00ecb5aee373d/core/res/res/values/config.xml#L1268 * https://github.com/android/platform_frameworks_base/blob/3bdbf644d61f46b531838558fabbd5b990fc4913/core/java/android/database/CursorWindow.java#L103 */ // Max 512K characters (a UTF-8 char is 4 bytes max, so a 512K characters string is always < 2Mb) private static final int MAX_TEXT_LEN = 1024 * 1024 / 2; public static String maxSQLiteText(final String text) { if (text.length() <= MAX_TEXT_LEN) { return text; } AppLog.w(T.UTILS, "sqlite > max text exceeded, storing truncated text"); return text.substring(0, MAX_TEXT_LEN); } }