20 | * Database is the central class in Squeaky. Instances of {@link Database} are responsible for 21 | * managing SQLite tables assigned to them as instances of {@link Table} using 22 | * {@link Database#addTable(Table)}. 23 | *
24 | * 25 | *26 | * Once all required Tables have been added to the database, a call to {@link Database#prepare()} 27 | * will trigger any necessary migrations and will place the Database instance in to a state where 28 | * it is ready to accept queries. 29 | *
30 | */ 31 | public class Database { 32 | private static final String DEFAULT_VERSIONS_TABLE_NAME = "versions"; 33 | private final Class extends DatabaseHelper> mHelperClass; 34 | private static final int SQLITE_DB_VERSION = 1; 35 | private HashMaprowid
or _id
208 | * value as a result.
209 | * @param stmt Insert query. (not enforced, but encouraged)
210 | * @param bindArgs Arguments to bind to '?'s in the query.
211 | * @return Value of the new record's rowid
/_id
column.
212 | */
213 | @SuppressWarnings("WeakerAccess")
214 | public synchronized long insert(String stmt, Object... bindArgs) {
215 | SQLiteStatement statement = getWritableDB().compileStatement(stmt);
216 | bindArgs(statement, bindArgs);
217 | Logger.i(stmt+";", bindArgs);
218 | return statement.executeInsert();
219 | }
220 |
221 | /**
222 | * Run an update/delete query on the database
223 | * @param stmt Query to execute.
224 | * @param bindArgs Arguments to bind to '?'s in the query.
225 | * @return Number of affected rows.
226 | */
227 | public synchronized int update(String stmt, Object... bindArgs) {
228 | if (bindArgs == null) {
229 | return updateBatch(new String[] {stmt}, null, false);
230 | }
231 | return updateBatch(new String[] {stmt}, new Object[][] {bindArgs}, false);
232 | }
233 |
234 | /**
235 | * Run a bunch of updates/deletes/inserts on the database at once, optionally within a
236 | * transaction.
237 | * @param stmts Array of statements to execute.
238 | * @param bindArgs Arguments to bind to '?'s in the queries. (Optional, if not needed, pass
239 | * null) Condition: bindArgs.length == stmts.length if not null.
240 | * @param withTransaction Whether or not to execute the updates within a transaction.
241 | * @return Number of updated records.
242 | */
243 | public synchronized int updateBatch(String[] stmts, Object[][] bindArgs, boolean withTransaction) {
244 | boolean hasArgs = bindArgs != null;
245 | if (hasArgs && bindArgs.length != stmts.length) {
246 | throw new DatabaseException("bindArgs.length != stmts.length");
247 | }
248 |
249 | if (withTransaction) {
250 | getWritableDB().beginTransaction();
251 | }
252 |
253 | int rows = 0;
254 | for (int i = 0; i < stmts.length; i++) {
255 | SQLiteStatement statement = getWritableDB().compileStatement(stmts[i]);
256 | if (hasArgs) {
257 | bindArgs(statement, bindArgs[i]);
258 | }
259 | try {
260 | rows += statement.executeUpdateDelete();
261 | } finally {
262 | statement.close();
263 | }
264 | Logger.i(stmts[i]+";", bindArgs[i]);
265 | }
266 |
267 | if (withTransaction) {
268 | getWritableDB().endTransaction();
269 | }
270 |
271 | return rows;
272 | }
273 |
274 | private void updateSimple(SQLiteDatabase db, String stmt, Object... bindArgs) {
275 | if (bindArgs != null) {
276 | updateBatchSimple(db, new String[]{stmt}, new Object[][]{bindArgs});
277 | } else {
278 | updateBatchSimple(db, new String[]{stmt}, null);
279 | }
280 | }
281 |
282 | private void updateBatchSimple(SQLiteDatabase db, String[] stmts, Object[][] bindArgs) {
283 | boolean hasArgs = bindArgs != null;
284 | if (hasArgs && bindArgs.length != stmts.length) {
285 | throw new DatabaseException("bindArgs.length != stmts.length");
286 | }
287 |
288 | for (int i = 0; i < stmts.length; i++) {
289 | if (!stmts[i].endsWith(";")) {
290 | stmts[i] += ";";
291 | }
292 | if (hasArgs && bindArgs[i] != null) {
293 | Logger.i(stmts[i], bindArgs[i]);
294 | db.execSQL(stmts[i], bindArgs[i]);
295 | } else {
296 | Logger.i(stmts[i]);
297 | db.execSQL(stmts[i]);
298 | }
299 | }
300 | }
301 |
302 | private void bindArgs(SQLiteStatement statement, Object[] args) {
303 | if (args == null) {
304 | return;
305 | }
306 | for (int i = 0; i < args.length; i++) {
307 | Object o = args[i];
308 | if (o instanceof String) {
309 | statement.bindString(i + 1, (String) o);
310 | } else if (o instanceof byte[]) {
311 | statement.bindBlob(i + 1, (byte[]) o);
312 | } else if (o instanceof Integer) {
313 | statement.bindLong(i+1, (Integer) o);
314 | } else if (o instanceof Long) {
315 | statement.bindLong(i + 1, (Long) o);
316 | } else if (o instanceof Double) {
317 | statement.bindDouble(i+1, (Double) o);
318 | } else if (o instanceof Float) {
319 | statement.bindDouble(i+1, (double) (Float) o);
320 | } else if (o == null) {
321 | statement.bindNull(i+1);
322 | } else if (o instanceof BlobValue) {
323 | statement.bindBlob(i+1, ((BlobValue) o).getBytes());
324 | } else if (o instanceof Boolean) {
325 | statement.bindLong(i+1, (Boolean)o ? 1 : 0);
326 | } else {
327 | statement.bindString(i+1, o.toString());
328 | }
329 | }
330 | }
331 |
332 | private void doMigrations(SQLiteDatabase db) {
333 | Cursor c = db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'", null);
334 | boolean needVersionsTable = true;
335 | while ( c.moveToNext() ) {
336 | if (c.getString(0).equals(mVersionsTable.getName())) {
337 | needVersionsTable = false;
338 | break;
339 | }
340 | }
341 | c.close();
342 |
343 | if (needVersionsTable) {
344 | updateBatchSimple(db, mVersionsTable.getCreateTable(), null);
345 | }
346 |
347 | Map