All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.parse.ParseSQLiteDatabase Maven / Gradle / Ivy

Go to download

A library that gives you access to the powerful Parse cloud platform from your Android app.

There is a newer version: 1.17.3
Show newest version
/*
 * Copyright (c) 2015-present, Parse, LLC.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */
package com.parse;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import bolts.Continuation;
import bolts.Task;
import bolts.TaskCompletionSource;

/** package */ class ParseSQLiteDatabase {

  /**
   * Database connections are locked to the thread that they are created in when using transactions.
   * We must use a single thread executor to make sure that all transactional DB actions are on
   * the same thread or else they will block.
   *
   * Symptoms include blocking on db.query, cursor.moveToFirst, etc.
   */
  private static final ExecutorService dbExecutor = Executors.newSingleThreadExecutor();

  /**
   * Queue for all database sessions. All database sessions must be serialized in order for
   * transactions to work correctly.
   */
  //TODO (grantland): do we have to serialize sessions of different databases?
  private static final TaskQueue taskQueue = new TaskQueue();

  /* protected */ static Task openDatabaseAsync(final SQLiteOpenHelper helper, int flags) {
    final ParseSQLiteDatabase db = new ParseSQLiteDatabase(flags);
    return db.open(helper).continueWithTask(new Continuation>() {
      @Override
      public Task then(Task task) throws Exception {
        return Task.forResult(db);
      }
    });
  }

  private SQLiteDatabase db;
  private Task current = null;
  private final Object currentLock = new Object();
  private final TaskCompletionSource tcs = new TaskCompletionSource<>();

  private int openFlags;

  /**
   * Creates a Session which opens a database connection and begins a transaction
   */
  private ParseSQLiteDatabase(int flags) {
    //TODO (grantland): if (!writable) -- disable transactions?
    //TODO (grantland): if (!writable) -- do we have to serialize everything?
    openFlags = flags;

    taskQueue.enqueue(new Continuation>() {
      @Override
      public Task then(Task toAwait) throws Exception {
        synchronized (currentLock) {
          current = toAwait;
        }
        return tcs.getTask();
      }
    });
  }

  public Task isReadOnlyAsync() {
    synchronized (currentLock) {
      Task task = current.continueWith(new Continuation() {
        @Override
        public Boolean then(Task task) throws Exception {
          return db.isReadOnly();
        }
      });
      current = task.makeVoid();
      return task;
    }
  }

  public Task isOpenAsync() {
    synchronized (currentLock) {
      Task task = current.continueWith(new Continuation() {
        @Override
        public Boolean then(Task task) throws Exception {
          return db.isOpen();
        }
      });
      current = task.makeVoid();
      return task;
    }
  }

  public boolean inTransaction() {
    return db.inTransaction();
  }

  /* package */ Task open(final SQLiteOpenHelper helper) {
    synchronized (currentLock) {
      current = current.continueWith(new Continuation() {
        @Override
        public SQLiteDatabase then(Task task) throws Exception {
          // get*Database() is synchronous and calls through SQLiteOpenHelper#onCreate, onUpdate,
          // etc.
          return (openFlags & SQLiteDatabase.OPEN_READONLY) == SQLiteDatabase.OPEN_READONLY
                  ? helper.getReadableDatabase()
                  : helper.getWritableDatabase();
        }
      }, dbExecutor).continueWithTask(new Continuation>() {
        @Override
        public Task then(Task task) throws Exception {
          db = task.getResult();
          return task.makeVoid();
        }
      }, Task.BACKGROUND_EXECUTOR); // We want to jump off the dbExecutor
      return current;
    }
  }

  /**
   * Executes a BEGIN TRANSACTION.
   * @see SQLiteDatabase#beginTransaction
   */
  public Task beginTransactionAsync() {
    synchronized (currentLock) {
      current = current.continueWithTask(new Continuation>() {
        @Override
        public Task then(Task task) throws Exception {
          db.beginTransaction();
          return task;
        }
      }, dbExecutor);
      return current.continueWithTask(new Continuation>() {
        @Override
        public Task then(Task task) throws Exception {
          // We want to jump off the dbExecutor
          return task;
        }
      }, Task.BACKGROUND_EXECUTOR);
    }
  }

  /**
   * Sets a transaction as successful.
   * @see SQLiteDatabase#setTransactionSuccessful
   */
  public Task setTransactionSuccessfulAsync() {
    synchronized (currentLock) {
      current = current.onSuccessTask(new Continuation>() {
        @Override
        public Task then(Task task) throws Exception {
          db.setTransactionSuccessful();
          return task;
        }
      }, dbExecutor);
      return current.continueWithTask(new Continuation>() {
        @Override
        public Task then(Task task) throws Exception {
          // We want to jump off the dbExecutor
          return task;
        }
      }, Task.BACKGROUND_EXECUTOR);
    }
  }

  /**
   * Ends a transaction.
   * @see SQLiteDatabase#endTransaction
   */
  public Task endTransactionAsync() {
    synchronized (currentLock) {
      current = current.continueWith(new Continuation() {
        @Override
        public Void then(Task task) throws Exception {
          db.endTransaction();
          // We want to swallow any exceptions from our Session task
          return null;
        }
      }, dbExecutor);
      return current.continueWithTask(new Continuation>() {
        @Override
        public Task then(Task task) throws Exception {
          // We want to jump off the dbExecutor
          return task;
        }
      }, Task.BACKGROUND_EXECUTOR);
    }
  }

  /**
   * Closes this session, sets the transaction as successful if no errors occurred, ends the
   * transaction and closes the database connection.
   */
  public Task closeAsync() {
    synchronized (currentLock) {
      current = current.continueWithTask(new Continuation>() {
        @Override
        public Task then(Task task) throws Exception {
          try {
            db.close();
          } finally {
            tcs.setResult(null);
          }
          return tcs.getTask();
        }
      }, dbExecutor);
      return current.continueWithTask(new Continuation>() {
        @Override
        public Task then(Task task) throws Exception {
          // We want to jump off the dbExecutor
          return task;
        }
      }, Task.BACKGROUND_EXECUTOR);
    }
  }

  /**
   * Runs a SELECT query.
   *
   * @see SQLiteDatabase#query
   */
  public Task queryAsync(final String table, final String[] select, final String where,
      final String[] args) {
    synchronized (currentLock) {
      Task task = current.onSuccess(new Continuation() {
        @Override
        public Cursor then(Task task) throws Exception {
          return db.query(table, select, where, args, null, null, null);
        }
      }, dbExecutor).onSuccess(new Continuation() {
        @Override
        public Cursor then(Task task) throws Exception {
          Cursor cursor = ParseSQLiteCursor.create(task.getResult(), dbExecutor);
          /* Ensure the cursor window is filled on the dbExecutor thread. We need to do this because
           * the cursor cannot be filled from a different thread than it was created on.
           */
          cursor.getCount();
          return cursor;
        }
      }, dbExecutor);
      current = task.makeVoid();
      return task.continueWithTask(new Continuation>() {
        @Override
        public Task then(Task task) throws Exception {
          // We want to jump off the dbExecutor
          return task;
        }
      }, Task.BACKGROUND_EXECUTOR);
    }
  }

  /**
   * Executes an INSERT.
   * @see SQLiteDatabase#insertWithOnConflict
   */
  public Task insertWithOnConflict(final String table, final ContentValues values,
       final int conflictAlgorithm) {
    synchronized (currentLock) {
      Task task = current.onSuccess(new Continuation() {
        @Override
        public Long then(Task task) throws Exception {
          return db.insertWithOnConflict(table, null, values, conflictAlgorithm);
        }
      }, dbExecutor);
      current = task.makeVoid();
      return task.continueWithTask(new Continuation>() {
        @Override
        public Task then(Task task) throws Exception {
          // We want to jump off the dbExecutor
          return task;
        }
      }, Task.BACKGROUND_EXECUTOR).makeVoid();
    }
  }

  /**
   * Executes an INSERT and throws on SQL errors.
   * @see SQLiteDatabase#insertOrThrow
   */
  public Task insertOrThrowAsync(final String table, final ContentValues values) {
    synchronized (currentLock) {
      Task task = current.onSuccess(new Continuation() {
        @Override
        public Long then(Task task) throws Exception {
          return db.insertOrThrow(table, null, values);
        }
      }, dbExecutor);
      current = task.makeVoid();
      return task.continueWithTask(new Continuation>() {
        @Override
        public Task then(Task task) throws Exception {
          // We want to jump off the dbExecutor
          return task;
        }
      }, Task.BACKGROUND_EXECUTOR).makeVoid();
    }
  }

  /**
   * Executes an UPDATE.
   * @see SQLiteDatabase#update
   */
  public Task updateAsync(final String table, final ContentValues values,
      final String where, final String[] args) {
    synchronized (currentLock) {
      Task task = current.onSuccess(new Continuation() {
        @Override
        public Integer then(Task task) throws Exception {
          return db.update(table, values, where, args);
        }
      }, dbExecutor);
      current = task.makeVoid();
      return task.continueWithTask(new Continuation>() {
        @Override
        public Task then(Task task) throws Exception {
          // We want to jump off the dbExecutor
          return task;
        }
      }, Task.BACKGROUND_EXECUTOR);
    }
  }

  /**
   * Executes a DELETE.
   * @see SQLiteDatabase#delete
   */
  public Task deleteAsync(final String table, final String where, final String[] args) {
    synchronized (currentLock) {
      Task task = current.onSuccess(new Continuation() {
        @Override
        public Integer then(Task task) throws Exception {
          return db.delete(table, where, args);
        }
      }, dbExecutor);
      current = task.makeVoid();
      return task.continueWithTask(new Continuation>() {
        @Override
        public Task then(Task task) throws Exception {
          // We want to jump off the dbExecutor
          return task;
        }
      }, Task.BACKGROUND_EXECUTOR).makeVoid();
    }
  }

  /**
   * Runs a raw query.
   *
   * @see SQLiteDatabase#rawQuery
   */
  public Task rawQueryAsync(final String sql, final String[] args) {
    synchronized (currentLock) {
      Task task = current.onSuccess(new Continuation() {
        @Override
        public Cursor then(Task task) throws Exception {
          return db.rawQuery(sql, args);
        }
      }, dbExecutor).onSuccess(new Continuation() {
        @Override
        public Cursor then(Task task) throws Exception {
          Cursor cursor = ParseSQLiteCursor.create(task.getResult(), dbExecutor);
          // Ensure the cursor window is filled on the dbExecutor thread. We need to do this because
          // the cursor cannot be filled from a different thread than it was created on.
          cursor.getCount();
          return cursor;
        }
      }, dbExecutor);
      current = task.makeVoid();
      return task.continueWithTask(new Continuation>() {
        @Override
        public Task then(Task task) throws Exception {
          // We want to jump off the dbExecutor
          return task;
        }
      }, Task.BACKGROUND_EXECUTOR);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy