();
- // Set the DatabaseErrorHandler to be used when SQLite reports corruption.
- // If the caller sets errorHandler = null, then use default errorhandler.
- mErrorHandler = (errorHandler == null) ? new DefaultDatabaseErrorHandler() : errorHandler;
- mConnectionNum = connectionNum;
- /* sqlite soft heap limit http://www.sqlite.org/c3ref/soft_heap_limit64.html
- * set it to 4 times the default cursor window size.
- * TODO what is an appropriate value, considering the WAL feature which could burn
- * a lot of memory with many connections to the database. needs testing to figure out
- * optimal value for this.
- */
- int limit = Resources.getSystem().getInteger(
- com.android.internal.R.integer.config_cursorWindowSize) * 1024 * 4;
- native_setSqliteSoftHeapLimit(limit);
- }
-
- /**
- * return whether the DB is opened as read only.
- * @return true if DB is opened as read only
+ * @return True if database is opened as read only.
*/
public boolean isReadOnly() {
- return (mFlags & OPEN_READ_MASK) == OPEN_READONLY;
+ synchronized (mLock) {
+ return isReadOnlyLocked();
+ }
+ }
+
+ private boolean isReadOnlyLocked() {
+ return (mConfigurationLocked.openFlags & OPEN_READ_MASK) == OPEN_READONLY;
}
/**
- * @return true if the DB is currently open (has not been closed)
+ * Returns true if the database is in-memory db.
+ *
+ * @return True if the database is in-memory.
+ * @hide
*/
- public boolean isOpen() {
- return mNativeHandle != 0;
+ public boolean isInMemoryDatabase() {
+ synchronized (mLock) {
+ return mConfigurationLocked.isInMemoryDb();
+ }
}
+ /**
+ * Returns true if the database is currently open.
+ *
+ * @return True if the database is currently open (has not been closed).
+ */
+ public boolean isOpen() {
+ synchronized (mLock) {
+ return mConnectionPoolLocked != null;
+ }
+ }
+
+ /**
+ * Returns true if the new version code is greater than the current database version.
+ *
+ * @param newVersion The new version code.
+ * @return True if the new version code is greater than the current database version.
+ */
public boolean needUpgrade(int newVersion) {
return newVersion > getVersion();
}
/**
- * Getter for the path to the database file.
+ * Gets the path to the database file.
*
- * @return the path to our database file.
+ * @return The path to the database file.
*/
public final String getPath() {
- return mPath;
- }
-
- /* package */ void logTimeStat(String sql, long beginMillis) {
- if (ENABLE_DB_SAMPLE) {
- logTimeStat(sql, beginMillis, null);
+ synchronized (mLock) {
+ return mConfigurationLocked.path;
}
}
- private void logTimeStat(String sql, long beginMillis, String prefix) {
- // Sample fast queries in proportion to the time taken.
- // Quantize the % first, so the logged sampling probability
- // exactly equals the actual sampling rate for this query.
-
- int samplePercent;
- long durationMillis = SystemClock.uptimeMillis() - beginMillis;
- if (durationMillis == 0 && prefix == GET_LOCK_LOG_PREFIX) {
- // The common case is locks being uncontended. Don't log those,
- // even at 1%, which is our default below.
- return;
- }
- if (sQueryLogTimeInMillis == 0) {
- sQueryLogTimeInMillis = SystemProperties.getInt("db.db_operation.threshold_ms", 500);
- }
- if (durationMillis >= sQueryLogTimeInMillis) {
- samplePercent = 100;
- } else {
- samplePercent = (int) (100 * durationMillis / sQueryLogTimeInMillis) + 1;
- if (mRandom.nextInt(100) >= samplePercent) return;
- }
-
- // Note: the prefix will be "COMMIT;" or "GETLOCK:" when non-null. We wait to do
- // it here so we avoid allocating in the common case.
- if (prefix != null) {
- sql = prefix + sql;
- }
- if (sql.length() > QUERY_LOG_SQL_LENGTH) sql = sql.substring(0, QUERY_LOG_SQL_LENGTH);
-
- // ActivityThread.currentPackageName() only returns non-null if the
- // current thread is an application main thread. This parameter tells
- // us whether an event loop is blocked, and if so, which app it is.
- //
- // Sadly, there's no fast way to determine app name if this is *not* a
- // main thread, or when we are invoked via Binder (e.g. ContentProvider).
- // Hopefully the full path to the database will be informative enough.
-
- String blockingPackage = AppGlobals.getInitialPackage();
- if (blockingPackage == null) blockingPackage = "";
-
- EventLog.writeEvent(
- EVENT_DB_OPERATION,
- getPathForLogs(),
- sql,
- durationMillis,
- blockingPackage,
- samplePercent);
- }
-
- /**
- * Removes email addresses from database filenames before they're
- * logged to the EventLog where otherwise apps could potentially
- * read them.
- */
- private String getPathForLogs() {
- if (mPathForLogs != null) {
- return mPathForLogs;
- }
- if (mPath == null) {
- return null;
- }
- if (mPath.indexOf('@') == -1) {
- mPathForLogs = mPath;
- } else {
- mPathForLogs = EMAIL_IN_DB_PATTERN.matcher(mPath).replaceAll("XX@YY");
- }
- return mPathForLogs;
- }
-
/**
* Sets the locale for this database. Does nothing if this database has
* the NO_LOCALIZED_COLLATORS flag set or was opened read only.
+ *
+ * @param locale The new locale.
+ *
* @throws SQLException if the locale could not be set. The most common reason
* for this is that there is no collator available for the locale you requested.
* In this case the database remains unchanged.
*/
public void setLocale(Locale locale) {
- lock();
- try {
- native_setLocale(locale.toString(), mFlags);
- } finally {
- unlock();
- }
- }
-
- /* package */ void verifyDbIsOpen() {
- if (!isOpen()) {
- throw new IllegalStateException("database " + getPath() + " (conn# " +
- mConnectionNum + ") already closed");
- }
- }
-
- /* package */ void verifyLockOwner() {
- verifyDbIsOpen();
- if (mLockingEnabled && !isDbLockedByCurrentThread()) {
- throw new IllegalStateException("Don't have database lock!");
- }
- }
-
- /**
- * Adds the given SQL and its compiled-statement-id-returned-by-sqlite to the
- * cache of compiledQueries attached to 'this'.
- *
- * If there is already a {@link SQLiteCompiledSql} in compiledQueries for the given SQL,
- * the new {@link SQLiteCompiledSql} object is NOT inserted into the cache (i.e.,the current
- * mapping is NOT replaced with the new mapping).
- */
- /* package */ synchronized void addToCompiledQueries(
- String sql, SQLiteCompiledSql compiledStatement) {
- // don't insert the new mapping if a mapping already exists
- if (mCompiledQueries.get(sql) != null) {
- return;
+ if (locale == null) {
+ throw new IllegalArgumentException("locale must not be null.");
}
- int maxCacheSz = (mConnectionNum == 0) ? mCompiledQueries.maxSize() :
- mParentConnObj.mCompiledQueries.maxSize();
-
- if (SQLiteDebug.DEBUG_SQL_CACHE) {
- boolean printWarning = (mConnectionNum == 0)
- ? (!mCacheFullWarning && mCompiledQueries.size() == maxCacheSz)
- : (!mParentConnObj.mCacheFullWarning &&
- mParentConnObj.mCompiledQueries.size() == maxCacheSz);
- if (printWarning) {
- /*
- * cache size is not enough for this app. log a warning.
- * chances are it is NOT using ? for bindargs - or cachesize is too small.
- */
- Log.w(TAG, "Reached MAX size for compiled-sql statement cache for database " +
- getPath() + ". Use setMaxSqlCacheSize() to increase cachesize. ");
- mCacheFullWarning = true;
- Log.d(TAG, "Here are the SQL statements in Cache of database: " + mPath);
- for (String s : mCompiledQueries.snapshot().keySet()) {
- Log.d(TAG, "Sql statement in Cache: " + s);
- }
- }
+ synchronized (mLock) {
+ throwIfNotOpenLocked();
+ mConfigurationLocked.locale = locale;
+ mConnectionPoolLocked.reconfigure(mConfigurationLocked);
}
- /* add the given SQLiteCompiledSql compiledStatement to cache.
- * no need to worry about the cache size - because {@link #mCompiledQueries}
- * self-limits its size.
- */
- mCompiledQueries.put(sql, compiledStatement);
- }
-
- /** package-level access for testing purposes */
- /* package */ synchronized void deallocCachedSqlStatements() {
- for (SQLiteCompiledSql compiledSql : mCompiledQueries.snapshot().values()) {
- compiledSql.releaseSqlStatement();
- }
- mCompiledQueries.evictAll();
- }
-
- /**
- * From the compiledQueries cache, returns the compiled-statement-id for the given SQL.
- * Returns null, if not found in the cache.
- */
- /* package */ synchronized SQLiteCompiledSql getCompiledStatementForSql(String sql) {
- return mCompiledQueries.get(sql);
}
/**
@@ -2162,115 +1516,21 @@ public class SQLiteDatabase extends SQLiteClosable {
* This method is thread-safe.
*
* @param cacheSize the size of the cache. can be (0 to {@link #MAX_SQL_CACHE_SIZE})
- * @throws IllegalStateException if input cacheSize > {@link #MAX_SQL_CACHE_SIZE} or
- * the value set with previous setMaxSqlCacheSize() call.
+ * @throws IllegalStateException if input cacheSize > {@link #MAX_SQL_CACHE_SIZE}.
*/
public void setMaxSqlCacheSize(int cacheSize) {
- synchronized (this) {
- LruCache oldCompiledQueries = mCompiledQueries;
- if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) {
- throw new IllegalStateException(
- "expected value between 0 and " + MAX_SQL_CACHE_SIZE);
- } else if (oldCompiledQueries != null && cacheSize < oldCompiledQueries.maxSize()) {
- throw new IllegalStateException("cannot set cacheSize to a value less than the "
- + "value set with previous setMaxSqlCacheSize() call.");
- }
- mCompiledQueries = new LruCache(cacheSize) {
- @Override
- protected void entryRemoved(boolean evicted, String key, SQLiteCompiledSql oldValue,
- SQLiteCompiledSql newValue) {
- verifyLockOwner();
- oldValue.releaseIfNotInUse();
- }
- };
- if (oldCompiledQueries != null) {
- for (Map.Entry entry
- : oldCompiledQueries.snapshot().entrySet()) {
- mCompiledQueries.put(entry.getKey(), entry.getValue());
- }
- }
+ if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) {
+ throw new IllegalStateException(
+ "expected value between 0 and " + MAX_SQL_CACHE_SIZE);
}
- }
- /* package */ synchronized boolean isInStatementCache(String sql) {
- return mCompiledQueries.get(sql) != null;
- }
-
- /* package */ synchronized void releaseCompiledSqlObj(
- String sql, SQLiteCompiledSql compiledSql) {
- if (mCompiledQueries.get(sql) == compiledSql) {
- // it is in cache - reset its inUse flag
- compiledSql.release();
- } else {
- // it is NOT in cache. finalize it.
- compiledSql.releaseSqlStatement();
+ synchronized (mLock) {
+ throwIfNotOpenLocked();
+ mConfigurationLocked.maxSqlCacheSize = cacheSize;
+ mConnectionPoolLocked.reconfigure(mConfigurationLocked);
}
}
- private synchronized int getCacheHitNum() {
- return mCompiledQueries.hitCount();
- }
-
- private synchronized int getCacheMissNum() {
- return mCompiledQueries.missCount();
- }
-
- private synchronized int getCachesize() {
- return mCompiledQueries.size();
- }
-
- /* package */ void finalizeStatementLater(int id) {
- if (!isOpen()) {
- // database already closed. this statement will already have been finalized.
- return;
- }
- synchronized(mClosedStatementIds) {
- if (mClosedStatementIds.contains(id)) {
- // this statement id is already queued up for finalization.
- return;
- }
- mClosedStatementIds.add(id);
- }
- }
-
- /* package */ boolean isInQueueOfStatementsToBeFinalized(int id) {
- if (!isOpen()) {
- // database already closed. this statement will already have been finalized.
- // return true so that the caller doesn't have to worry about finalizing this statement.
- return true;
- }
- synchronized(mClosedStatementIds) {
- return mClosedStatementIds.contains(id);
- }
- }
-
- /* package */ void closePendingStatements() {
- if (!isOpen()) {
- // since this database is already closed, no need to finalize anything.
- mClosedStatementIds.clear();
- return;
- }
- verifyLockOwner();
- /* to minimize synchronization on mClosedStatementIds, make a copy of the list */
- ArrayList list = new ArrayList(mClosedStatementIds.size());
- synchronized(mClosedStatementIds) {
- list.addAll(mClosedStatementIds);
- mClosedStatementIds.clear();
- }
- // finalize all the statements from the copied list
- int size = list.size();
- for (int i = 0; i < size; i++) {
- native_finalize(list.get(i));
- }
- }
-
- /**
- * for testing only
- */
- /* package */ ArrayList getQueuedUpStmtList() {
- return mClosedStatementIds;
- }
-
/**
* This method enables parallel execution of queries from multiple threads on the same database.
* It does this by opening multiple handles to the database and using a different
@@ -2314,37 +1574,43 @@ public class SQLiteDatabase extends SQLiteClosable {
* @return true if write-ahead-logging is set. false otherwise
*/
public boolean enableWriteAheadLogging() {
- // make sure the database is not READONLY. WAL doesn't make sense for readonly-databases.
- if (isReadOnly()) {
- return false;
- }
- // acquire lock - no that no other thread is enabling WAL at the same time
- lock();
- try {
- if (mConnectionPool != null) {
- // already enabled
+ synchronized (mLock) {
+ throwIfNotOpenLocked();
+
+ if (mIsWALEnabledLocked) {
return true;
}
- if (mPath.equalsIgnoreCase(MEMORY_DB_PATH)) {
+
+ if (isReadOnlyLocked()) {
+ // WAL doesn't make sense for readonly-databases.
+ // TODO: True, but connection pooling does still make sense...
+ return false;
+ }
+
+ if (mConfigurationLocked.isInMemoryDb()) {
Log.i(TAG, "can't enable WAL for memory databases.");
return false;
}
// make sure this database has NO attached databases because sqlite's write-ahead-logging
// doesn't work for databases with attached databases
- if (mHasAttachedDbs) {
+ if (mHasAttachedDbsLocked) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG,
- "this database: " + mPath + " has attached databases. can't enable WAL.");
+ Log.d(TAG, "this database: " + mConfigurationLocked.label
+ + " has attached databases. can't enable WAL.");
}
return false;
}
- mConnectionPool = new DatabaseConnectionPool(this);
- setJournalMode(mPath, "WAL");
- return true;
- } finally {
- unlock();
+
+ mIsWALEnabledLocked = true;
+ mConfigurationLocked.maxConnectionPoolSize = Math.max(2,
+ Resources.getSystem().getInteger(
+ com.android.internal.R.integer.db_connection_pool_size));
+ mConnectionPoolLocked.reconfigure(mConfigurationLocked);
}
+
+ setJournalMode("WAL");
+ return true;
}
/**
@@ -2352,178 +1618,68 @@ public class SQLiteDatabase extends SQLiteClosable {
* @hide
*/
public void disableWriteAheadLogging() {
- // grab database lock so that writeAheadLogging is not disabled from 2 different threads
- // at the same time
- lock();
- try {
- if (mConnectionPool == null) {
- return; // already disabled
+ synchronized (mLock) {
+ throwIfNotOpenLocked();
+
+ if (!mIsWALEnabledLocked) {
+ return;
}
- mConnectionPool.close();
- setJournalMode(mPath, "TRUNCATE");
- mConnectionPool = null;
- } finally {
- unlock();
- }
- }
- /* package */ SQLiteDatabase getDatabaseHandle(String sql) {
- if (isPooledConnection()) {
- // this is a pooled database connection
- // use it if it is open AND if I am not currently part of a transaction
- if (isOpen() && !amIInTransaction()) {
- // TODO: use another connection from the pool
- // if this connection is currently in use by some other thread
- // AND if there are free connections in the pool
- return this;
- } else {
- // the pooled connection is not open! could have been closed either due
- // to corruption on this or some other connection to the database
- // OR, maybe the connection pool is disabled after this connection has been
- // allocated to me. try to get some other pooled or main database connection
- return getParentDbConnObj().getDbConnection(sql);
- }
- } else {
- // this is NOT a pooled connection. can we get one?
- return getDbConnection(sql);
- }
- }
-
- /* package */ SQLiteDatabase createPoolConnection(short connectionNum) {
- SQLiteDatabase db = openDatabase(mPath, mFactory, mFlags, mErrorHandler, connectionNum);
- db.mParentConnObj = this;
- return db;
- }
-
- private synchronized SQLiteDatabase getParentDbConnObj() {
- return mParentConnObj;
- }
-
- private boolean isPooledConnection() {
- return this.mConnectionNum > 0;
- }
-
- /* package */ SQLiteDatabase getDbConnection(String sql) {
- verifyDbIsOpen();
- // this method should always be called with main database connection handle.
- // the only time when it is called with pooled database connection handle is
- // corruption occurs while trying to open a pooled database connection handle.
- // in that case, simply return 'this' handle
- if (isPooledConnection()) {
- return this;
+ mIsWALEnabledLocked = false;
+ mConfigurationLocked.maxConnectionPoolSize = 1;
+ mConnectionPoolLocked.reconfigure(mConfigurationLocked);
}
- // use the current connection handle if
- // 1. if the caller is part of the ongoing transaction, if any
- // 2. OR, if there is NO connection handle pool setup
- if (amIInTransaction() || mConnectionPool == null) {
- return this;
- } else {
- // get a connection handle from the pool
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- assert mConnectionPool != null;
- Log.i(TAG, mConnectionPool.toString());
- }
- return mConnectionPool.get(sql);
- }
- }
-
- private void releaseDbConnection(SQLiteDatabase db) {
- // ignore this release call if
- // 1. the database is closed
- // 2. OR, if db is NOT a pooled connection handle
- // 3. OR, if the database being released is same as 'this' (this condition means
- // that we should always be releasing a pooled connection handle by calling this method
- // from the 'main' connection handle
- if (!isOpen() || !db.isPooledConnection() || (db == this)) {
- return;
- }
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- assert isPooledConnection();
- assert mConnectionPool != null;
- Log.d(TAG, "releaseDbConnection threadid = " + Thread.currentThread().getId() +
- ", releasing # " + db.mConnectionNum + ", " + getPath());
- }
- mConnectionPool.release(db);
+ setJournalMode("TRUNCATE");
}
/**
- * this method is used to collect data about ALL open databases in the current process.
- * bugreport is a user of this data.
+ * Collect statistics about all open databases in the current process.
+ * Used by bug report.
*/
- /* package */ static ArrayList getDbStats() {
+ static ArrayList getDbStats() {
ArrayList dbStatsList = new ArrayList();
- // make a local copy of mActiveDatabases - so that this method is not competing
- // for synchronization lock on mActiveDatabases
- ArrayList> tempList;
- synchronized(mActiveDatabases) {
- tempList = (ArrayList>)mActiveDatabases.clone();
- }
- for (WeakReference w : tempList) {
- SQLiteDatabase db = w.get();
- if (db == null || !db.isOpen()) {
- continue;
- }
-
- try {
- // get SQLITE_DBSTATUS_LOOKASIDE_USED for the db
- int lookasideUsed = db.native_getDbLookaside();
-
- // get the lastnode of the dbname
- String path = db.getPath();
- int indx = path.lastIndexOf("/");
- String lastnode = path.substring((indx != -1) ? ++indx : 0);
-
- // get list of attached dbs and for each db, get its size and pagesize
- List> attachedDbs = db.getAttachedDbs();
- if (attachedDbs == null) {
- continue;
- }
- for (int i = 0; i < attachedDbs.size(); i++) {
- Pair p = attachedDbs.get(i);
- long pageCount = DatabaseUtils.longForQuery(db, "PRAGMA " + p.first
- + ".page_count;", null);
-
- // first entry in the attached db list is always the main database
- // don't worry about prefixing the dbname with "main"
- String dbName;
- if (i == 0) {
- dbName = lastnode;
- } else {
- // lookaside is only relevant for the main db
- lookasideUsed = 0;
- dbName = " (attached) " + p.first;
- // if the attached db has a path, attach the lastnode from the path to above
- if (p.second.trim().length() > 0) {
- int idx = p.second.lastIndexOf("/");
- dbName += " : " + p.second.substring((idx != -1) ? ++idx : 0);
- }
- }
- if (pageCount > 0) {
- dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(),
- lookasideUsed, db.getCacheHitNum(), db.getCacheMissNum(),
- db.getCachesize()));
- }
- }
- // if there are pooled connections, return the cache stats for them also.
- // while we are trying to query the pooled connections for stats, some other thread
- // could be disabling conneciton pool. so, grab a reference to the connection pool.
- DatabaseConnectionPool connPool = db.mConnectionPool;
- if (connPool != null) {
- for (SQLiteDatabase pDb : connPool.getConnectionList()) {
- dbStatsList.add(new DbStats("(pooled # " + pDb.mConnectionNum + ") "
- + lastnode, 0, 0, 0, pDb.getCacheHitNum(),
- pDb.getCacheMissNum(), pDb.getCachesize()));
- }
- }
- } catch (SQLiteException e) {
- // ignore. we don't care about exceptions when we are taking adb
- // bugreport!
- }
+ for (SQLiteDatabase db : getActiveDatabases()) {
+ db.collectDbStats(dbStatsList);
}
return dbStatsList;
}
+ private void collectDbStats(ArrayList dbStatsList) {
+ synchronized (mLock) {
+ if (mConnectionPoolLocked != null) {
+ mConnectionPoolLocked.collectDbStats(dbStatsList);
+ }
+ }
+ }
+
+ private static ArrayList getActiveDatabases() {
+ ArrayList databases = new ArrayList();
+ synchronized (sActiveDatabases) {
+ databases.addAll(sActiveDatabases.keySet());
+ }
+ return databases;
+ }
+
+ /**
+ * Dump detailed information about all open databases in the current process.
+ * Used by bug report.
+ */
+ static void dumpAll(Printer printer) {
+ for (SQLiteDatabase db : getActiveDatabases()) {
+ db.dump(printer);
+ }
+ }
+
+ private void dump(Printer printer) {
+ synchronized (mLock) {
+ if (mConnectionPoolLocked != null) {
+ printer.println("");
+ mConnectionPoolLocked.dump(printer);
+ }
+ }
+ }
+
/**
* Returns list of full pathnames of all attached databases including the main database
* by executing 'pragma database_list' on the database.
@@ -2532,23 +1688,27 @@ public class SQLiteDatabase extends SQLiteClosable {
* is not open.
*/
public List> getAttachedDbs() {
- if (!isOpen()) {
- return null;
- }
ArrayList> attachedDbs = new ArrayList>();
- if (!mHasAttachedDbs) {
- // No attached databases.
- // There is a small window where attached databases exist but this flag is not set yet.
- // This can occur when this thread is in a race condition with another thread
- // that is executing the SQL statement: "attach database as "
- // If this thread is NOT ok with such a race condition (and thus possibly not receive
- // the entire list of attached databases), then the caller should ensure that no thread
- // is executing any SQL statements while a thread is calling this method.
- // Typically, this method is called when 'adb bugreport' is done or the caller wants to
- // collect stats on the database and all its attached databases.
- attachedDbs.add(new Pair("main", mPath));
- return attachedDbs;
+ synchronized (mLock) {
+ if (mConnectionPoolLocked == null) {
+ return null; // not open
+ }
+
+ if (!mHasAttachedDbsLocked) {
+ // No attached databases.
+ // There is a small window where attached databases exist but this flag is not
+ // set yet. This can occur when this thread is in a race condition with another
+ // thread that is executing the SQL statement: "attach database as "
+ // If this thread is NOT ok with such a race condition (and thus possibly not
+ // receivethe entire list of attached databases), then the caller should ensure
+ // that no thread is executing any SQL statements while a thread is calling this
+ // method. Typically, this method is called when 'adb bugreport' is done or the
+ // caller wants to collect stats on the database and all its attached databases.
+ attachedDbs.add(new Pair("main", mConfigurationLocked.path));
+ return attachedDbs;
+ }
}
+
// has attached databases. query sqlite to get the list of attached databases.
Cursor c = null;
try {
@@ -2583,7 +1743,8 @@ public class SQLiteDatabase extends SQLiteClosable {
* false otherwise.
*/
public boolean isDatabaseIntegrityOk() {
- verifyDbIsOpen();
+ throwIfNotOpen(); // fail fast
+
List> attachedDbs = null;
try {
attachedDbs = getAttachedDbs();
@@ -2594,8 +1755,9 @@ public class SQLiteDatabase extends SQLiteClosable {
} catch (SQLiteException e) {
// can't get attachedDb list. do integrity check on the main database
attachedDbs = new ArrayList>();
- attachedDbs.add(new Pair("main", this.mPath));
+ attachedDbs.add(new Pair("main", getPath()));
}
+
for (int i = 0; i < attachedDbs.size(); i++) {
Pair p = attachedDbs.get(i);
SQLiteStatement prog = null;
@@ -2615,59 +1777,64 @@ public class SQLiteDatabase extends SQLiteClosable {
}
/**
- * Native call to open the database.
+ * Prevent other threads from using the database's primary connection.
*
- * @param path The full path to the database
- */
- private native void dbopen(String path, int flags);
-
- /**
- * Native call to setup tracing of all SQL statements
+ * This method is only used by {@link SQLiteOpenHelper} when transitioning from
+ * a readable to a writable database. It should not be used in any other way.
*
- * @param path the full path to the database
- * @param connectionNum connection number: 0 - N, where the main database
- * connection handle is numbered 0 and the connection handles in the connection
- * pool are numbered 1..N.
+ * @see #unlockPrimaryConnection()
*/
- private native void enableSqlTracing(String path, short connectionNum);
+ void lockPrimaryConnection() {
+ getThreadSession().beginTransaction(SQLiteSession.TRANSACTION_MODE_DEFERRED,
+ null, SQLiteConnectionPool.CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY);
+ }
/**
- * Native call to setup profiling of all SQL statements.
- * currently, sqlite's profiling = printing of execution-time
- * (wall-clock time) of each of the SQL statements, as they
- * are executed.
+ * Allow other threads to use the database's primary connection.
*
- * @param path the full path to the database
- * @param connectionNum connection number: 0 - N, where the main database
- * connection handle is numbered 0 and the connection handles in the connection
- * pool are numbered 1..N.
+ * @see #lockPrimaryConnection()
*/
- private native void enableSqlProfiling(String path, short connectionNum);
+ void unlockPrimaryConnection() {
+ getThreadSession().endTransaction();
+ }
+
+ @Override
+ public String toString() {
+ return "SQLiteDatabase: " + getPath();
+ }
+
+ private void throwIfNotOpen() {
+ synchronized (mConnectionPoolLocked) {
+ throwIfNotOpenLocked();
+ }
+ }
+
+ private void throwIfNotOpenLocked() {
+ if (mConnectionPoolLocked == null) {
+ throw new IllegalStateException("The database '" + mConfigurationLocked.label
+ + "' is not open.");
+ }
+ }
/**
- * Native call to set the locale. {@link #lock} must be held when calling
- * this method.
- * @throws SQLException
+ * Used to allow returning sub-classes of {@link Cursor} when calling query.
*/
- private native void native_setLocale(String loc, int flags);
+ public interface CursorFactory {
+ /**
+ * See {@link SQLiteCursor#SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)}.
+ */
+ public Cursor newCursor(SQLiteDatabase db,
+ SQLiteCursorDriver masterQuery, String editTable,
+ SQLiteQuery query);
+ }
/**
- * return the SQLITE_DBSTATUS_LOOKASIDE_USED documented here
- * http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html
- * @return int value of SQLITE_DBSTATUS_LOOKASIDE_USED
+ * A callback interface for a custom sqlite3 function.
+ * This can be used to create a function that can be called from
+ * sqlite3 database triggers.
+ * @hide
*/
- private native int native_getDbLookaside();
-
- /**
- * finalizes the given statement id.
- *
- * @param statementId statement to be finzlied by sqlite
- */
- private final native void native_finalize(int statementId);
-
- /**
- * set sqlite soft heap limit
- * http://www.sqlite.org/c3ref/soft_heap_limit64.html
- */
- private native void native_setSqliteSoftHeapLimit(int softHeapLimit);
+ public interface CustomFunction {
+ public void callback(String[] args);
+ }
}
diff --git a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
new file mode 100644
index 0000000000000..bc79ad3d46744
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database.sqlite;
+
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+/**
+ * Describes how to configure a database.
+ *
+ * The purpose of this object is to keep track of all of the little
+ * configuration settings that are applied to a database after it
+ * is opened so that they can be applied to all connections in the
+ * connection pool uniformly.
+ *
+ * Each connection maintains its own copy of this object so it can
+ * keep track of which settings have already been applied.
+ *
+ *
+ * @hide
+ */
+public final class SQLiteDatabaseConfiguration {
+ // The pattern we use to strip email addresses from database paths
+ // when constructing a label to use in log messages.
+ private static final Pattern EMAIL_IN_DB_PATTERN =
+ Pattern.compile("[\\w\\.\\-]+@[\\w\\.\\-]+");
+
+ /**
+ * Special path used by in-memory databases.
+ */
+ public static final String MEMORY_DB_PATH = ":memory:";
+
+ /**
+ * The database path.
+ */
+ public final String path;
+
+ /**
+ * The flags used to open the database.
+ */
+ public final int openFlags;
+
+ /**
+ * The label to use to describe the database when it appears in logs.
+ * This is derived from the path but is stripped to remove PII.
+ */
+ public final String label;
+
+ /**
+ * The maximum number of connections to retain in the connection pool.
+ * Must be at least 1.
+ *
+ * Default is 1.
+ */
+ public int maxConnectionPoolSize;
+
+ /**
+ * The maximum size of the prepared statement cache for each database connection.
+ * Must be non-negative.
+ *
+ * Default is 25.
+ */
+ public int maxSqlCacheSize;
+
+ /**
+ * The database locale.
+ *
+ * Default is the value returned by {@link Locale#getDefault()}.
+ */
+ public Locale locale;
+
+ /**
+ * The custom functions to register.
+ */
+ public final ArrayList customFunctions =
+ new ArrayList();
+
+ /**
+ * Creates a database configuration with the required parameters for opening a
+ * database and default values for all other parameters.
+ *
+ * @param path The database path.
+ * @param openFlags Open flags for the database, such as {@link SQLiteDatabase#OPEN_READWRITE}.
+ */
+ public SQLiteDatabaseConfiguration(String path, int openFlags) {
+ if (path == null) {
+ throw new IllegalArgumentException("path must not be null.");
+ }
+
+ this.path = path;
+ this.openFlags = openFlags;
+ label = stripPathForLogs(path);
+
+ // Set default values for optional parameters.
+ maxConnectionPoolSize = 1;
+ maxSqlCacheSize = 25;
+ locale = Locale.getDefault();
+ }
+
+ /**
+ * Creates a database configuration as a copy of another configuration.
+ *
+ * @param other The other configuration.
+ */
+ public SQLiteDatabaseConfiguration(SQLiteDatabaseConfiguration other) {
+ if (other == null) {
+ throw new IllegalArgumentException("other must not be null.");
+ }
+
+ this.path = other.path;
+ this.openFlags = other.openFlags;
+ this.label = other.label;
+ updateParametersFrom(other);
+ }
+
+ /**
+ * Updates the non-immutable parameters of this configuration object
+ * from the other configuration object.
+ *
+ * @param other The object from which to copy the parameters.
+ */
+ public void updateParametersFrom(SQLiteDatabaseConfiguration other) {
+ if (other == null) {
+ throw new IllegalArgumentException("other must not be null.");
+ }
+ if (!path.equals(other.path) || openFlags != other.openFlags) {
+ throw new IllegalArgumentException("other configuration must refer to "
+ + "the same database.");
+ }
+
+ maxConnectionPoolSize = other.maxConnectionPoolSize;
+ maxSqlCacheSize = other.maxSqlCacheSize;
+ locale = other.locale;
+ customFunctions.clear();
+ customFunctions.addAll(other.customFunctions);
+ }
+
+ /**
+ * Returns true if the database is in-memory.
+ * @return True if the database is in-memory.
+ */
+ public boolean isInMemoryDb() {
+ return path.equalsIgnoreCase(MEMORY_DB_PATH);
+ }
+
+ private static String stripPathForLogs(String path) {
+ if (path.indexOf('@') == -1) {
+ return path;
+ }
+ return EMAIL_IN_DB_PATTERN.matcher(path).replaceAll("XX@YY");
+ }
+}
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index 029bb4a872490..d87c3e4b810d6 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -29,6 +29,12 @@ import android.util.Printer;
* {@hide}
*/
public final class SQLiteDebug {
+ /**
+ * Controls the printing of informational SQL log messages.
+ */
+ public static final boolean DEBUG_SQL_LOG =
+ Log.isLoggable("SQLiteLog", Log.VERBOSE);
+
/**
* Controls the printing of SQL statements as they are executed.
*/
@@ -186,6 +192,7 @@ public final class SQLiteDebug {
* @param printer The printer for dumping database state.
*/
public static void dump(Printer printer, String[] args) {
+ SQLiteDatabase.dumpAll(printer);
}
/**
diff --git a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
index a5e762e66f7a4..52fd1d2709faa 100644
--- a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
+++ b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
@@ -25,10 +25,9 @@ import android.database.sqlite.SQLiteDatabase.CursorFactory;
* @hide
*/
public class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
- private String mEditTable;
- private SQLiteDatabase mDatabase;
- private Cursor mCursor;
- private String mSql;
+ private final SQLiteDatabase mDatabase;
+ private final String mEditTable;
+ private final String mSql;
private SQLiteQuery mQuery;
public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable) {
@@ -38,33 +37,27 @@ public class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
}
public Cursor query(CursorFactory factory, String[] selectionArgs) {
- // Compile the query
- SQLiteQuery query = null;
-
+ final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql);
+ final Cursor cursor;
try {
- mDatabase.lock(mSql);
- mDatabase.closePendingStatements();
- query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs);
+ query.bindAllArgsAsStrings(selectionArgs);
- // Create the cursor
if (factory == null) {
- mCursor = new SQLiteCursor(this, mEditTable, query);
+ cursor = new SQLiteCursor(this, mEditTable, query);
} else {
- mCursor = factory.newCursor(mDatabase, this, mEditTable, query);
+ cursor = factory.newCursor(mDatabase, this, mEditTable, query);
}
-
- mQuery = query;
- query = null;
- return mCursor;
- } finally {
- // Make sure this object is cleaned up if something happens
- if (query != null) query.close();
- mDatabase.unlock();
+ } catch (RuntimeException ex) {
+ query.close();
+ throw ex;
}
+
+ mQuery = query;
+ return cursor;
}
public void cursorClosed() {
- mCursor = null;
+ // Do nothing
}
public void setBindArguments(String[] bindArgs) {
diff --git a/core/java/android/database/sqlite/SQLiteGlobal.java b/core/java/android/database/sqlite/SQLiteGlobal.java
new file mode 100644
index 0000000000000..5e129be721bcf
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteGlobal.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database.sqlite;
+
+import android.os.StatFs;
+
+/**
+ * Provides access to SQLite functions that affect all database connection,
+ * such as memory management.
+ *
+ * @hide
+ */
+public final class SQLiteGlobal {
+ private static final String TAG = "SQLiteGlobal";
+
+ private static final Object sLock = new Object();
+ private static boolean sInitialized;
+ private static int sSoftHeapLimit;
+ private static int sDefaultPageSize;
+
+ private static native void nativeConfig(boolean verboseLog, int softHeapLimit);
+ private static native int nativeReleaseMemory(int bytesToFree);
+
+ private SQLiteGlobal() {
+ }
+
+ /**
+ * Initializes global SQLite settings the first time it is called.
+ * Should be called before opening the first (or any) database.
+ * Does nothing on repeated subsequent calls.
+ */
+ public static void initializeOnce() {
+ synchronized (sLock) {
+ if (!sInitialized) {
+ sInitialized = true;
+
+ // Limit to 8MB for now. This is 4 times the maximum cursor window
+ // size, as has been used by the original code in SQLiteDatabase for
+ // a long time.
+ // TODO: We really do need to test whether this helps or hurts us.
+ sSoftHeapLimit = 8 * 1024 * 1024;
+
+ // Configure SQLite.
+ nativeConfig(SQLiteDebug.DEBUG_SQL_LOG, sSoftHeapLimit);
+ }
+ }
+ }
+
+ /**
+ * Attempts to release memory by pruning the SQLite page cache and other
+ * internal data structures.
+ *
+ * @return The number of bytes that were freed.
+ */
+ public static int releaseMemory() {
+ synchronized (sLock) {
+ if (!sInitialized) {
+ return 0;
+ }
+ return nativeReleaseMemory(sSoftHeapLimit);
+ }
+ }
+
+ /**
+ * Gets the default page size to use when creating a database.
+ */
+ public static int getDefaultPageSize() {
+ synchronized (sLock) {
+ if (sDefaultPageSize == 0) {
+ sDefaultPageSize = new StatFs("/data").getBlockSize();
+ }
+ return sDefaultPageSize;
+ }
+ }
+}
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index 56cf94832cda9..31da7e4e9cafc 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -143,12 +143,14 @@ public abstract class SQLiteOpenHelper {
// If we have a read-only database open, someone could be using it
// (though they shouldn't), which would cause a lock to be held on
// the file, and our attempts to open the database read-write would
- // fail waiting for the file lock. To prevent that, we acquire the
- // lock on the read-only database, which shuts out other users.
+ // fail waiting for the file lock. To prevent that, we acquire a lock
+ // on the read-only database, which shuts out other users.
boolean success = false;
SQLiteDatabase db = null;
- if (mDatabase != null) mDatabase.lock();
+ if (mDatabase != null) {
+ mDatabase.lockPrimaryConnection();
+ }
try {
mIsInitializing = true;
if (mName == null) {
@@ -185,11 +187,13 @@ public abstract class SQLiteOpenHelper {
if (success) {
if (mDatabase != null) {
try { mDatabase.close(); } catch (Exception e) { }
- mDatabase.unlock();
+ mDatabase.unlockPrimaryConnection();
}
mDatabase = db;
} else {
- if (mDatabase != null) mDatabase.unlock();
+ if (mDatabase != null) {
+ mDatabase.unlockPrimaryConnection();
+ }
if (db != null) db.close();
}
}
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index 2bbc6d70c2a6f..81944587aa4d1 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -17,225 +17,104 @@
package android.database.sqlite;
import android.database.DatabaseUtils;
-import android.database.Cursor;
-import java.util.HashMap;
+import java.util.Arrays;
/**
* A base class for compiled SQLite programs.
- *
- * SQLiteProgram is NOT internally synchronized so code using a SQLiteProgram from multiple
- * threads should perform its own synchronization when using the SQLiteProgram.
+ *
+ * This class is not thread-safe.
+ *
*/
public abstract class SQLiteProgram extends SQLiteClosable {
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
- private static final String TAG = "SQLiteProgram";
+ private final SQLiteDatabase mDatabase;
+ private final String mSql;
+ private final boolean mReadOnly;
+ private final String[] mColumnNames;
+ private final int mNumParameters;
+ private final Object[] mBindArgs;
- /** The database this program is compiled against.
- * @hide
- */
- protected SQLiteDatabase mDatabase;
-
- /** The SQL used to create this query */
- /* package */ final String mSql;
-
- /**
- * Native linkage, do not modify. This comes from the database and should not be modified
- * in here or in the native code.
- * @hide
- */
- protected int nHandle;
-
- /**
- * the SQLiteCompiledSql object for the given sql statement.
- */
- /* package */ SQLiteCompiledSql mCompiledSql;
-
- /**
- * SQLiteCompiledSql statement id is populated with the corresponding object from the above
- * member. This member is used by the native_bind_* methods
- * @hide
- */
- protected int nStatement;
-
- /**
- * In the case of {@link SQLiteStatement}, this member stores the bindargs passed
- * to the following methods, instead of actually doing the binding.
- *
- * - {@link #bindBlob(int, byte[])}
- * - {@link #bindDouble(int, double)}
- * - {@link #bindLong(int, long)}
- * - {@link #bindNull(int)}
- * - {@link #bindString(int, String)}
- *
- *
- * Each entry in the array is a Pair of
- *
- * - bind arg position number
- * - the value to be bound to the bindarg
- *
- *
- * It is lazily initialized in the above bind methods
- * and it is cleared in {@link #clearBindings()} method.
- *
- * It is protected (in multi-threaded environment) by {@link SQLiteProgram}.this
- */
- /* package */ HashMap mBindArgs = null;
- /* package */ final int mStatementType;
- /* package */ static final int STATEMENT_CACHEABLE = 16;
- /* package */ static final int STATEMENT_DONT_PREPARE = 32;
- /* package */ static final int STATEMENT_USE_POOLED_CONN = 64;
- /* package */ static final int STATEMENT_TYPE_MASK = 0x0f;
-
- /* package */ SQLiteProgram(SQLiteDatabase db, String sql) {
- this(db, sql, null, true);
- }
-
- /* package */ SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs,
- boolean compileFlag) {
+ SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs) {
+ mDatabase = db;
mSql = sql.trim();
+
int n = DatabaseUtils.getSqlStatementType(mSql);
switch (n) {
- case DatabaseUtils.STATEMENT_UPDATE:
- mStatementType = n | STATEMENT_CACHEABLE;
- break;
- case DatabaseUtils.STATEMENT_SELECT:
- mStatementType = n | STATEMENT_CACHEABLE | STATEMENT_USE_POOLED_CONN;
- break;
case DatabaseUtils.STATEMENT_BEGIN:
case DatabaseUtils.STATEMENT_COMMIT:
case DatabaseUtils.STATEMENT_ABORT:
- mStatementType = n | STATEMENT_DONT_PREPARE;
+ mReadOnly = false;
+ mColumnNames = EMPTY_STRING_ARRAY;
+ mNumParameters = 0;
break;
+
default:
- mStatementType = n;
- }
- db.acquireReference();
- db.addSQLiteClosable(this);
- mDatabase = db;
- nHandle = db.mNativeHandle;
- if (bindArgs != null) {
- int size = bindArgs.length;
- for (int i = 0; i < size; i++) {
- this.addToBindArgs(i + 1, bindArgs[i]);
- }
- }
- if (compileFlag) {
- compileAndbindAllArgs();
- }
- }
-
- private void compileSql() {
- // only cache CRUD statements
- if ((mStatementType & STATEMENT_CACHEABLE) == 0) {
- mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql);
- nStatement = mCompiledSql.nStatement;
- // since it is not in the cache, no need to acquire() it.
- return;
+ boolean assumeReadOnly = (n == DatabaseUtils.STATEMENT_SELECT);
+ SQLiteStatementInfo info = new SQLiteStatementInfo();
+ db.getThreadSession().prepare(mSql,
+ db.getThreadDefaultConnectionFlags(assumeReadOnly), info);
+ mReadOnly = info.readOnly;
+ mColumnNames = info.columnNames;
+ mNumParameters = info.numParameters;
+ break;
}
- mCompiledSql = mDatabase.getCompiledStatementForSql(mSql);
- if (mCompiledSql == null) {
- // create a new compiled-sql obj
- mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql);
-
- // add it to the cache of compiled-sqls
- // but before adding it and thus making it available for anyone else to use it,
- // make sure it is acquired by me.
- mCompiledSql.acquire();
- mDatabase.addToCompiledQueries(mSql, mCompiledSql);
+ if (mNumParameters != 0) {
+ mBindArgs = new Object[mNumParameters];
} else {
- // it is already in compiled-sql cache.
- // try to acquire the object.
- if (!mCompiledSql.acquire()) {
- int last = mCompiledSql.nStatement;
- // the SQLiteCompiledSql in cache is in use by some other SQLiteProgram object.
- // we can't have two different SQLiteProgam objects can't share the same
- // CompiledSql object. create a new one.
- // finalize it when I am done with it in "this" object.
- mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql);
- // since it is not in the cache, no need to acquire() it.
+ mBindArgs = null;
+ }
+
+ if (bindArgs != null) {
+ if (bindArgs.length > mNumParameters) {
+ throw new IllegalArgumentException("Too many bind arguments. "
+ + bindArgs.length + " arguments were provided but the statement needs "
+ + mNumParameters + " arguments.");
}
+ System.arraycopy(bindArgs, 0, mBindArgs, 0, bindArgs.length);
}
- nStatement = mCompiledSql.nStatement;
}
- @Override
- protected void onAllReferencesReleased() {
- release();
- mDatabase.removeSQLiteClosable(this);
- mDatabase.releaseReference();
+ final SQLiteDatabase getDatabase() {
+ return mDatabase;
}
- @Override
- protected void onAllReferencesReleasedFromContainer() {
- release();
- mDatabase.releaseReference();
- }
-
- /* package */ void release() {
- if (mCompiledSql == null) {
- return;
- }
- mDatabase.releaseCompiledSqlObj(mSql, mCompiledSql);
- mCompiledSql = null;
- nStatement = 0;
- }
-
- /**
- * Returns a unique identifier for this program.
- *
- * @return a unique identifier for this program
- * @deprecated do not use this method. it is not guaranteed to be the same across executions of
- * the SQL statement contained in this object.
- */
- @Deprecated
- public final int getUniqueId() {
- return -1;
- }
-
- /**
- * used only for testing purposes
- */
- /* package */ int getSqlStatementId() {
- synchronized(this) {
- return (mCompiledSql == null) ? 0 : nStatement;
- }
- }
-
- /* package */ String getSqlString() {
+ final String getSql() {
return mSql;
}
- private void bind(int type, int index, Object value) {
- mDatabase.verifyDbIsOpen();
- addToBindArgs(index, (type == Cursor.FIELD_TYPE_NULL) ? null : value);
- if (nStatement > 0) {
- // bind only if the SQL statement is compiled
- acquireReference();
- try {
- switch (type) {
- case Cursor.FIELD_TYPE_NULL:
- native_bind_null(index);
- break;
- case Cursor.FIELD_TYPE_BLOB:
- native_bind_blob(index, (byte[]) value);
- break;
- case Cursor.FIELD_TYPE_FLOAT:
- native_bind_double(index, (Double) value);
- break;
- case Cursor.FIELD_TYPE_INTEGER:
- native_bind_long(index, (Long) value);
- break;
- case Cursor.FIELD_TYPE_STRING:
- default:
- native_bind_string(index, (String) value);
- break;
- }
- } finally {
- releaseReference();
- }
- }
+ final Object[] getBindArgs() {
+ return mBindArgs;
+ }
+
+ final String[] getColumnNames() {
+ return mColumnNames;
+ }
+
+ /** @hide */
+ protected final SQLiteSession getSession() {
+ return mDatabase.getThreadSession();
+ }
+
+ /** @hide */
+ protected final int getConnectionFlags() {
+ return mDatabase.getThreadDefaultConnectionFlags(mReadOnly);
+ }
+
+ /** @hide */
+ protected final void onCorruption() {
+ mDatabase.onCorruption();
+ }
+
+ /**
+ * Unimplemented.
+ * @deprecated This method is deprecated and must not be used.
+ */
+ @Deprecated
+ public final int getUniqueId() {
+ return -1;
}
/**
@@ -245,7 +124,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
* @param index The 1-based index to the parameter to bind null to
*/
public void bindNull(int index) {
- bind(Cursor.FIELD_TYPE_NULL, index, null);
+ bind(index, null);
}
/**
@@ -256,7 +135,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
* @param value The value to bind
*/
public void bindLong(int index, long value) {
- bind(Cursor.FIELD_TYPE_INTEGER, index, value);
+ bind(index, value);
}
/**
@@ -267,7 +146,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
* @param value The value to bind
*/
public void bindDouble(int index, double value) {
- bind(Cursor.FIELD_TYPE_FLOAT, index, value);
+ bind(index, value);
}
/**
@@ -275,13 +154,13 @@ public abstract class SQLiteProgram extends SQLiteClosable {
* {@link #clearBindings} is called.
*
* @param index The 1-based index to the parameter to bind
- * @param value The value to bind
+ * @param value The value to bind, must not be null
*/
public void bindString(int index, String value) {
if (value == null) {
throw new IllegalArgumentException("the bind value at index " + index + " is null");
}
- bind(Cursor.FIELD_TYPE_STRING, index, value);
+ bind(index, value);
}
/**
@@ -289,29 +168,21 @@ public abstract class SQLiteProgram extends SQLiteClosable {
* {@link #clearBindings} is called.
*
* @param index The 1-based index to the parameter to bind
- * @param value The value to bind
+ * @param value The value to bind, must not be null
*/
public void bindBlob(int index, byte[] value) {
if (value == null) {
throw new IllegalArgumentException("the bind value at index " + index + " is null");
}
- bind(Cursor.FIELD_TYPE_BLOB, index, value);
+ bind(index, value);
}
/**
* Clears all existing bindings. Unset bindings are treated as NULL.
*/
public void clearBindings() {
- mBindArgs = null;
- if (this.nStatement == 0) {
- return;
- }
- mDatabase.verifyDbIsOpen();
- acquireReference();
- try {
- native_clear_bindings();
- } finally {
- releaseReference();
+ if (mBindArgs != null) {
+ Arrays.fill(mBindArgs, null);
}
}
@@ -319,102 +190,33 @@ public abstract class SQLiteProgram extends SQLiteClosable {
* Release this program's resources, making it invalid.
*/
public void close() {
- mBindArgs = null;
- if (nHandle == 0 || !mDatabase.isOpen()) {
- return;
- }
releaseReference();
}
- private void addToBindArgs(int index, Object value) {
- if (mBindArgs == null) {
- mBindArgs = new HashMap();
- }
- mBindArgs.put(index, value);
- }
-
- /* package */ void compileAndbindAllArgs() {
- if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) {
- if (mBindArgs != null) {
- throw new IllegalArgumentException("Can't pass bindargs for this sql :" + mSql);
- }
- // no need to prepare this SQL statement
- return;
- }
- if (nStatement == 0) {
- // SQL statement is not compiled yet. compile it now.
- compileSql();
- }
- if (mBindArgs == null) {
- return;
- }
- for (int index : mBindArgs.keySet()) {
- Object value = mBindArgs.get(index);
- if (value == null) {
- native_bind_null(index);
- } else if (value instanceof Double || value instanceof Float) {
- native_bind_double(index, ((Number) value).doubleValue());
- } else if (value instanceof Number) {
- native_bind_long(index, ((Number) value).longValue());
- } else if (value instanceof Boolean) {
- Boolean bool = (Boolean)value;
- native_bind_long(index, (bool) ? 1 : 0);
- if (bool) {
- native_bind_long(index, 1);
- } else {
- native_bind_long(index, 0);
- }
- } else if (value instanceof byte[]){
- native_bind_blob(index, (byte[]) value);
- } else {
- native_bind_string(index, value.toString());
- }
- }
- }
-
/**
* Given an array of String bindArgs, this method binds all of them in one single call.
*
- * @param bindArgs the String array of bind args.
+ * @param bindArgs the String array of bind args, none of which must be null.
*/
public void bindAllArgsAsStrings(String[] bindArgs) {
- if (bindArgs == null) {
- return;
- }
- int size = bindArgs.length;
- for (int i = 0; i < size; i++) {
- bindString(i + 1, bindArgs[i]);
+ if (bindArgs != null) {
+ for (int i = bindArgs.length; i != 0; i--) {
+ bindString(i, bindArgs[i - 1]);
+ }
}
}
- /* package */ synchronized final void setNativeHandle(int nHandle) {
- this.nHandle = nHandle;
+ @Override
+ protected void onAllReferencesReleased() {
+ clearBindings();
}
- /**
- * @hide
- * Compiles SQL into a SQLite program.
- *
- * The database lock must be held when calling this method.
- * @param sql The SQL to compile.
- */
- protected final native void native_compile(String sql);
-
- /**
- * @hide
- */
- protected final native void native_finalize();
-
- /** @hide */
- protected final native void native_bind_null(int index);
- /** @hide */
- protected final native void native_bind_long(int index, long value);
- /** @hide */
- protected final native void native_bind_double(int index, double value);
- /** @hide */
- protected final native void native_bind_string(int index, String value);
- /** @hide */
- protected final native void native_bind_blob(int index, byte[] value);
- private final native void native_clear_bindings();
+ private void bind(int index, Object value) {
+ if (index < 1 || index > mNumParameters) {
+ throw new IllegalArgumentException("Cannot bind argument at index "
+ + index + " because the index is out of range. "
+ + "The statement has " + mNumParameters + " parameters.");
+ }
+ mBindArgs[index - 1] = value;
+ }
}
-
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
index 6dd25393674c1..17aa8865ee49a 100644
--- a/core/java/android/database/sqlite/SQLiteQuery.java
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -17,60 +17,24 @@
package android.database.sqlite;
import android.database.CursorWindow;
-import android.os.SystemClock;
-import android.text.TextUtils;
import android.util.Log;
/**
- * A SQLite program that represents a query that reads the resulting rows into a CursorWindow.
- * This class is used by SQLiteCursor and isn't useful itself.
- *
- * SQLiteQuery is not internally synchronized so code using a SQLiteQuery from multiple
- * threads should perform its own synchronization when using the SQLiteQuery.
+ * Represents a query that reads the resulting rows into a {@link SQLiteQuery}.
+ * This class is used by {@link SQLiteCursor} and isn't useful itself.
+ *
+ * This class is not thread-safe.
+ *
*/
public final class SQLiteQuery extends SQLiteProgram {
private static final String TAG = "SQLiteQuery";
- private static native long nativeFillWindow(int databasePtr, int statementPtr, int windowPtr,
- int offsetParam, int startPos, int requiredPos, boolean countAllRows);
-
- private static native int nativeColumnCount(int statementPtr);
- private static native String nativeColumnName(int statementPtr, int columnIndex);
-
- /** The index of the unbound OFFSET parameter */
- private int mOffsetIndex = 0;
-
- private boolean mClosed = false;
-
- /**
- * Create a persistent query object.
- *
- * @param db The database that this query object is associated with
- * @param query The SQL string for this query.
- * @param offsetIndex The 1-based index to the OFFSET parameter,
- */
- /* package */ SQLiteQuery(SQLiteDatabase db, String query, int offsetIndex, String[] bindArgs) {
- super(db, query);
- mOffsetIndex = offsetIndex;
- bindAllArgsAsStrings(bindArgs);
+ SQLiteQuery(SQLiteDatabase db, String query) {
+ super(db, query, null);
}
/**
- * Constructor used to create new instance to replace a given instance of this class.
- * This constructor is used when the current Query object is now associated with a different
- * {@link SQLiteDatabase} object.
- *
- * @param db The database that this query object is associated with
- * @param query the instance of {@link SQLiteQuery} to be replaced
- */
- /* package */ SQLiteQuery(SQLiteDatabase db, SQLiteQuery query) {
- super(db, query.mSql);
- this.mBindArgs = query.mBindArgs;
- this.mOffsetIndex = query.mOffsetIndex;
- }
-
- /**
- * Reads rows into a buffer. This method acquires the database lock.
+ * Reads rows into a buffer.
*
* @param window The window to fill into
* @param startPos The start position for filling the window.
@@ -81,83 +45,23 @@ public final class SQLiteQuery extends SQLiteProgram {
* @return Number of rows that were enumerated. Might not be all rows
* unless countAllRows is true.
*/
- /* package */ int fillWindow(CursorWindow window,
- int startPos, int requiredPos, boolean countAllRows) {
- mDatabase.lock(mSql);
- long timeStart = SystemClock.uptimeMillis();
+ int fillWindow(CursorWindow window, int startPos, int requiredPos, boolean countAllRows) {
+ acquireReference();
try {
- acquireReference();
+ window.acquireReference();
try {
- window.acquireReference();
- long result = nativeFillWindow(nHandle, nStatement, window.mWindowPtr,
- mOffsetIndex, startPos, requiredPos, countAllRows);
- int actualPos = (int)(result >> 32);
- int countedRows = (int)result;
- window.setStartPosition(actualPos);
- if (SQLiteDebug.DEBUG_LOG_SLOW_QUERIES) {
- long elapsed = SystemClock.uptimeMillis() - timeStart;
- if (SQLiteDebug.shouldLogSlowQuery(elapsed)) {
- Log.d(TAG, "fillWindow took " + elapsed
- + " ms: window=\"" + window
- + "\", startPos=" + startPos
- + ", requiredPos=" + requiredPos
- + ", offset=" + mOffsetIndex
- + ", actualPos=" + actualPos
- + ", filledRows=" + window.getNumRows()
- + ", countedRows=" + countedRows
- + ", query=\"" + mSql + "\""
- + ", args=[" + (mBindArgs != null ?
- TextUtils.join(", ", mBindArgs.values()) : "")
- + "]");
- }
- }
- mDatabase.logTimeStat(mSql, timeStart);
- return countedRows;
- } catch (IllegalStateException e){
- // simply ignore it
- return 0;
- } catch (SQLiteDatabaseCorruptException e) {
- mDatabase.onCorruption();
- throw e;
- } catch (SQLiteException e) {
- Log.e(TAG, "exception: " + e.getMessage() + "; query: " + mSql);
- throw e;
+ int numRows = getSession().executeForCursorWindow(getSql(), getBindArgs(),
+ window, startPos, requiredPos, countAllRows, getConnectionFlags());
+ return numRows;
+ } catch (SQLiteDatabaseCorruptException ex) {
+ onCorruption();
+ throw ex;
+ } catch (SQLiteException ex) {
+ Log.e(TAG, "exception: " + ex.getMessage() + "; query: " + getSql());
+ throw ex;
} finally {
window.releaseReference();
}
- } finally {
- releaseReference();
- mDatabase.unlock();
- }
- }
-
- /**
- * Get the column count for the statement. Only valid on query based
- * statements. The database must be locked
- * when calling this method.
- *
- * @return The number of column in the statement's result set.
- */
- /* package */ int columnCountLocked() {
- acquireReference();
- try {
- return nativeColumnCount(nStatement);
- } finally {
- releaseReference();
- }
- }
-
- /**
- * Retrieves the column name for the given column index. The database must be locked
- * when calling this method.
- *
- * @param columnIndex the index of the column to get the name for
- * @return The requested column's name
- */
- /* package */ String columnNameLocked(int columnIndex) {
- acquireReference();
- try {
- return nativeColumnName(nStatement, columnIndex);
} finally {
releaseReference();
}
@@ -165,22 +69,6 @@ public final class SQLiteQuery extends SQLiteProgram {
@Override
public String toString() {
- return "SQLiteQuery: " + mSql;
- }
-
- @Override
- public void close() {
- super.close();
- mClosed = true;
- }
-
- /**
- * Called by SQLiteCursor when it is requeried.
- */
- /* package */ void requery() {
- if (mClosed) {
- throw new IllegalStateException("requerying a closed cursor");
- }
- compileAndbindAllArgs();
+ return "SQLiteQuery: " + getSql();
}
}
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index 8f8eb6e4d3312..1b7b39800d35b 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -341,7 +341,7 @@ public class SQLiteQueryBuilder
// in both the wrapped and original forms.
String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy,
having, sortOrder, limit);
- validateSql(db, sqlForValidation); // will throw if query is invalid
+ validateQuerySql(db, sqlForValidation); // will throw if query is invalid
}
String sql = buildQuery(
@@ -357,16 +357,12 @@ public class SQLiteQueryBuilder
}
/**
- * Verifies that a SQL statement is valid by compiling it.
+ * Verifies that a SQL SELECT statement is valid by compiling it.
* If the SQL statement is not valid, this method will throw a {@link SQLiteException}.
*/
- private void validateSql(SQLiteDatabase db, String sql) {
- db.lock(sql);
- try {
- new SQLiteCompiledSql(db, sql).releaseSqlStatement();
- } finally {
- db.unlock();
- }
+ private void validateQuerySql(SQLiteDatabase db, String sql) {
+ db.getThreadSession().prepare(sql,
+ db.getThreadDefaultConnectionFlags(true /*readOnly*/), null);
}
/**
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
new file mode 100644
index 0000000000000..61fe45af59472
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -0,0 +1,878 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database.sqlite;
+
+import android.database.CursorWindow;
+import android.database.DatabaseUtils;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Provides a single client the ability to use a database.
+ *
+ * About database sessions
+ *
+ * Database access is always performed using a session. The session
+ * manages the lifecycle of transactions and database connections.
+ *
+ * Sessions can be used to perform both read-only and read-write operations.
+ * There is some advantage to knowing when a session is being used for
+ * read-only purposes because the connection pool can optimize the use
+ * of the available connections to permit multiple read-only operations
+ * to execute in parallel whereas read-write operations may need to be serialized.
+ *
+ * When Write Ahead Logging (WAL) is enabled, the database can
+ * execute simultaneous read-only and read-write transactions, provided that
+ * at most one read-write transaction is performed at a time. When WAL is not
+ * enabled, read-only transactions can execute in parallel but read-write
+ * transactions are mutually exclusive.
+ *
+ *
+ * Ownership and concurrency guarantees
+ *
+ * Session objects are not thread-safe. In fact, session objects are thread-bound.
+ * The {@link SQLiteDatabase} uses a thread-local variable to associate a session
+ * with each thread for the use of that thread alone. Consequently, each thread
+ * has its own session object and therefore its own transaction state independent
+ * of other threads.
+ *
+ * A thread has at most one session per database. This constraint ensures that
+ * a thread can never use more than one database connection at a time for a
+ * given database. As the number of available database connections is limited,
+ * if a single thread tried to acquire multiple connections for the same database
+ * at the same time, it might deadlock. Therefore we allow there to be only
+ * one session (so, at most one connection) per thread per database.
+ *
+ *
+ * Transactions
+ *
+ * There are two kinds of transaction: implicit transactions and explicit
+ * transactions.
+ *
+ * An implicit transaction is created whenever a database operation is requested
+ * and there is no explicit transaction currently in progress. An implicit transaction
+ * only lasts for the duration of the database operation in question and then it
+ * is ended. If the database operation was successful, then its changes are committed.
+ *
+ * An explicit transaction is started by calling {@link #beginTransaction} and
+ * specifying the desired transaction mode. Once an explicit transaction has begun,
+ * all subsequent database operations will be performed as part of that transaction.
+ * To end an explicit transaction, first call {@link #setTransactionSuccessful} if the
+ * transaction was successful, then call {@link #end}. If the transaction was
+ * marked successful, its changes will be committed, otherwise they will be rolled back.
+ *
+ * Explicit transactions can also be nested. A nested explicit transaction is
+ * started with {@link #beginTransaction}, marked successful with
+ * {@link #setTransactionSuccessful}and ended with {@link #endTransaction}.
+ * If any nested transaction is not marked successful, then the entire transaction
+ * including all of its nested transactions will be rolled back
+ * when the outermost transaction is ended.
+ *
+ * To improve concurrency, an explicit transaction can be yielded by calling
+ * {@link #yieldTransaction}. If there is contention for use of the database,
+ * then yielding ends the current transaction, commits its changes, releases the
+ * database connection for use by another session for a little while, and starts a
+ * new transaction with the same properties as the original one.
+ * Changes committed by {@link #yieldTransaction} cannot be rolled back.
+ *
+ * When a transaction is started, the client can provide a {@link SQLiteTransactionListener}
+ * to listen for notifications of transaction-related events.
+ *
+ * Recommended usage:
+ *
+ * // First, begin the transaction.
+ * session.beginTransaction(SQLiteSession.TRANSACTION_MODE_DEFERRED, 0);
+ * try {
+ * // Then do stuff...
+ * session.execute("INSERT INTO ...", null, 0);
+ *
+ * // As the very last step before ending the transaction, mark it successful.
+ * session.setTransactionSuccessful();
+ * } finally {
+ * // Finally, end the transaction.
+ * // This statement will commit the transaction if it was marked successful or
+ * // roll it back otherwise.
+ * session.endTransaction();
+ * }
+ *
+ *
+ *
+ * Database connections
+ *
+ * A {@link SQLiteDatabase} can have multiple active sessions at the same
+ * time. Each session acquires and releases connections to the database
+ * as needed to perform each requested database transaction. If all connections
+ * are in use, then database transactions on some sessions will block until a
+ * connection becomes available.
+ *
+ * The session acquires a single database connection only for the duration
+ * of a single (implicit or explicit) database transaction, then releases it.
+ * This characteristic allows a small pool of database connections to be shared
+ * efficiently by multiple sessions as long as they are not all trying to perform
+ * database transactions at the same time.
+ *
+ *
+ * Responsiveness
+ *
+ * Because there are a limited number of database connections and the session holds
+ * a database connection for the entire duration of a database transaction,
+ * it is important to keep transactions short. This is especially important
+ * for read-write transactions since they may block other transactions
+ * from executing. Consider calling {@link #yieldTransaction} periodically
+ * during long-running transactions.
+ *
+ * Another important consideration is that transactions that take too long to
+ * run may cause the application UI to become unresponsive. Even if the transaction
+ * is executed in a background thread, the user will get bored and
+ * frustrated if the application shows no data for several seconds while
+ * a transaction runs.
+ *
+ * Guidelines:
+ *
+ * - Do not perform database transactions on the UI thread.
+ * - Keep database transactions as short as possible.
+ * - Simple queries often run faster than complex queries.
+ * - Measure the performance of your database transactions.
+ * - Consider what will happen when the size of the data set grows.
+ * A query that works well on 100 rows may struggle with 10,000.
+ *
+ *
+ * TODO: Support timeouts on all possibly blocking operations.
+ *
+ * @hide
+ */
+public final class SQLiteSession {
+ private final SQLiteConnectionPool mConnectionPool;
+
+ private SQLiteConnection mConnection;
+ private int mConnectionFlags;
+ private Transaction mTransactionPool;
+ private Transaction mTransactionStack;
+
+ /**
+ * Transaction mode: Deferred.
+ *
+ * In a deferred transaction, no locks are acquired on the database
+ * until the first operation is performed. If the first operation is
+ * read-only, then a SHARED lock is acquired, otherwise
+ * a RESERVED lock is acquired.
+ *
+ * While holding a SHARED lock, this session is only allowed to
+ * read but other sessions are allowed to read or write.
+ * While holding a RESERVED lock, this session is allowed to read
+ * or write but other sessions are only allowed to read.
+ *
+ * Because the lock is only acquired when needed in a deferred transaction,
+ * it is possible for another session to write to the database first before
+ * this session has a chance to do anything.
+ *
+ * Corresponds to the SQLite BEGIN DEFERRED transaction mode.
+ *
+ */
+ public static final int TRANSACTION_MODE_DEFERRED = 0;
+
+ /**
+ * Transaction mode: Immediate.
+ *
+ * When an immediate transaction begins, the session acquires a
+ * RESERVED lock.
+ *
+ * While holding a RESERVED lock, this session is allowed to read
+ * or write but other sessions are only allowed to read.
+ *
+ * Corresponds to the SQLite BEGIN IMMEDIATE transaction mode.
+ *
+ */
+ public static final int TRANSACTION_MODE_IMMEDIATE = 1;
+
+ /**
+ * Transaction mode: Exclusive.
+ *
+ * When an exclusive transaction begins, the session acquires an
+ * EXCLUSIVE lock.
+ *
+ * While holding an EXCLUSIVE lock, this session is allowed to read
+ * or write but no other sessions are allowed to access the database.
+ *
+ * Corresponds to the SQLite BEGIN EXCLUSIVE transaction mode.
+ *
+ */
+ public static final int TRANSACTION_MODE_EXCLUSIVE = 2;
+
+ /**
+ * Creates a session bound to the specified connection pool.
+ *
+ * @param connectionPool The connection pool.
+ */
+ public SQLiteSession(SQLiteConnectionPool connectionPool) {
+ if (connectionPool == null) {
+ throw new IllegalArgumentException("connectionPool must not be null");
+ }
+
+ mConnectionPool = connectionPool;
+ }
+
+ /**
+ * Returns true if the session has a transaction in progress.
+ *
+ * @return True if the session has a transaction in progress.
+ */
+ public boolean hasTransaction() {
+ return mTransactionStack != null;
+ }
+
+ /**
+ * Returns true if the session has a nested transaction in progress.
+ *
+ * @return True if the session has a nested transaction in progress.
+ */
+ public boolean hasNestedTransaction() {
+ return mTransactionStack != null && mTransactionStack.mParent != null;
+ }
+
+ /**
+ * Returns true if the session has an active database connection.
+ *
+ * @return True if the session has an active database connection.
+ */
+ public boolean hasConnection() {
+ return mConnection != null;
+ }
+
+ /**
+ * Begins a transaction.
+ *
+ * Transactions may nest. If the transaction is not in progress,
+ * then a database connection is obtained and a new transaction is started.
+ * Otherwise, a nested transaction is started.
+ *
+ * Each call to {@link #beginTransaction} must be matched exactly by a call
+ * to {@link #endTransaction}. To mark a transaction as successful,
+ * call {@link #setTransactionSuccessful} before calling {@link #endTransaction}.
+ * If the transaction is not successful, or if any of its nested
+ * transactions were not successful, then the entire transaction will
+ * be rolled back when the outermost transaction is ended.
+ *
+ *
+ * @param transactionMode The transaction mode. One of: {@link #TRANSACTION_MODE_DEFERRED},
+ * {@link #TRANSACTION_MODE_IMMEDIATE}, or {@link #TRANSACTION_MODE_EXCLUSIVE}.
+ * Ignored when creating a nested transaction.
+ * @param transactionListener The transaction listener, or null if none.
+ * @param connectionFlags The connection flags to use if a connection must be
+ * acquired by this operation. Refer to {@link SQLiteConnectionPool}.
+ *
+ * @throws IllegalStateException if {@link #setTransactionSuccessful} has already been
+ * called for the current transaction.
+ *
+ * @see #setTransactionSuccessful
+ * @see #yieldTransaction
+ * @see #endTransaction
+ */
+ public void beginTransaction(int transactionMode,
+ SQLiteTransactionListener transactionListener, int connectionFlags) {
+ throwIfTransactionMarkedSuccessful();
+ beginTransactionUnchecked(transactionMode, transactionListener, connectionFlags);
+ }
+
+ private void beginTransactionUnchecked(int transactionMode,
+ SQLiteTransactionListener transactionListener, int connectionFlags) {
+ acquireConnectionIfNoTransaction(null, connectionFlags); // might throw
+ try {
+ // Set up the transaction such that we can back out safely
+ // in case we fail part way.
+ if (mTransactionStack == null) {
+ // Execute SQL might throw a runtime exception.
+ switch (transactionMode) {
+ case TRANSACTION_MODE_IMMEDIATE:
+ mConnection.execute("BEGIN IMMEDIATE;", null); // might throw
+ break;
+ case TRANSACTION_MODE_EXCLUSIVE:
+ mConnection.execute("BEGIN EXCLUSIVE;", null); // might throw
+ break;
+ default:
+ mConnection.execute("BEGIN;", null); // might throw
+ break;
+ }
+ }
+
+ // Listener might throw a runtime exception.
+ if (transactionListener != null) {
+ try {
+ transactionListener.onBegin(); // might throw
+ } catch (RuntimeException ex) {
+ if (mTransactionStack == null) {
+ mConnection.execute("ROLLBACK;", null); // might throw
+ }
+ throw ex;
+ }
+ }
+
+ // Bookkeeping can't throw, except an OOM, which is just too bad...
+ Transaction transaction = obtainTransaction(transactionMode, transactionListener);
+ transaction.mParent = mTransactionStack;
+ mTransactionStack = transaction;
+ } finally {
+ releaseConnectionIfNoTransaction(); // might throw
+ }
+ }
+
+ /**
+ * Marks the current transaction as having completed successfully.
+ *
+ * This method can be called at most once between {@link #beginTransaction} and
+ * {@link #endTransaction} to indicate that the changes made by the transaction should be
+ * committed. If this method is not called, the changes will be rolled back
+ * when the transaction is ended.
+ *
+ *
+ * @throws IllegalStateException if there is no current transaction, or if
+ * {@link #setTransactionSuccessful} has already been called for the current transaction.
+ *
+ * @see #beginTransaction
+ * @see #endTransaction
+ */
+ public void setTransactionSuccessful() {
+ throwIfNoTransaction();
+ throwIfTransactionMarkedSuccessful();
+
+ mTransactionStack.mMarkedSuccessful = true;
+ }
+
+ /**
+ * Ends the current transaction and commits or rolls back changes.
+ *
+ * If this is the outermost transaction (not nested within any other
+ * transaction), then the changes are committed if {@link #setTransactionSuccessful}
+ * was called or rolled back otherwise.
+ *
+ * This method must be called exactly once for each call to {@link #beginTransaction}.
+ *
+ *
+ * @throws IllegalStateException if there is no current transaction.
+ *
+ * @see #beginTransaction
+ * @see #setTransactionSuccessful
+ * @see #yieldTransaction
+ */
+ public void endTransaction() {
+ throwIfNoTransaction();
+ assert mConnection != null;
+
+ endTransactionUnchecked();
+ }
+
+ private void endTransactionUnchecked() {
+ final Transaction top = mTransactionStack;
+ boolean successful = top.mMarkedSuccessful && !top.mChildFailed;
+
+ RuntimeException listenerException = null;
+ final SQLiteTransactionListener listener = top.mListener;
+ if (listener != null) {
+ try {
+ if (successful) {
+ listener.onCommit(); // might throw
+ } else {
+ listener.onRollback(); // might throw
+ }
+ } catch (RuntimeException ex) {
+ listenerException = ex;
+ successful = false;
+ }
+ }
+
+ mTransactionStack = top.mParent;
+ recycleTransaction(top);
+
+ if (mTransactionStack != null) {
+ if (!successful) {
+ mTransactionStack.mChildFailed = true;
+ }
+ } else {
+ try {
+ if (successful) {
+ mConnection.execute("COMMIT;", null); // might throw
+ } else {
+ mConnection.execute("ROLLBACK;", null); // might throw
+ }
+ } finally {
+ releaseConnectionIfNoTransaction(); // might throw
+ }
+ }
+
+ if (listenerException != null) {
+ throw listenerException;
+ }
+ }
+
+ /**
+ * Temporarily ends a transaction to let other threads have use of
+ * the database. Begins a new transaction after a specified delay.
+ *
+ * If there are other threads waiting to acquire connections,
+ * then the current transaction is committed and the database
+ * connection is released. After a short delay, a new transaction
+ * is started.
+ *
+ * The transaction is assumed to be successful so far. Do not call
+ * {@link #setTransactionSuccessful()} before calling this method.
+ * This method will fail if the transaction has already been marked
+ * successful.
+ *
+ * The changes that were committed by a yield cannot be rolled back later.
+ *
+ * Before this method was called, there must already have been
+ * a transaction in progress. When this method returns, there will
+ * still be a transaction in progress, either the same one as before
+ * or a new one if the transaction was actually yielded.
+ *
+ * This method should not be called when there is a nested transaction
+ * in progress because it is not possible to yield a nested transaction.
+ * If throwIfNested is true, then attempting to yield
+ * a nested transaction will throw {@link IllegalStateException}, otherwise
+ * the method will return false in that case.
+ *
+ * If there is no nested transaction in progress but a previous nested
+ * transaction failed, then the transaction is not yielded (because it
+ * must be rolled back) and this method returns false.
+ *
+ *
+ * @param sleepAfterYieldDelayMillis A delay time to wait after yielding
+ * the database connection to allow other threads some time to run.
+ * If the value is less than or equal to zero, there will be no additional
+ * delay beyond the time it will take to begin a new transaction.
+ * @param throwIfUnsafe If true, then instead of returning false when no
+ * transaction is in progress, a nested transaction is in progress, or when
+ * the transaction has already been marked successful, throws {@link IllegalStateException}.
+ * @return True if the transaction was actually yielded.
+ *
+ * @throws IllegalStateException if throwIfNested is true and
+ * there is no current transaction, there is a nested transaction in progress or
+ * if {@link #setTransactionSuccessful} has already been called for the current transaction.
+ *
+ * @see #beginTransaction
+ * @see #endTransaction
+ */
+ public boolean yieldTransaction(long sleepAfterYieldDelayMillis, boolean throwIfUnsafe) {
+ if (throwIfUnsafe) {
+ throwIfNoTransaction();
+ throwIfTransactionMarkedSuccessful();
+ throwIfNestedTransaction();
+ } else {
+ if (mTransactionStack == null || mTransactionStack.mMarkedSuccessful
+ || mTransactionStack.mParent != null) {
+ return false;
+ }
+ }
+ assert mConnection != null;
+
+ if (mTransactionStack.mChildFailed) {
+ return false;
+ }
+
+ return yieldTransactionUnchecked(sleepAfterYieldDelayMillis); // might throw
+ }
+
+ private boolean yieldTransactionUnchecked(long sleepAfterYieldDelayMillis) {
+ if (!mConnectionPool.shouldYieldConnection(mConnection, mConnectionFlags)) {
+ return false;
+ }
+
+ final int transactionMode = mTransactionStack.mMode;
+ final SQLiteTransactionListener listener = mTransactionStack.mListener;
+ final int connectionFlags = mConnectionFlags;
+ endTransactionUnchecked(); // might throw
+
+ if (sleepAfterYieldDelayMillis > 0) {
+ try {
+ Thread.sleep(sleepAfterYieldDelayMillis);
+ } catch (InterruptedException ex) {
+ // we have been interrupted, that's all we need to do
+ }
+ }
+
+ beginTransactionUnchecked(transactionMode, listener, connectionFlags); // might throw
+ return true;
+ }
+
+ /**
+ * Prepares a statement for execution but does not bind its parameters or execute it.
+ *
+ * This method can be used to check for syntax errors during compilation
+ * prior to execution of the statement. If the {@code outStatementInfo} argument
+ * is not null, the provided {@link SQLiteStatementInfo} object is populated
+ * with information about the statement.
+ *
+ * A prepared statement makes no reference to the arguments that may eventually
+ * be bound to it, consequently it it possible to cache certain prepared statements
+ * such as SELECT or INSERT/UPDATE statements. If the statement is cacheable,
+ * then it will be stored in the cache for later and reused if possible.
+ *
+ *
+ * @param sql The SQL statement to prepare.
+ * @param connectionFlags The connection flags to use if a connection must be
+ * acquired by this operation. Refer to {@link SQLiteConnectionPool}.
+ * @param outStatementInfo The {@link SQLiteStatementInfo} object to populate
+ * with information about the statement, or null if none.
+ *
+ * @throws SQLiteException if an error occurs, such as a syntax error.
+ */
+ public void prepare(String sql, int connectionFlags, SQLiteStatementInfo outStatementInfo) {
+ if (sql == null) {
+ throw new IllegalArgumentException("sql must not be null.");
+ }
+
+ acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+ try {
+ mConnection.prepare(sql, outStatementInfo); // might throw
+ } finally {
+ releaseConnectionIfNoTransaction(); // might throw
+ }
+ }
+
+ /**
+ * Executes a statement that does not return a result.
+ *
+ * @param sql The SQL statement to execute.
+ * @param bindArgs The arguments to bind, or null if none.
+ * @param connectionFlags The connection flags to use if a connection must be
+ * acquired by this operation. Refer to {@link SQLiteConnectionPool}.
+ *
+ * @throws SQLiteException if an error occurs, such as a syntax error
+ * or invalid number of bind arguments.
+ */
+ public void execute(String sql, Object[] bindArgs, int connectionFlags) {
+ if (sql == null) {
+ throw new IllegalArgumentException("sql must not be null.");
+ }
+
+ if (executeSpecial(sql, bindArgs, connectionFlags)) {
+ return;
+ }
+
+ acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+ try {
+ mConnection.execute(sql, bindArgs); // might throw
+ } finally {
+ releaseConnectionIfNoTransaction(); // might throw
+ }
+ }
+
+ /**
+ * Executes a statement that returns a single long result.
+ *
+ * @param sql The SQL statement to execute.
+ * @param bindArgs The arguments to bind, or null if none.
+ * @param connectionFlags The connection flags to use if a connection must be
+ * acquired by this operation. Refer to {@link SQLiteConnectionPool}.
+ * @return The value of the first column in the first row of the result set
+ * as a long, or zero if none.
+ *
+ * @throws SQLiteException if an error occurs, such as a syntax error
+ * or invalid number of bind arguments.
+ */
+ public long executeForLong(String sql, Object[] bindArgs, int connectionFlags) {
+ if (sql == null) {
+ throw new IllegalArgumentException("sql must not be null.");
+ }
+
+ if (executeSpecial(sql, bindArgs, connectionFlags)) {
+ return 0;
+ }
+
+ acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+ try {
+ return mConnection.executeForLong(sql, bindArgs); // might throw
+ } finally {
+ releaseConnectionIfNoTransaction(); // might throw
+ }
+ }
+
+ /**
+ * Executes a statement that returns a single {@link String} result.
+ *
+ * @param sql The SQL statement to execute.
+ * @param bindArgs The arguments to bind, or null if none.
+ * @param connectionFlags The connection flags to use if a connection must be
+ * acquired by this operation. Refer to {@link SQLiteConnectionPool}.
+ * @return The value of the first column in the first row of the result set
+ * as a String, or null if none.
+ *
+ * @throws SQLiteException if an error occurs, such as a syntax error
+ * or invalid number of bind arguments.
+ */
+ public String executeForString(String sql, Object[] bindArgs, int connectionFlags) {
+ if (sql == null) {
+ throw new IllegalArgumentException("sql must not be null.");
+ }
+
+ if (executeSpecial(sql, bindArgs, connectionFlags)) {
+ return null;
+ }
+
+ acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+ try {
+ return mConnection.executeForString(sql, bindArgs); // might throw
+ } finally {
+ releaseConnectionIfNoTransaction(); // might throw
+ }
+ }
+
+ /**
+ * Executes a statement that returns a single BLOB result as a
+ * file descriptor to a shared memory region.
+ *
+ * @param sql The SQL statement to execute.
+ * @param bindArgs The arguments to bind, or null if none.
+ * @param connectionFlags The connection flags to use if a connection must be
+ * acquired by this operation. Refer to {@link SQLiteConnectionPool}.
+ * @return The file descriptor for a shared memory region that contains
+ * the value of the first column in the first row of the result set as a BLOB,
+ * or null if none.
+ *
+ * @throws SQLiteException if an error occurs, such as a syntax error
+ * or invalid number of bind arguments.
+ */
+ public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs,
+ int connectionFlags) {
+ if (sql == null) {
+ throw new IllegalArgumentException("sql must not be null.");
+ }
+
+ if (executeSpecial(sql, bindArgs, connectionFlags)) {
+ return null;
+ }
+
+ acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+ try {
+ return mConnection.executeForBlobFileDescriptor(sql, bindArgs); // might throw
+ } finally {
+ releaseConnectionIfNoTransaction(); // might throw
+ }
+ }
+
+ /**
+ * Executes a statement that returns a count of the number of rows
+ * that were changed. Use for UPDATE or DELETE SQL statements.
+ *
+ * @param sql The SQL statement to execute.
+ * @param bindArgs The arguments to bind, or null if none.
+ * @param connectionFlags The connection flags to use if a connection must be
+ * acquired by this operation. Refer to {@link SQLiteConnectionPool}.
+ * @return The number of rows that were changed.
+ *
+ * @throws SQLiteException if an error occurs, such as a syntax error
+ * or invalid number of bind arguments.
+ */
+ public int executeForChangedRowCount(String sql, Object[] bindArgs, int connectionFlags) {
+ if (sql == null) {
+ throw new IllegalArgumentException("sql must not be null.");
+ }
+
+ if (executeSpecial(sql, bindArgs, connectionFlags)) {
+ return 0;
+ }
+
+ acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+ try {
+ return mConnection.executeForChangedRowCount(sql, bindArgs); // might throw
+ } finally {
+ releaseConnectionIfNoTransaction(); // might throw
+ }
+ }
+
+ /**
+ * Executes a statement that returns the row id of the last row inserted
+ * by the statement. Use for INSERT SQL statements.
+ *
+ * @param sql The SQL statement to execute.
+ * @param bindArgs The arguments to bind, or null if none.
+ * @param connectionFlags The connection flags to use if a connection must be
+ * acquired by this operation. Refer to {@link SQLiteConnectionPool}.
+ * @return The row id of the last row that was inserted, or 0 if none.
+ *
+ * @throws SQLiteException if an error occurs, such as a syntax error
+ * or invalid number of bind arguments.
+ */
+ public long executeForLastInsertedRowId(String sql, Object[] bindArgs, int connectionFlags) {
+ if (sql == null) {
+ throw new IllegalArgumentException("sql must not be null.");
+ }
+
+ if (executeSpecial(sql, bindArgs, connectionFlags)) {
+ return 0;
+ }
+
+ acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+ try {
+ return mConnection.executeForLastInsertedRowId(sql, bindArgs); // might throw
+ } finally {
+ releaseConnectionIfNoTransaction(); // might throw
+ }
+ }
+
+ /**
+ * Executes a statement and populates the specified {@link CursorWindow}
+ * with a range of results. Returns the number of rows that were counted
+ * during query execution.
+ *
+ * @param sql The SQL statement to execute.
+ * @param bindArgs The arguments to bind, or null if none.
+ * @param window The cursor window to clear and fill.
+ * @param startPos The start position for filling the window.
+ * @param requiredPos The position of a row that MUST be in the window.
+ * If it won't fit, then the query should discard part of what it filled
+ * so that it does. Must be greater than or equal to startPos.
+ * @param countAllRows True to count all rows that the query would return
+ * regagless of whether they fit in the window.
+ * @param connectionFlags The connection flags to use if a connection must be
+ * acquired by this operation. Refer to {@link SQLiteConnectionPool}.
+ * @return The number of rows that were counted during query execution. Might
+ * not be all rows in the result set unless countAllRows is true.
+ *
+ * @throws SQLiteException if an error occurs, such as a syntax error
+ * or invalid number of bind arguments.
+ */
+ public int executeForCursorWindow(String sql, Object[] bindArgs,
+ CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
+ int connectionFlags) {
+ if (sql == null) {
+ throw new IllegalArgumentException("sql must not be null.");
+ }
+ if (window == null) {
+ throw new IllegalArgumentException("window must not be null.");
+ }
+
+ if (executeSpecial(sql, bindArgs, connectionFlags)) {
+ window.clear();
+ return 0;
+ }
+
+ acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+ try {
+ return mConnection.executeForCursorWindow(sql, bindArgs,
+ window, startPos, requiredPos, countAllRows); // might throw
+ } finally {
+ releaseConnectionIfNoTransaction(); // might throw
+ }
+ }
+
+ /**
+ * Performs special reinterpretation of certain SQL statements such as "BEGIN",
+ * "COMMIT" and "ROLLBACK" to ensure that transaction state invariants are
+ * maintained.
+ *
+ * This function is mainly used to support legacy apps that perform their
+ * own transactions by executing raw SQL rather than calling {@link #beginTransaction}
+ * and the like.
+ *
+ * @param sql The SQL statement to execute.
+ * @param bindArgs The arguments to bind, or null if none.
+ * @param connectionFlags The connection flags to use if a connection must be
+ * acquired by this operation. Refer to {@link SQLiteConnectionPool}.
+ * @return True if the statement was of a special form that was handled here,
+ * false otherwise.
+ *
+ * @throws SQLiteException if an error occurs, such as a syntax error
+ * or invalid number of bind arguments.
+ */
+ private boolean executeSpecial(String sql, Object[] bindArgs, int connectionFlags) {
+ final int type = DatabaseUtils.getSqlStatementType(sql);
+ switch (type) {
+ case DatabaseUtils.STATEMENT_BEGIN:
+ beginTransaction(TRANSACTION_MODE_EXCLUSIVE, null, connectionFlags);
+ return true;
+
+ case DatabaseUtils.STATEMENT_COMMIT:
+ setTransactionSuccessful();
+ endTransaction();
+ return true;
+
+ case DatabaseUtils.STATEMENT_ABORT:
+ endTransaction();
+ return true;
+ }
+ return false;
+ }
+
+ private void acquireConnectionIfNoTransaction(String sql, int connectionFlags) {
+ if (mTransactionStack == null) {
+ assert mConnection == null;
+ mConnection = mConnectionPool.acquireConnection(sql, connectionFlags); // might throw
+ mConnectionFlags = connectionFlags;
+ }
+ }
+
+ private void releaseConnectionIfNoTransaction() {
+ if (mTransactionStack == null && mConnection != null) {
+ try {
+ mConnectionPool.releaseConnection(mConnection); // might throw
+ } finally {
+ mConnection = null;
+ }
+ }
+ }
+
+ private void throwIfNoTransaction() {
+ if (mTransactionStack == null) {
+ throw new IllegalStateException("Cannot perform this operation because "
+ + "there is no current transaction.");
+ }
+ }
+
+ private void throwIfTransactionMarkedSuccessful() {
+ if (mTransactionStack != null && mTransactionStack.mMarkedSuccessful) {
+ throw new IllegalStateException("Cannot perform this operation because "
+ + "the transaction has already been marked successful. The only "
+ + "thing you can do now is call endTransaction().");
+ }
+ }
+
+ private void throwIfNestedTransaction() {
+ if (mTransactionStack == null && mTransactionStack.mParent != null) {
+ throw new IllegalStateException("Cannot perform this operation because "
+ + "a nested transaction is in progress.");
+ }
+ }
+
+ private Transaction obtainTransaction(int mode, SQLiteTransactionListener listener) {
+ Transaction transaction = mTransactionPool;
+ if (transaction != null) {
+ mTransactionPool = transaction.mParent;
+ transaction.mParent = null;
+ transaction.mMarkedSuccessful = false;
+ transaction.mChildFailed = false;
+ } else {
+ transaction = new Transaction();
+ }
+ transaction.mMode = mode;
+ transaction.mListener = listener;
+ return transaction;
+ }
+
+ private void recycleTransaction(Transaction transaction) {
+ transaction.mParent = mTransactionPool;
+ transaction.mListener = null;
+ mTransactionPool = transaction;
+ }
+
+ private static final class Transaction {
+ public Transaction mParent;
+ public int mMode;
+ public SQLiteTransactionListener mListener;
+ public boolean mMarkedSuccessful;
+ public boolean mChildFailed;
+ }
+}
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index c99a6fb8cd5ff..4e20da00c25c9 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -16,47 +16,19 @@
package android.database.sqlite;
-import android.database.DatabaseUtils;
import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.io.IOException;
-
-import dalvik.system.BlockGuard;
/**
- * A pre-compiled statement against a {@link SQLiteDatabase} that can be reused.
- * The statement cannot return multiple rows, but 1x1 result sets are allowed.
- * Don't use SQLiteStatement constructor directly, please use
- * {@link SQLiteDatabase#compileStatement(String)}
- *
- * SQLiteStatement is NOT internally synchronized so code using a SQLiteStatement from multiple
- * threads should perform its own synchronization when using the SQLiteStatement.
+ * Represents a statement that can be executed against a database. The statement
+ * cannot return multiple rows or columns, but single value (1 x 1) result sets
+ * are supported.
+ *
+ * This class is not thread-safe.
+ *
*/
-@SuppressWarnings("deprecation")
-public final class SQLiteStatement extends SQLiteProgram
-{
- private static final String TAG = "SQLiteStatement";
-
- private static final boolean READ = true;
- private static final boolean WRITE = false;
-
- private SQLiteDatabase mOrigDb;
- private int mState;
- /** possible value for {@link #mState}. indicates that a transaction is started. */
- private static final int TRANS_STARTED = 1;
- /** possible value for {@link #mState}. indicates that a lock is acquired. */
- private static final int LOCK_ACQUIRED = 2;
-
- /**
- * Don't use SQLiteStatement constructor directly, please use
- * {@link SQLiteDatabase#compileStatement(String)}
- * @param db
- * @param sql
- */
- /* package */ SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs) {
- super(db, sql, bindArgs, false /* don't compile sql statement */);
+public final class SQLiteStatement extends SQLiteProgram {
+ SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs) {
+ super(db, sql, bindArgs);
}
/**
@@ -67,7 +39,15 @@ public final class SQLiteStatement extends SQLiteProgram
* some reason
*/
public void execute() {
- executeUpdateDelete();
+ acquireReference();
+ try {
+ getSession().execute(getSql(), getBindArgs(), getConnectionFlags());
+ } catch (SQLiteDatabaseCorruptException ex) {
+ onCorruption();
+ throw ex;
+ } finally {
+ releaseReference();
+ }
}
/**
@@ -79,21 +59,15 @@ public final class SQLiteStatement extends SQLiteProgram
* some reason
*/
public int executeUpdateDelete() {
+ acquireReference();
try {
- saveSqlAsLastSqlStatement();
- acquireAndLock(WRITE);
- int numChanges = 0;
- if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) {
- // since the statement doesn't have to be prepared,
- // call the following native method which will not prepare
- // the query plan
- native_executeSql(mSql);
- } else {
- numChanges = native_execute();
- }
- return numChanges;
+ return getSession().executeForChangedRowCount(
+ getSql(), getBindArgs(), getConnectionFlags());
+ } catch (SQLiteDatabaseCorruptException ex) {
+ onCorruption();
+ throw ex;
} finally {
- releaseAndUnlock();
+ releaseReference();
}
}
@@ -107,23 +81,18 @@ public final class SQLiteStatement extends SQLiteProgram
* some reason
*/
public long executeInsert() {
+ acquireReference();
try {
- saveSqlAsLastSqlStatement();
- acquireAndLock(WRITE);
- return native_executeInsert();
+ return getSession().executeForLastInsertedRowId(
+ getSql(), getBindArgs(), getConnectionFlags());
+ } catch (SQLiteDatabaseCorruptException ex) {
+ onCorruption();
+ throw ex;
} finally {
- releaseAndUnlock();
+ releaseReference();
}
}
- private void saveSqlAsLastSqlStatement() {
- if (((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
- DatabaseUtils.STATEMENT_UPDATE) ||
- (mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
- DatabaseUtils.STATEMENT_BEGIN) {
- mDatabase.setLastSqlStatement(mSql);
- }
- }
/**
* Execute a statement that returns a 1 by 1 table with a numeric value.
* For example, SELECT COUNT(*) FROM table;
@@ -133,17 +102,15 @@ public final class SQLiteStatement extends SQLiteProgram
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
*/
public long simpleQueryForLong() {
+ acquireReference();
try {
- long timeStart = acquireAndLock(READ);
- long retValue = native_1x1_long();
- mDatabase.logTimeStat(mSql, timeStart);
- return retValue;
- } catch (SQLiteDoneException e) {
- throw new SQLiteDoneException(
- "expected 1 row from this query but query returned no data. check the query: " +
- mSql);
+ return getSession().executeForLong(
+ getSql(), getBindArgs(), getConnectionFlags());
+ } catch (SQLiteDatabaseCorruptException ex) {
+ onCorruption();
+ throw ex;
} finally {
- releaseAndUnlock();
+ releaseReference();
}
}
@@ -156,17 +123,15 @@ public final class SQLiteStatement extends SQLiteProgram
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
*/
public String simpleQueryForString() {
+ acquireReference();
try {
- long timeStart = acquireAndLock(READ);
- String retValue = native_1x1_string();
- mDatabase.logTimeStat(mSql, timeStart);
- return retValue;
- } catch (SQLiteDoneException e) {
- throw new SQLiteDoneException(
- "expected 1 row from this query but query returned no data. check the query: " +
- mSql);
+ return getSession().executeForString(
+ getSql(), getBindArgs(), getConnectionFlags());
+ } catch (SQLiteDatabaseCorruptException ex) {
+ onCorruption();
+ throw ex;
} finally {
- releaseAndUnlock();
+ releaseReference();
}
}
@@ -179,121 +144,20 @@ public final class SQLiteStatement extends SQLiteProgram
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
*/
public ParcelFileDescriptor simpleQueryForBlobFileDescriptor() {
- try {
- long timeStart = acquireAndLock(READ);
- ParcelFileDescriptor retValue = native_1x1_blob_ashmem();
- mDatabase.logTimeStat(mSql, timeStart);
- return retValue;
- } catch (IOException ex) {
- Log.e(TAG, "simpleQueryForBlobFileDescriptor() failed", ex);
- return null;
- } catch (SQLiteDoneException e) {
- throw new SQLiteDoneException(
- "expected 1 row from this query but query returned no data. check the query: " +
- mSql);
- } finally {
- releaseAndUnlock();
- }
- }
-
- /**
- * Called before every method in this class before executing a SQL statement,
- * this method does the following:
- *
- * - make sure the database is open
- * - get a database connection from the connection pool,if possible
- * - notifies {@link BlockGuard} of read/write
- * - if the SQL statement is an update, start transaction if not already in one.
- * otherwise, get lock on the database
- * - acquire reference on this object
- * - and then return the current time _after_ the database lock was acquired
- *
- *
- * This method removes the duplicate code from the other public
- * methods in this class.
- */
- private long acquireAndLock(boolean rwFlag) {
- mState = 0;
- // use pooled database connection handles for SELECT SQL statements
- mDatabase.verifyDbIsOpen();
- SQLiteDatabase db = ((mStatementType & SQLiteProgram.STATEMENT_USE_POOLED_CONN) > 0)
- ? mDatabase.getDbConnection(mSql) : mDatabase;
- // use the database connection obtained above
- mOrigDb = mDatabase;
- mDatabase = db;
- setNativeHandle(mDatabase.mNativeHandle);
- if (rwFlag == WRITE) {
- BlockGuard.getThreadPolicy().onWriteToDisk();
- } else {
- BlockGuard.getThreadPolicy().onReadFromDisk();
- }
-
- /*
- * Special case handling of SQLiteDatabase.execSQL("BEGIN transaction").
- * we know it is execSQL("BEGIN transaction") from the caller IF there is no lock held.
- * beginTransaction() methods in SQLiteDatabase call lockForced() before
- * calling execSQL("BEGIN transaction").
- */
- if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_BEGIN) {
- if (!mDatabase.isDbLockedByCurrentThread()) {
- // transaction is NOT started by calling beginTransaction() methods in
- // SQLiteDatabase
- mDatabase.setTransactionUsingExecSqlFlag();
- }
- } else if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
- DatabaseUtils.STATEMENT_UPDATE) {
- // got update SQL statement. if there is NO pending transaction, start one
- if (!mDatabase.inTransaction()) {
- mDatabase.beginTransactionNonExclusive();
- mState = TRANS_STARTED;
- }
- }
- // do I have database lock? if not, grab it.
- if (!mDatabase.isDbLockedByCurrentThread()) {
- mDatabase.lock(mSql);
- mState = LOCK_ACQUIRED;
- }
-
acquireReference();
- long startTime = SystemClock.uptimeMillis();
- mDatabase.closePendingStatements();
- compileAndbindAllArgs();
- return startTime;
+ try {
+ return getSession().executeForBlobFileDescriptor(
+ getSql(), getBindArgs(), getConnectionFlags());
+ } catch (SQLiteDatabaseCorruptException ex) {
+ onCorruption();
+ throw ex;
+ } finally {
+ releaseReference();
+ }
}
- /**
- * this method releases locks and references acquired in {@link #acquireAndLock(boolean)}
- */
- private void releaseAndUnlock() {
- releaseReference();
- if (mState == TRANS_STARTED) {
- try {
- mDatabase.setTransactionSuccessful();
- } finally {
- mDatabase.endTransaction();
- }
- } else if (mState == LOCK_ACQUIRED) {
- mDatabase.unlock();
- }
- if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
- DatabaseUtils.STATEMENT_COMMIT ||
- (mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
- DatabaseUtils.STATEMENT_ABORT) {
- mDatabase.resetTransactionUsingExecSqlFlag();
- }
- clearBindings();
- // release the compiled sql statement so that the caller's SQLiteStatement no longer
- // has a hard reference to a database object that may get deallocated at any point.
- release();
- // restore the database connection handle to the original value
- mDatabase = mOrigDb;
- setNativeHandle(mDatabase.mNativeHandle);
+ @Override
+ public String toString() {
+ return "SQLiteProgram: " + getSql();
}
-
- private final native int native_execute();
- private final native long native_executeInsert();
- private final native long native_1x1_long();
- private final native String native_1x1_string();
- private final native ParcelFileDescriptor native_1x1_blob_ashmem() throws IOException;
- private final native void native_executeSql(String sql);
}
diff --git a/core/java/android/database/sqlite/SQLiteStatementInfo.java b/core/java/android/database/sqlite/SQLiteStatementInfo.java
new file mode 100644
index 0000000000000..3edfdb05206c8
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteStatementInfo.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database.sqlite;
+
+/**
+ * Describes a SQLite statement.
+ *
+ * @hide
+ */
+public final class SQLiteStatementInfo {
+ /**
+ * The number of parameters that the statement has.
+ */
+ public int numParameters;
+
+ /**
+ * The names of all columns in the result set of the statement.
+ */
+ public String[] columnNames;
+
+ /**
+ * True if the statement is read-only.
+ */
+ public boolean readOnly;
+}
diff --git a/core/java/android/util/LruCache.java b/core/java/android/util/LruCache.java
index f1014a7ae1b19..51e373c8dddc3 100644
--- a/core/java/android/util/LruCache.java
+++ b/core/java/android/util/LruCache.java
@@ -85,6 +85,23 @@ public class LruCache {
this.map = new LinkedHashMap(0, 0.75f, true);
}
+ /**
+ * Sets the size of the cache.
+ * @param maxSize The new maximum size.
+ *
+ * @hide
+ */
+ public void resize(int maxSize) {
+ if (maxSize <= 0) {
+ throw new IllegalArgumentException("maxSize <= 0");
+ }
+
+ synchronized (this) {
+ this.maxSize = maxSize;
+ }
+ trimToSize(maxSize);
+ }
+
/**
* Returns the value for {@code key} if it exists in the cache or can be
* created by {@code #create}. If a value was returned, it is moved to the
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 8be1996d67a2c..39b84bba15d38 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -39,12 +39,10 @@ LOCAL_SRC_FILES:= \
android_opengl_GLES11Ext.cpp \
android_opengl_GLES20.cpp \
android_database_CursorWindow.cpp \
- android_database_SQLiteCompiledSql.cpp \
+ android_database_SQLiteCommon.cpp \
+ android_database_SQLiteConnection.cpp \
+ android_database_SQLiteGlobal.cpp \
android_database_SQLiteDebug.cpp \
- android_database_SQLiteDatabase.cpp \
- android_database_SQLiteProgram.cpp \
- android_database_SQLiteQuery.cpp \
- android_database_SQLiteStatement.cpp \
android_emoji_EmojiFactory.cpp \
android_view_Display.cpp \
android_view_DisplayEventReceiver.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index c00661532009e..8a3063fa1d796 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -121,12 +121,9 @@ extern int register_android_view_HardwareRenderer(JNIEnv* env);
extern int register_android_view_Surface(JNIEnv* env);
extern int register_android_view_TextureView(JNIEnv* env);
extern int register_android_database_CursorWindow(JNIEnv* env);
-extern int register_android_database_SQLiteCompiledSql(JNIEnv* env);
-extern int register_android_database_SQLiteDatabase(JNIEnv* env);
+extern int register_android_database_SQLiteConnection(JNIEnv* env);
+extern int register_android_database_SQLiteGlobal(JNIEnv* env);
extern int register_android_database_SQLiteDebug(JNIEnv* env);
-extern int register_android_database_SQLiteProgram(JNIEnv* env);
-extern int register_android_database_SQLiteQuery(JNIEnv* env);
-extern int register_android_database_SQLiteStatement(JNIEnv* env);
extern int register_android_debug_JNITest(JNIEnv* env);
extern int register_android_nio_utils(JNIEnv* env);
extern int register_android_text_format_Time(JNIEnv* env);
@@ -1141,12 +1138,9 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_graphics_YuvImage),
REG_JNI(register_android_database_CursorWindow),
- REG_JNI(register_android_database_SQLiteCompiledSql),
- REG_JNI(register_android_database_SQLiteDatabase),
+ REG_JNI(register_android_database_SQLiteConnection),
+ REG_JNI(register_android_database_SQLiteGlobal),
REG_JNI(register_android_database_SQLiteDebug),
- REG_JNI(register_android_database_SQLiteProgram),
- REG_JNI(register_android_database_SQLiteQuery),
- REG_JNI(register_android_database_SQLiteStatement),
REG_JNI(register_android_os_Debug),
REG_JNI(register_android_os_FileObserver),
REG_JNI(register_android_os_FileUtils),
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index 659fa357d99b3..d53644de318af 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -31,8 +31,8 @@
#include
#include "binder/CursorWindow.h"
-#include "sqlite3_exception.h"
#include "android_util_Binder.h"
+#include "android_database_SQLiteCommon.h"
namespace android {
diff --git a/core/jni/android_database_SQLiteCommon.cpp b/core/jni/android_database_SQLiteCommon.cpp
new file mode 100644
index 0000000000000..d5fdb15e57115
--- /dev/null
+++ b/core/jni/android_database_SQLiteCommon.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "android_database_SQLiteCommon.h"
+
+namespace android {
+
+/* throw a SQLiteException with a message appropriate for the error in handle */
+void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle) {
+ throw_sqlite3_exception(env, handle, NULL);
+}
+
+/* throw a SQLiteException with the given message */
+void throw_sqlite3_exception(JNIEnv* env, const char* message) {
+ throw_sqlite3_exception(env, NULL, message);
+}
+
+/* throw a SQLiteException with a message appropriate for the error in handle
+ concatenated with the given message
+ */
+void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle, const char* message) {
+ if (handle) {
+ throw_sqlite3_exception(env, sqlite3_errcode(handle),
+ sqlite3_errmsg(handle), message);
+ } else {
+ // we use SQLITE_OK so that a generic SQLiteException is thrown;
+ // any code not specified in the switch statement below would do.
+ throw_sqlite3_exception(env, SQLITE_OK, "unknown error", message);
+ }
+}
+
+/* throw a SQLiteException for a given error code */
+void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* message) {
+ if (errcode == SQLITE_DONE) {
+ throw_sqlite3_exception(env, errcode, NULL, message);
+ } else {
+ char temp[21];
+ sprintf(temp, "error code %d", errcode);
+ throw_sqlite3_exception(env, errcode, temp, message);
+ }
+}
+
+/* throw a SQLiteException for a given error code, sqlite3message, and
+ user message
+ */
+void throw_sqlite3_exception(JNIEnv* env, int errcode,
+ const char* sqlite3Message, const char* message) {
+ const char* exceptionClass;
+ switch (errcode) {
+ case SQLITE_IOERR:
+ exceptionClass = "android/database/sqlite/SQLiteDiskIOException";
+ break;
+ case SQLITE_CORRUPT:
+ case SQLITE_NOTADB: // treat "unsupported file format" error as corruption also
+ exceptionClass = "android/database/sqlite/SQLiteDatabaseCorruptException";
+ break;
+ case SQLITE_CONSTRAINT:
+ exceptionClass = "android/database/sqlite/SQLiteConstraintException";
+ break;
+ case SQLITE_ABORT:
+ exceptionClass = "android/database/sqlite/SQLiteAbortException";
+ break;
+ case SQLITE_DONE:
+ exceptionClass = "android/database/sqlite/SQLiteDoneException";
+ break;
+ case SQLITE_FULL:
+ exceptionClass = "android/database/sqlite/SQLiteFullException";
+ break;
+ case SQLITE_MISUSE:
+ exceptionClass = "android/database/sqlite/SQLiteMisuseException";
+ break;
+ case SQLITE_PERM:
+ exceptionClass = "android/database/sqlite/SQLiteAccessPermException";
+ break;
+ case SQLITE_BUSY:
+ exceptionClass = "android/database/sqlite/SQLiteDatabaseLockedException";
+ break;
+ case SQLITE_LOCKED:
+ exceptionClass = "android/database/sqlite/SQLiteTableLockedException";
+ break;
+ case SQLITE_READONLY:
+ exceptionClass = "android/database/sqlite/SQLiteReadOnlyDatabaseException";
+ break;
+ case SQLITE_CANTOPEN:
+ exceptionClass = "android/database/sqlite/SQLiteCantOpenDatabaseException";
+ break;
+ case SQLITE_TOOBIG:
+ exceptionClass = "android/database/sqlite/SQLiteBlobTooBigException";
+ break;
+ case SQLITE_RANGE:
+ exceptionClass = "android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException";
+ break;
+ case SQLITE_NOMEM:
+ exceptionClass = "android/database/sqlite/SQLiteOutOfMemoryException";
+ break;
+ case SQLITE_MISMATCH:
+ exceptionClass = "android/database/sqlite/SQLiteDatatypeMismatchException";
+ break;
+ case SQLITE_UNCLOSED:
+ exceptionClass = "android/database/sqlite/SQLiteUnfinalizedObjectsException";
+ break;
+ default:
+ exceptionClass = "android/database/sqlite/SQLiteException";
+ break;
+ }
+
+ if (sqlite3Message != NULL && message != NULL) {
+ char* fullMessage = (char *)malloc(strlen(sqlite3Message) + strlen(message) + 3);
+ if (fullMessage != NULL) {
+ strcpy(fullMessage, sqlite3Message);
+ strcat(fullMessage, ": ");
+ strcat(fullMessage, message);
+ jniThrowException(env, exceptionClass, fullMessage);
+ free(fullMessage);
+ } else {
+ jniThrowException(env, exceptionClass, sqlite3Message);
+ }
+ } else if (sqlite3Message != NULL) {
+ jniThrowException(env, exceptionClass, sqlite3Message);
+ } else {
+ jniThrowException(env, exceptionClass, message);
+ }
+}
+
+
+} // namespace android
diff --git a/core/jni/android_database_SQLiteCommon.h b/core/jni/android_database_SQLiteCommon.h
new file mode 100644
index 0000000000000..0cac176502e2d
--- /dev/null
+++ b/core/jni/android_database_SQLiteCommon.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_DATABASE_SQLITE_COMMON_H
+#define _ANDROID_DATABASE_SQLITE_COMMON_H
+
+#include
+#include
+
+#include
+
+// Special log tags defined in SQLiteDebug.java.
+#define SQLITE_LOG_TAG "SQLiteLog"
+#define SQLITE_TRACE_TAG "SQLiteStatements"
+#define SQLITE_PROFILE_TAG "SQLiteTime"
+
+namespace android {
+
+/* throw a SQLiteException with a message appropriate for the error in handle */
+void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle);
+
+/* throw a SQLiteException with the given message */
+void throw_sqlite3_exception(JNIEnv* env, const char* message);
+
+/* throw a SQLiteException with a message appropriate for the error in handle
+ concatenated with the given message
+ */
+void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle, const char* message);
+
+/* throw a SQLiteException for a given error code */
+void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* message);
+
+void throw_sqlite3_exception(JNIEnv* env, int errcode,
+ const char* sqlite3Message, const char* message);
+
+}
+
+#endif // _ANDROID_DATABASE_SQLITE_COMMON_H
diff --git a/core/jni/android_database_SQLiteCompiledSql.cpp b/core/jni/android_database_SQLiteCompiledSql.cpp
deleted file mode 100644
index 857267a18da5d..0000000000000
--- a/core/jni/android_database_SQLiteCompiledSql.cpp
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2006-2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "Cursor"
-
-#include
-#include
-#include
-
-#include
-
-#include
-
-#include
-#include
-#include
-
-#include "sqlite3_exception.h"
-
-
-namespace android {
-
-static jfieldID gHandleField;
-static jfieldID gStatementField;
-
-
-#define GET_STATEMENT(env, object) \
- (sqlite3_stmt *)env->GetIntField(object, gStatementField)
-#define GET_HANDLE(env, object) \
- (sqlite3 *)env->GetIntField(object, gHandleField)
-
-
-sqlite3_stmt * compile(JNIEnv* env, jobject object,
- sqlite3 * handle, jstring sqlString)
-{
- int err;
- jchar const * sql;
- jsize sqlLen;
- sqlite3_stmt * statement = GET_STATEMENT(env, object);
-
- // Make sure not to leak the statement if it already exists
- if (statement != NULL) {
- sqlite3_finalize(statement);
- env->SetIntField(object, gStatementField, 0);
- }
-
- // Compile the SQL
- sql = env->GetStringChars(sqlString, NULL);
- sqlLen = env->GetStringLength(sqlString);
- err = sqlite3_prepare16_v2(handle, sql, sqlLen * 2, &statement, NULL);
- env->ReleaseStringChars(sqlString, sql);
-
- if (err == SQLITE_OK) {
- // Store the statement in the Java object for future calls
- ALOGV("Prepared statement %p on %p", statement, handle);
- env->SetIntField(object, gStatementField, (int)statement);
- return statement;
- } else {
- // Error messages like 'near ")": syntax error' are not
- // always helpful enough, so construct an error string that
- // includes the query itself.
- const char *query = env->GetStringUTFChars(sqlString, NULL);
- char *message = (char*) malloc(strlen(query) + 50);
- if (message) {
- strcpy(message, ", while compiling: "); // less than 50 chars
- strcat(message, query);
- }
- env->ReleaseStringUTFChars(sqlString, query);
- throw_sqlite3_exception(env, handle, message);
- free(message);
- return NULL;
- }
-}
-
-static void native_compile(JNIEnv* env, jobject object, jstring sqlString)
-{
- compile(env, object, GET_HANDLE(env, object), sqlString);
-}
-
-
-static JNINativeMethod sMethods[] =
-{
- /* name, signature, funcPtr */
- {"native_compile", "(Ljava/lang/String;)V", (void *)native_compile},
-};
-
-int register_android_database_SQLiteCompiledSql(JNIEnv * env)
-{
- jclass clazz;
-
- clazz = env->FindClass("android/database/sqlite/SQLiteCompiledSql");
- if (clazz == NULL) {
- ALOGE("Can't find android/database/sqlite/SQLiteCompiledSql");
- return -1;
- }
-
- gHandleField = env->GetFieldID(clazz, "nHandle", "I");
- gStatementField = env->GetFieldID(clazz, "nStatement", "I");
-
- if (gHandleField == NULL || gStatementField == NULL) {
- ALOGE("Error locating fields");
- return -1;
- }
-
- return AndroidRuntime::registerNativeMethods(env,
- "android/database/sqlite/SQLiteCompiledSql", sMethods, NELEM(sMethods));
-}
-
-} // namespace android
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
new file mode 100644
index 0000000000000..d0d53f6f451dd
--- /dev/null
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -0,0 +1,959 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "SQLiteConnection"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "binder/CursorWindow.h"
+
+#include
+#include
+
+#include "android_database_SQLiteCommon.h"
+
+#define UTF16_STORAGE 0
+#define ANDROID_TABLE "android_metadata"
+
+namespace android {
+
+static struct {
+ jfieldID name;
+ jfieldID numArgs;
+ jmethodID dispatchCallback;
+} gSQLiteCustomFunctionClassInfo;
+
+static struct {
+ jclass clazz;
+} gStringClassInfo;
+
+struct SQLiteConnection {
+ // Open flags.
+ // Must be kept in sync with the constants defined in SQLiteDatabase.java.
+ enum {
+ OPEN_READWRITE = 0x00000000,
+ OPEN_READONLY = 0x00000001,
+ OPEN_READ_MASK = 0x00000001,
+ NO_LOCALIZED_COLLATORS = 0x00000010,
+ CREATE_IF_NECESSARY = 0x10000000,
+ };
+
+ sqlite3* const db;
+ const int openFlags;
+ const String8 path;
+ const String8 label;
+
+ SQLiteConnection(sqlite3* db, int openFlags, const String8& path, const String8& label) :
+ db(db), openFlags(openFlags), path(path), label(label) { }
+};
+
+// Called each time a statement begins execution, when tracing is enabled.
+static void sqliteTraceCallback(void *data, const char *sql) {
+ SQLiteConnection* connection = static_cast(data);
+ ALOG(LOG_VERBOSE, SQLITE_TRACE_TAG, "%s: \"%s\"\n",
+ connection->label.string(), sql);
+}
+
+// Called each time a statement finishes execution, when profiling is enabled.
+static void sqliteProfileCallback(void *data, const char *sql, sqlite3_uint64 tm) {
+ SQLiteConnection* connection = static_cast(data);
+ ALOG(LOG_VERBOSE, SQLITE_PROFILE_TAG, "%s: \"%s\" took %0.3f ms\n",
+ connection->label.string(), sql, tm * 0.000001f);
+}
+
+
+static jint nativeOpen(JNIEnv* env, jclass clazz, jstring pathStr, jint openFlags,
+ jstring labelStr, jboolean enableTrace, jboolean enableProfile) {
+ int sqliteFlags;
+ if (openFlags & SQLiteConnection::CREATE_IF_NECESSARY) {
+ sqliteFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
+ } else if (openFlags & SQLiteConnection::OPEN_READONLY) {
+ sqliteFlags = SQLITE_OPEN_READONLY;
+ } else {
+ sqliteFlags = SQLITE_OPEN_READWRITE;
+ }
+
+ const char* pathChars = env->GetStringUTFChars(pathStr, NULL);
+ String8 path(pathChars);
+ env->ReleaseStringUTFChars(pathStr, pathChars);
+
+ const char* labelChars = env->GetStringUTFChars(labelStr, NULL);
+ String8 label(labelChars);
+ env->ReleaseStringUTFChars(labelStr, labelChars);
+
+ sqlite3* db;
+ int err = sqlite3_open_v2(path.string(), &db, sqliteFlags, NULL);
+ if (err != SQLITE_OK) {
+ throw_sqlite3_exception_errcode(env, err, "Could not open database");
+ return 0;
+ }
+
+ // Set the default busy handler to retry for 1000ms and then return SQLITE_BUSY
+ err = sqlite3_busy_timeout(db, 1000 /* ms */);
+ if (err != SQLITE_OK) {
+ throw_sqlite3_exception(env, db, "Could not set busy timeout");
+ sqlite3_close(db);
+ return 0;
+ }
+
+ // Enable WAL auto-checkpointing after a commit whenever at least one frame is in the log.
+ // This ensures that a checkpoint will occur after each transaction if needed.
+ err = sqlite3_wal_autocheckpoint(db, 1);
+ if (err) {
+ throw_sqlite3_exception(env, db, "Could not enable auto-checkpointing.");
+ sqlite3_close(db);
+ return 0;
+ }
+
+ // Register custom Android functions.
+ err = register_android_functions(db, UTF16_STORAGE);
+ if (err) {
+ throw_sqlite3_exception(env, db, "Could not register Android SQL functions.");
+ sqlite3_close(db);
+ return 0;
+ }
+
+ // Create wrapper object.
+ SQLiteConnection* connection = new SQLiteConnection(db, openFlags, path, label);
+
+ // Enable tracing and profiling if requested.
+ if (enableTrace) {
+ sqlite3_trace(db, &sqliteTraceCallback, connection);
+ }
+ if (enableProfile) {
+ sqlite3_profile(db, &sqliteProfileCallback, connection);
+ }
+
+ ALOGV("Opened connection %p with label '%s'", db, label.string());
+ return reinterpret_cast(connection);
+}
+
+static void nativeClose(JNIEnv* env, jclass clazz, jint connectionPtr) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+
+ if (connection) {
+ ALOGV("Closing connection %p", connection->db);
+ int err = sqlite3_close(connection->db);
+ if (err != SQLITE_OK) {
+ // This can happen if sub-objects aren't closed first. Make sure the caller knows.
+ ALOGE("sqlite3_close(%p) failed: %d", connection->db, err);
+ throw_sqlite3_exception(env, connection->db, "Count not close db.");
+ return;
+ }
+
+ delete connection;
+ }
+}
+
+// Called each time a custom function is evaluated.
+static void sqliteCustomFunctionCallback(sqlite3_context *context,
+ int argc, sqlite3_value **argv) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+ // Get the callback function object.
+ // Create a new local reference to it in case the callback tries to do something
+ // dumb like unregister the function (thereby destroying the global ref) while it is running.
+ jobject functionObjGlobal = reinterpret_cast(sqlite3_user_data(context));
+ jobject functionObj = env->NewLocalRef(functionObjGlobal);
+
+ jobjectArray argsArray = env->NewObjectArray(argc, gStringClassInfo.clazz, NULL);
+ if (argsArray) {
+ for (int i = 0; i < argc; i++) {
+ const jchar* arg = static_cast(sqlite3_value_text16(argv[i]));
+ if (!arg) {
+ ALOGW("NULL argument in custom_function_callback. This should not happen.");
+ } else {
+ size_t argLen = sqlite3_value_bytes16(argv[i]) / sizeof(jchar);
+ jstring argStr = env->NewString(arg, argLen);
+ if (!argStr) {
+ goto error; // out of memory error
+ }
+ env->SetObjectArrayElement(argsArray, i, argStr);
+ env->DeleteLocalRef(argStr);
+ }
+ }
+
+ // TODO: Support functions that return values.
+ env->CallVoidMethod(functionObj,
+ gSQLiteCustomFunctionClassInfo.dispatchCallback, argsArray);
+
+error:
+ env->DeleteLocalRef(argsArray);
+ }
+
+ env->DeleteLocalRef(functionObj);
+
+ if (env->ExceptionCheck()) {
+ ALOGE("An exception was thrown by custom SQLite function.");
+ LOGE_EX(env);
+ env->ExceptionClear();
+ }
+}
+
+// Called when a custom function is destroyed.
+static void sqliteCustomFunctionDestructor(void* data) {
+ jobject functionObjGlobal = reinterpret_cast(data);
+
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->DeleteGlobalRef(functionObjGlobal);
+}
+
+static void nativeRegisterCustomFunction(JNIEnv* env, jclass clazz, jint connectionPtr,
+ jobject functionObj) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+
+ jstring nameStr = jstring(env->GetObjectField(
+ functionObj, gSQLiteCustomFunctionClassInfo.name));
+ jint numArgs = env->GetIntField(functionObj, gSQLiteCustomFunctionClassInfo.numArgs);
+
+ jobject functionObjGlobal = env->NewGlobalRef(functionObj);
+
+ const char* name = env->GetStringUTFChars(nameStr, NULL);
+ int err = sqlite3_create_function_v2(connection->db, name, numArgs, SQLITE_UTF16,
+ reinterpret_cast(functionObjGlobal),
+ &sqliteCustomFunctionCallback, NULL, NULL, &sqliteCustomFunctionDestructor);
+ env->ReleaseStringUTFChars(nameStr, name);
+
+ if (err != SQLITE_OK) {
+ ALOGE("sqlite3_create_function returned %d", err);
+ env->DeleteGlobalRef(functionObjGlobal);
+ throw_sqlite3_exception(env, connection->db);
+ return;
+ }
+}
+
+// Set locale in the android_metadata table, install localized collators, and rebuild indexes
+static void nativeSetLocale(JNIEnv* env, jclass clazz, jint connectionPtr, jstring localeStr) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+
+ if (connection->openFlags & SQLiteConnection::NO_LOCALIZED_COLLATORS) {
+ // We should probably throw IllegalStateException but the contract for
+ // setLocale says that we just do nothing. Oh well.
+ return;
+ }
+
+ int err;
+ char const* locale = env->GetStringUTFChars(localeStr, NULL);
+ sqlite3_stmt* stmt = NULL;
+ char** meta = NULL;
+ int rowCount, colCount;
+ char* dbLocale = NULL;
+
+ // create the table, if necessary and possible
+ if (!(connection->openFlags & SQLiteConnection::OPEN_READONLY)) {
+ err = sqlite3_exec(connection->db,
+ "CREATE TABLE IF NOT EXISTS " ANDROID_TABLE " (locale TEXT)",
+ NULL, NULL, NULL);
+ if (err != SQLITE_OK) {
+ ALOGE("CREATE TABLE " ANDROID_TABLE " failed");
+ throw_sqlite3_exception(env, connection->db);
+ goto done;
+ }
+ }
+
+ // try to read from the table
+ err = sqlite3_get_table(connection->db,
+ "SELECT locale FROM " ANDROID_TABLE " LIMIT 1",
+ &meta, &rowCount, &colCount, NULL);
+ if (err != SQLITE_OK) {
+ ALOGE("SELECT locale FROM " ANDROID_TABLE " failed");
+ throw_sqlite3_exception(env, connection->db);
+ goto done;
+ }
+
+ dbLocale = (rowCount >= 1) ? meta[colCount] : NULL;
+
+ if (dbLocale != NULL && !strcmp(dbLocale, locale)) {
+ // database locale is the same as the desired locale; set up the collators and go
+ err = register_localized_collators(connection->db, locale, UTF16_STORAGE);
+ if (err != SQLITE_OK) {
+ throw_sqlite3_exception(env, connection->db);
+ }
+ goto done; // no database changes needed
+ }
+
+ if (connection->openFlags & SQLiteConnection::OPEN_READONLY) {
+ // read-only database, so we're going to have to put up with whatever we got
+ // For registering new index. Not for modifing the read-only database.
+ err = register_localized_collators(connection->db, locale, UTF16_STORAGE);
+ if (err != SQLITE_OK) {
+ throw_sqlite3_exception(env, connection->db);
+ }
+ goto done;
+ }
+
+ // need to update android_metadata and indexes atomically, so use a transaction...
+ err = sqlite3_exec(connection->db, "BEGIN TRANSACTION", NULL, NULL, NULL);
+ if (err != SQLITE_OK) {
+ ALOGE("BEGIN TRANSACTION failed setting locale");
+ throw_sqlite3_exception(env, connection->db);
+ goto done;
+ }
+
+ err = register_localized_collators(connection->db, locale, UTF16_STORAGE);
+ if (err != SQLITE_OK) {
+ ALOGE("register_localized_collators() failed setting locale");
+ throw_sqlite3_exception(env, connection->db);
+ goto rollback;
+ }
+
+ err = sqlite3_exec(connection->db, "DELETE FROM " ANDROID_TABLE, NULL, NULL, NULL);
+ if (err != SQLITE_OK) {
+ ALOGE("DELETE failed setting locale");
+ throw_sqlite3_exception(env, connection->db);
+ goto rollback;
+ }
+
+ static const char *sql = "INSERT INTO " ANDROID_TABLE " (locale) VALUES(?);";
+ err = sqlite3_prepare_v2(connection->db, sql, -1, &stmt, NULL);
+ if (err != SQLITE_OK) {
+ ALOGE("sqlite3_prepare_v2(\"%s\") failed", sql);
+ throw_sqlite3_exception(env, connection->db);
+ goto rollback;
+ }
+
+ err = sqlite3_bind_text(stmt, 1, locale, -1, SQLITE_TRANSIENT);
+ if (err != SQLITE_OK) {
+ ALOGE("sqlite3_bind_text() failed setting locale");
+ throw_sqlite3_exception(env, connection->db);
+ goto rollback;
+ }
+
+ err = sqlite3_step(stmt);
+ if (err != SQLITE_OK && err != SQLITE_DONE) {
+ ALOGE("sqlite3_step(\"%s\") failed setting locale", sql);
+ throw_sqlite3_exception(env, connection->db);
+ goto rollback;
+ }
+
+ err = sqlite3_exec(connection->db, "REINDEX LOCALIZED", NULL, NULL, NULL);
+ if (err != SQLITE_OK) {
+ ALOGE("REINDEX LOCALIZED failed");
+ throw_sqlite3_exception(env, connection->db);
+ goto rollback;
+ }
+
+ // all done, yay!
+ err = sqlite3_exec(connection->db, "COMMIT TRANSACTION", NULL, NULL, NULL);
+ if (err != SQLITE_OK) {
+ ALOGE("COMMIT TRANSACTION failed setting locale");
+ throw_sqlite3_exception(env, connection->db);
+ goto done;
+ }
+
+rollback:
+ if (err != SQLITE_OK) {
+ sqlite3_exec(connection->db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
+ }
+
+done:
+ if (stmt) {
+ sqlite3_finalize(stmt);
+ }
+ if (meta) {
+ sqlite3_free_table(meta);
+ }
+ if (locale) {
+ env->ReleaseStringUTFChars(localeStr, locale);
+ }
+}
+
+static jint nativePrepareStatement(JNIEnv* env, jclass clazz, jint connectionPtr,
+ jstring sqlString) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+
+ jsize sqlLength = env->GetStringLength(sqlString);
+ const jchar* sql = env->GetStringCritical(sqlString, NULL);
+ sqlite3_stmt* statement;
+ int err = sqlite3_prepare16_v2(connection->db,
+ sql, sqlLength * sizeof(jchar), &statement, NULL);
+ env->ReleaseStringCritical(sqlString, sql);
+
+ if (err != SQLITE_OK) {
+ // Error messages like 'near ")": syntax error' are not
+ // always helpful enough, so construct an error string that
+ // includes the query itself.
+ const char *query = env->GetStringUTFChars(sqlString, NULL);
+ char *message = (char*) malloc(strlen(query) + 50);
+ if (message) {
+ strcpy(message, ", while compiling: "); // less than 50 chars
+ strcat(message, query);
+ }
+ env->ReleaseStringUTFChars(sqlString, query);
+ throw_sqlite3_exception(env, connection->db, message);
+ free(message);
+ return 0;
+ }
+
+ ALOGV("Prepared statement %p on connection %p", statement, connection->db);
+ return reinterpret_cast(statement);
+}
+
+static void nativeFinalizeStatement(JNIEnv* env, jclass clazz, jint connectionPtr,
+ jint statementPtr) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ sqlite3_stmt* statement = reinterpret_cast(statementPtr);
+
+ ALOGV("Finalized statement %p on connection %p", statement, connection->db);
+ int err = sqlite3_finalize(statement);
+ if (err != SQLITE_OK) {
+ throw_sqlite3_exception(env, connection->db, NULL);
+ }
+}
+
+static jint nativeGetParameterCount(JNIEnv* env, jclass clazz, jint connectionPtr,
+ jint statementPtr) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ sqlite3_stmt* statement = reinterpret_cast(statementPtr);
+
+ return sqlite3_bind_parameter_count(statement);
+}
+
+static jboolean nativeIsReadOnly(JNIEnv* env, jclass clazz, jint connectionPtr,
+ jint statementPtr) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ sqlite3_stmt* statement = reinterpret_cast(statementPtr);
+
+ return sqlite3_stmt_readonly(statement) != 0;
+}
+
+static jint nativeGetColumnCount(JNIEnv* env, jclass clazz, jint connectionPtr,
+ jint statementPtr) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ sqlite3_stmt* statement = reinterpret_cast(statementPtr);
+
+ return sqlite3_column_count(statement);
+}
+
+static jstring nativeGetColumnName(JNIEnv* env, jclass clazz, jint connectionPtr,
+ jint statementPtr, jint index) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ sqlite3_stmt* statement = reinterpret_cast(statementPtr);
+
+ const jchar* name = static_cast(sqlite3_column_name16(statement, index));
+ if (name) {
+ size_t length = 0;
+ while (name[length]) {
+ length += 1;
+ }
+ return env->NewString(name, length);
+ }
+ return NULL;
+}
+
+static void nativeBindNull(JNIEnv* env, jclass clazz, jint connectionPtr,
+ jint statementPtr, jint index) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ sqlite3_stmt* statement = reinterpret_cast(statementPtr);
+
+ int err = sqlite3_bind_null(statement, index);
+ if (err != SQLITE_OK) {
+ throw_sqlite3_exception(env, connection->db, NULL);
+ }
+}
+
+static void nativeBindLong(JNIEnv* env, jclass clazz, jint connectionPtr,
+ jint statementPtr, jint index, jlong value) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ sqlite3_stmt* statement = reinterpret_cast(statementPtr);
+
+ int err = sqlite3_bind_int64(statement, index, value);
+ if (err != SQLITE_OK) {
+ throw_sqlite3_exception(env, connection->db, NULL);
+ }
+}
+
+static void nativeBindDouble(JNIEnv* env, jclass clazz, jint connectionPtr,
+ jint statementPtr, jint index, jdouble value) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ sqlite3_stmt* statement = reinterpret_cast(statementPtr);
+
+ int err = sqlite3_bind_double(statement, index, value);
+ if (err != SQLITE_OK) {
+ throw_sqlite3_exception(env, connection->db, NULL);
+ }
+}
+
+static void nativeBindString(JNIEnv* env, jclass clazz, jint connectionPtr,
+ jint statementPtr, jint index, jstring valueString) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ sqlite3_stmt* statement = reinterpret_cast(statementPtr);
+
+ jsize valueLength = env->GetStringLength(valueString);
+ const jchar* value = env->GetStringCritical(valueString, NULL);
+ int err = sqlite3_bind_text16(statement, index, value, valueLength * sizeof(jchar),
+ SQLITE_TRANSIENT);
+ env->ReleaseStringCritical(valueString, value);
+ if (err != SQLITE_OK) {
+ throw_sqlite3_exception(env, connection->db, NULL);
+ }
+}
+
+static void nativeBindBlob(JNIEnv* env, jclass clazz, jint connectionPtr,
+ jint statementPtr, jint index, jbyteArray valueArray) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ sqlite3_stmt* statement = reinterpret_cast(statementPtr);
+
+ jsize valueLength = env->GetArrayLength(valueArray);
+ jbyte* value = static_cast(env->GetPrimitiveArrayCritical(valueArray, NULL));
+ int err = sqlite3_bind_blob(statement, index, value, valueLength, SQLITE_TRANSIENT);
+ env->ReleasePrimitiveArrayCritical(valueArray, value, JNI_ABORT);
+ if (err != SQLITE_OK) {
+ throw_sqlite3_exception(env, connection->db, NULL);
+ }
+}
+
+static void nativeResetStatementAndClearBindings(JNIEnv* env, jclass clazz, jint connectionPtr,
+ jint statementPtr) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ sqlite3_stmt* statement = reinterpret_cast(statementPtr);
+
+ int err = sqlite3_reset(statement);
+ if (err == SQLITE_OK) {
+ err = sqlite3_clear_bindings(statement);
+ }
+ if (err != SQLITE_OK) {
+ throw_sqlite3_exception(env, connection->db, NULL);
+ }
+}
+
+static int executeNonQuery(JNIEnv* env, SQLiteConnection* connection, sqlite3_stmt* statement) {
+ int err = sqlite3_step(statement);
+ if (err == SQLITE_ROW) {
+ throw_sqlite3_exception(env,
+ "Queries can be performed using SQLiteDatabase query or rawQuery methods only.");
+ } else if (err != SQLITE_DONE) {
+ throw_sqlite3_exception_errcode(env, err, sqlite3_errmsg(connection->db));
+ }
+ return err;
+}
+
+static void nativeExecute(JNIEnv* env, jclass clazz, jint connectionPtr,
+ jint statementPtr) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ sqlite3_stmt* statement = reinterpret_cast(statementPtr);
+
+ executeNonQuery(env, connection, statement);
+}
+
+static jint nativeExecuteForChangedRowCount(JNIEnv* env, jclass clazz,
+ jint connectionPtr, jint statementPtr) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ sqlite3_stmt* statement = reinterpret_cast(statementPtr);
+
+ int err = executeNonQuery(env, connection, statement);
+ return err == SQLITE_DONE ? sqlite3_changes(connection->db) : -1;
+}
+
+static jlong nativeExecuteForLastInsertedRowId(JNIEnv* env, jclass clazz,
+ jint connectionPtr, jint statementPtr) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ sqlite3_stmt* statement = reinterpret_cast(statementPtr);
+
+ int err = executeNonQuery(env, connection, statement);
+ return err == SQLITE_DONE && sqlite3_changes(connection->db) > 0
+ ? sqlite3_last_insert_rowid(connection->db) : -1;
+}
+
+static int executeOneRowQuery(JNIEnv* env, SQLiteConnection* connection, sqlite3_stmt* statement) {
+ int err = sqlite3_step(statement);
+ if (err != SQLITE_ROW) {
+ throw_sqlite3_exception_errcode(env, err, sqlite3_errmsg(connection->db));
+ }
+ return err;
+}
+
+static jlong nativeExecuteForLong(JNIEnv* env, jclass clazz,
+ jint connectionPtr, jint statementPtr) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ sqlite3_stmt* statement = reinterpret_cast(statementPtr);
+
+ int err = executeOneRowQuery(env, connection, statement);
+ if (err == SQLITE_ROW && sqlite3_column_count(statement) >= 1) {
+ return sqlite3_column_int64(statement, 0);
+ }
+ return -1;
+}
+
+static jstring nativeExecuteForString(JNIEnv* env, jclass clazz,
+ jint connectionPtr, jint statementPtr) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ sqlite3_stmt* statement = reinterpret_cast(statementPtr);
+
+ int err = executeOneRowQuery(env, connection, statement);
+ if (err == SQLITE_ROW && sqlite3_column_count(statement) >= 1) {
+ const jchar* text = static_cast(sqlite3_column_text16(statement, 0));
+ if (text) {
+ size_t length = sqlite3_column_bytes16(statement, 0) / sizeof(jchar);
+ return env->NewString(text, length);
+ }
+ }
+ return NULL;
+}
+
+static int createAshmemRegionWithData(JNIEnv* env, const void* data, size_t length) {
+ int error = 0;
+ int fd = ashmem_create_region(NULL, length);
+ if (fd < 0) {
+ error = errno;
+ ALOGE("ashmem_create_region failed: %s", strerror(error));
+ } else {
+ if (length > 0) {
+ void* ptr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (ptr == MAP_FAILED) {
+ error = errno;
+ ALOGE("mmap failed: %s", strerror(error));
+ } else {
+ memcpy(ptr, data, length);
+ munmap(ptr, length);
+ }
+ }
+
+ if (!error) {
+ if (ashmem_set_prot_region(fd, PROT_READ) < 0) {
+ error = errno;
+ ALOGE("ashmem_set_prot_region failed: %s", strerror(errno));
+ } else {
+ return fd;
+ }
+ }
+
+ close(fd);
+ }
+
+ jniThrowIOException(env, error);
+ return -1;
+}
+
+static jint nativeExecuteForBlobFileDescriptor(JNIEnv* env, jclass clazz,
+ jint connectionPtr, jint statementPtr) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ sqlite3_stmt* statement = reinterpret_cast(statementPtr);
+
+ int err = executeOneRowQuery(env, connection, statement);
+ if (err == SQLITE_ROW && sqlite3_column_count(statement) >= 1) {
+ const void* blob = sqlite3_column_blob(statement, 0);
+ if (blob) {
+ int length = sqlite3_column_bytes(statement, 0);
+ if (length >= 0) {
+ return createAshmemRegionWithData(env, blob, length);
+ }
+ }
+ }
+ return -1;
+}
+
+enum CopyRowResult {
+ CPR_OK,
+ CPR_FULL,
+ CPR_ERROR,
+};
+
+static CopyRowResult copyRow(JNIEnv* env, CursorWindow* window,
+ sqlite3_stmt* statement, int numColumns, int startPos, int addedRows) {
+ // Allocate a new field directory for the row.
+ status_t status = window->allocRow();
+ if (status) {
+ LOG_WINDOW("Failed allocating fieldDir at startPos %d row %d, error=%d",
+ startPos, addedRows, status);
+ return CPR_FULL;
+ }
+
+ // Pack the row into the window.
+ CopyRowResult result = CPR_OK;
+ for (int i = 0; i < numColumns; i++) {
+ int type = sqlite3_column_type(statement, i);
+ if (type == SQLITE_TEXT) {
+ // TEXT data
+ const char* text = reinterpret_cast(
+ sqlite3_column_text(statement, i));
+ // SQLite does not include the NULL terminator in size, but does
+ // ensure all strings are NULL terminated, so increase size by
+ // one to make sure we store the terminator.
+ size_t sizeIncludingNull = sqlite3_column_bytes(statement, i) + 1;
+ status = window->putString(addedRows, i, text, sizeIncludingNull);
+ if (status) {
+ LOG_WINDOW("Failed allocating %u bytes for text at %d,%d, error=%d",
+ sizeIncludingNull, startPos + addedRows, i, status);
+ result = CPR_FULL;
+ break;
+ }
+ LOG_WINDOW("%d,%d is TEXT with %u bytes",
+ startPos + addedRows, i, sizeIncludingNull);
+ } else if (type == SQLITE_INTEGER) {
+ // INTEGER data
+ int64_t value = sqlite3_column_int64(statement, i);
+ status = window->putLong(addedRows, i, value);
+ if (status) {
+ LOG_WINDOW("Failed allocating space for a long in column %d, error=%d",
+ i, status);
+ result = CPR_FULL;
+ break;
+ }
+ LOG_WINDOW("%d,%d is INTEGER 0x%016llx", startPos + addedRows, i, value);
+ } else if (type == SQLITE_FLOAT) {
+ // FLOAT data
+ double value = sqlite3_column_double(statement, i);
+ status = window->putDouble(addedRows, i, value);
+ if (status) {
+ LOG_WINDOW("Failed allocating space for a double in column %d, error=%d",
+ i, status);
+ result = CPR_FULL;
+ break;
+ }
+ LOG_WINDOW("%d,%d is FLOAT %lf", startPos + addedRows, i, value);
+ } else if (type == SQLITE_BLOB) {
+ // BLOB data
+ const void* blob = sqlite3_column_blob(statement, i);
+ size_t size = sqlite3_column_bytes(statement, i);
+ status = window->putBlob(addedRows, i, blob, size);
+ if (status) {
+ LOG_WINDOW("Failed allocating %u bytes for blob at %d,%d, error=%d",
+ size, startPos + addedRows, i, status);
+ result = CPR_FULL;
+ break;
+ }
+ LOG_WINDOW("%d,%d is Blob with %u bytes",
+ startPos + addedRows, i, size);
+ } else if (type == SQLITE_NULL) {
+ // NULL field
+ status = window->putNull(addedRows, i);
+ if (status) {
+ LOG_WINDOW("Failed allocating space for a null in column %d, error=%d",
+ i, status);
+ result = CPR_FULL;
+ break;
+ }
+
+ LOG_WINDOW("%d,%d is NULL", startPos + addedRows, i);
+ } else {
+ // Unknown data
+ ALOGE("Unknown column type when filling database window");
+ throw_sqlite3_exception(env, "Unknown column type when filling window");
+ result = CPR_ERROR;
+ break;
+ }
+ }
+
+ // Free the last row if if was not successfully copied.
+ if (result != CPR_OK) {
+ window->freeLastRow();
+ }
+ return result;
+}
+
+static jlong nativeExecuteForCursorWindow(JNIEnv* env, jclass clazz,
+ jint connectionPtr, jint statementPtr, jint windowPtr,
+ jint startPos, jint requiredPos, jboolean countAllRows) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ sqlite3_stmt* statement = reinterpret_cast(statementPtr);
+ CursorWindow* window = reinterpret_cast(windowPtr);
+
+ status_t status = window->clear();
+ if (status) {
+ String8 msg;
+ msg.appendFormat("Failed to clear the cursor window, status=%d", status);
+ throw_sqlite3_exception(env, connection->db, msg.string());
+ return 0;
+ }
+
+ int numColumns = sqlite3_column_count(statement);
+ status = window->setNumColumns(numColumns);
+ if (status) {
+ String8 msg;
+ msg.appendFormat("Failed to set the cursor window column count to %d, status=%d",
+ numColumns, status);
+ throw_sqlite3_exception(env, connection->db, msg.string());
+ return 0;
+ }
+
+ int retryCount = 0;
+ int totalRows = 0;
+ int addedRows = 0;
+ bool windowFull = false;
+ bool gotException = false;
+ while (!gotException && (!windowFull || countAllRows)) {
+ int err = sqlite3_step(statement);
+ if (err == SQLITE_ROW) {
+ LOG_WINDOW("Stepped statement %p to row %d", statement, totalRows);
+ retryCount = 0;
+ totalRows += 1;
+
+ // Skip the row if the window is full or we haven't reached the start position yet.
+ if (startPos >= totalRows || windowFull) {
+ continue;
+ }
+
+ CopyRowResult cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);
+ if (cpr == CPR_FULL && addedRows && startPos + addedRows < requiredPos) {
+ // We filled the window before we got to the one row that we really wanted.
+ // Clear the window and start filling it again from here.
+ // TODO: Would be nicer if we could progressively replace earlier rows.
+ window->clear();
+ window->setNumColumns(numColumns);
+ startPos += addedRows;
+ addedRows = 0;
+ cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);
+ }
+
+ if (cpr == CPR_OK) {
+ addedRows += 1;
+ } else if (cpr == CPR_FULL) {
+ windowFull = true;
+ } else {
+ gotException = true;
+ }
+ } else if (err == SQLITE_DONE) {
+ // All rows processed, bail
+ LOG_WINDOW("Processed all rows");
+ break;
+ } else if (err == SQLITE_LOCKED || err == SQLITE_BUSY) {
+ // The table is locked, retry
+ LOG_WINDOW("Database locked, retrying");
+ if (retryCount > 50) {
+ ALOGE("Bailing on database busy retry");
+ throw_sqlite3_exception(env, connection->db, "retrycount exceeded");
+ gotException = true;
+ } else {
+ // Sleep to give the thread holding the lock a chance to finish
+ usleep(1000);
+ retryCount++;
+ }
+ } else {
+ throw_sqlite3_exception(env, connection->db);
+ gotException = true;
+ }
+ }
+
+ LOG_WINDOW("Resetting statement %p after fetching %d rows and adding %d rows"
+ "to the window in %d bytes",
+ statement, totalRows, addedRows, window->size() - window->freeSpace());
+ sqlite3_reset(statement);
+
+ // Report the total number of rows on request.
+ if (startPos > totalRows) {
+ ALOGE("startPos %d > actual rows %d", startPos, totalRows);
+ }
+ jlong result = jlong(startPos) << 32 | jlong(totalRows);
+ return result;
+}
+
+static jint nativeGetDbLookaside(JNIEnv* env, jobject clazz, jint connectionPtr) {
+ SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+
+ int cur = -1;
+ int unused;
+ sqlite3_db_status(connection->db, SQLITE_DBSTATUS_LOOKASIDE_USED, &cur, &unused, 0);
+ return cur;
+}
+
+
+static JNINativeMethod sMethods[] =
+{
+ /* name, signature, funcPtr */
+ { "nativeOpen", "(Ljava/lang/String;ILjava/lang/String;ZZ)I",
+ (void*)nativeOpen },
+ { "nativeClose", "(I)V",
+ (void*)nativeClose },
+ { "nativeRegisterCustomFunction", "(ILandroid/database/sqlite/SQLiteCustomFunction;)V",
+ (void*)nativeRegisterCustomFunction },
+ { "nativeSetLocale", "(ILjava/lang/String;)V",
+ (void*)nativeSetLocale },
+ { "nativePrepareStatement", "(ILjava/lang/String;)I",
+ (void*)nativePrepareStatement },
+ { "nativeFinalizeStatement", "(II)V",
+ (void*)nativeFinalizeStatement },
+ { "nativeGetParameterCount", "(II)I",
+ (void*)nativeGetParameterCount },
+ { "nativeIsReadOnly", "(II)Z",
+ (void*)nativeIsReadOnly },
+ { "nativeGetColumnCount", "(II)I",
+ (void*)nativeGetColumnCount },
+ { "nativeGetColumnName", "(III)Ljava/lang/String;",
+ (void*)nativeGetColumnName },
+ { "nativeBindNull", "(III)V",
+ (void*)nativeBindNull },
+ { "nativeBindLong", "(IIIJ)V",
+ (void*)nativeBindLong },
+ { "nativeBindDouble", "(IIID)V",
+ (void*)nativeBindDouble },
+ { "nativeBindString", "(IIILjava/lang/String;)V",
+ (void*)nativeBindString },
+ { "nativeBindBlob", "(III[B)V",
+ (void*)nativeBindBlob },
+ { "nativeResetStatementAndClearBindings", "(II)V",
+ (void*)nativeResetStatementAndClearBindings },
+ { "nativeExecute", "(II)V",
+ (void*)nativeExecute },
+ { "nativeExecuteForLong", "(II)J",
+ (void*)nativeExecuteForLong },
+ { "nativeExecuteForString", "(II)Ljava/lang/String;",
+ (void*)nativeExecuteForString },
+ { "nativeExecuteForBlobFileDescriptor", "(II)I",
+ (void*)nativeExecuteForBlobFileDescriptor },
+ { "nativeExecuteForChangedRowCount", "(II)I",
+ (void*)nativeExecuteForChangedRowCount },
+ { "nativeExecuteForLastInsertedRowId", "(II)J",
+ (void*)nativeExecuteForLastInsertedRowId },
+ { "nativeExecuteForCursorWindow", "(IIIIIZ)J",
+ (void*)nativeExecuteForCursorWindow },
+ { "nativeGetDbLookaside", "(I)I",
+ (void*)nativeGetDbLookaside },
+};
+
+#define FIND_CLASS(var, className) \
+ var = env->FindClass(className); \
+ LOG_FATAL_IF(! var, "Unable to find class " className);
+
+#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \
+ var = env->GetMethodID(clazz, methodName, fieldDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find method" methodName);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+ var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+int register_android_database_SQLiteConnection(JNIEnv *env)
+{
+ jclass clazz;
+ FIND_CLASS(clazz, "android/database/sqlite/SQLiteCustomFunction");
+
+ GET_FIELD_ID(gSQLiteCustomFunctionClassInfo.name, clazz,
+ "name", "Ljava/lang/String;");
+ GET_FIELD_ID(gSQLiteCustomFunctionClassInfo.numArgs, clazz,
+ "numArgs", "I");
+ GET_METHOD_ID(gSQLiteCustomFunctionClassInfo.dispatchCallback,
+ clazz, "dispatchCallback", "([Ljava/lang/String;)V");
+
+ FIND_CLASS(clazz, "java/lang/String");
+ gStringClassInfo.clazz = jclass(env->NewGlobalRef(clazz));
+
+ return AndroidRuntime::registerNativeMethods(env, "android/database/sqlite/SQLiteConnection",
+ sMethods, NELEM(sMethods));
+}
+
+} // namespace android
diff --git a/core/jni/android_database_SQLiteDatabase.cpp b/core/jni/android_database_SQLiteDatabase.cpp
deleted file mode 100644
index 28c421de6768f..0000000000000
--- a/core/jni/android_database_SQLiteDatabase.cpp
+++ /dev/null
@@ -1,636 +0,0 @@
-/*
- * Copyright (C) 2006-2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "SqliteDatabaseCpp"
-
-#include
-#include
-#include
-
-#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "sqlite3_exception.h"
-
-#define UTF16_STORAGE 0
-#define INVALID_VERSION -1
-#define ANDROID_TABLE "android_metadata"
-/* uncomment the next line to force-enable logging of all statements */
-// #define DB_LOG_STATEMENTS
-
-#define DEBUG_JNI 0
-
-namespace android {
-
-enum {
- OPEN_READWRITE = 0x00000000,
- OPEN_READONLY = 0x00000001,
- OPEN_READ_MASK = 0x00000001,
- NO_LOCALIZED_COLLATORS = 0x00000010,
- CREATE_IF_NECESSARY = 0x10000000
-};
-
-static jfieldID offset_db_handle;
-static jmethodID method_custom_function_callback;
-static jclass string_class;
-static jint sSqliteSoftHeapLimit = 0;
-
-static char *createStr(const char *path, short extra) {
- int len = strlen(path) + extra;
- char *str = (char *)malloc(len + 1);
- strncpy(str, path, len);
- str[len] = NULL;
- return str;
-}
-
-static void sqlLogger(void *databaseName, int iErrCode, const char *zMsg) {
- // skip printing this message if it is due to certain types of errors
- if (iErrCode == 0 || iErrCode == SQLITE_CONSTRAINT) return;
- // print databasename, errorcode and msg
- ALOGI("sqlite returned: error code = %d, msg = %s, db=%s\n", iErrCode, zMsg, databaseName);
-}
-
-// register the logging func on sqlite. needs to be done BEFORE any sqlite3 func is called.
-static void registerLoggingFunc(const char *path) {
- static bool loggingFuncSet = false;
- if (loggingFuncSet) {
- return;
- }
-
- ALOGV("Registering sqlite logging func \n");
- int err = sqlite3_config(SQLITE_CONFIG_LOG, &sqlLogger, (void *)createStr(path, 0));
- if (err != SQLITE_OK) {
- ALOGW("sqlite returned error = %d when trying to register logging func.\n", err);
- return;
- }
- loggingFuncSet = true;
-}
-
-/* public native void dbopen(String path, int flags, String locale); */
-static void dbopen(JNIEnv* env, jobject object, jstring pathString, jint flags)
-{
- int err;
- sqlite3 * handle = NULL;
- sqlite3_stmt * statement = NULL;
- char const * path8 = env->GetStringUTFChars(pathString, NULL);
- int sqliteFlags;
-
- // register the logging func on sqlite. needs to be done BEFORE any sqlite3 func is called.
- registerLoggingFunc(path8);
-
- // convert our flags into the sqlite flags
- if (flags & CREATE_IF_NECESSARY) {
- sqliteFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
- } else if (flags & OPEN_READONLY) {
- sqliteFlags = SQLITE_OPEN_READONLY;
- } else {
- sqliteFlags = SQLITE_OPEN_READWRITE;
- }
-
- err = sqlite3_open_v2(path8, &handle, sqliteFlags, NULL);
- if (err != SQLITE_OK) {
- ALOGE("sqlite3_open_v2(\"%s\", &handle, %d, NULL) failed\n", path8, sqliteFlags);
- throw_sqlite3_exception(env, handle);
- goto done;
- }
-
- // The soft heap limit prevents the page cache allocations from growing
- // beyond the given limit, no matter what the max page cache sizes are
- // set to. The limit does not, as of 3.5.0, affect any other allocations.
- sqlite3_soft_heap_limit(sSqliteSoftHeapLimit);
-
- // Set the default busy handler to retry for 1000ms and then return SQLITE_BUSY
- err = sqlite3_busy_timeout(handle, 1000 /* ms */);
- if (err != SQLITE_OK) {
- ALOGE("sqlite3_busy_timeout(handle, 1000) failed for \"%s\"\n", path8);
- throw_sqlite3_exception(env, handle);
- goto done;
- }
-
-#ifdef DB_INTEGRITY_CHECK
- static const char* integritySql = "pragma integrity_check(1);";
- err = sqlite3_prepare_v2(handle, integritySql, -1, &statement, NULL);
- if (err != SQLITE_OK) {
- ALOGE("sqlite_prepare_v2(handle, \"%s\") failed for \"%s\"\n", integritySql, path8);
- throw_sqlite3_exception(env, handle);
- goto done;
- }
-
- // first is OK or error message
- err = sqlite3_step(statement);
- if (err != SQLITE_ROW) {
- ALOGE("integrity check failed for \"%s\"\n", integritySql, path8);
- throw_sqlite3_exception(env, handle);
- goto done;
- } else {
- const char *text = (const char*)sqlite3_column_text(statement, 0);
- if (strcmp(text, "ok") != 0) {
- ALOGE("integrity check failed for \"%s\": %s\n", integritySql, path8, text);
- jniThrowException(env, "android/database/sqlite/SQLiteDatabaseCorruptException", text);
- goto done;
- }
- }
-#endif
-
- err = register_android_functions(handle, UTF16_STORAGE);
- if (err) {
- throw_sqlite3_exception(env, handle);
- goto done;
- }
-
- ALOGV("Opened '%s' - %p\n", path8, handle);
- env->SetIntField(object, offset_db_handle, (int) handle);
- handle = NULL; // The caller owns the handle now.
-
-done:
- // Release allocated resources
- if (path8 != NULL) env->ReleaseStringUTFChars(pathString, path8);
- if (statement != NULL) sqlite3_finalize(statement);
- if (handle != NULL) sqlite3_close(handle);
-}
-
-static char *getDatabaseName(JNIEnv* env, sqlite3 * handle, jstring databaseName, short connNum) {
- char const *path = env->GetStringUTFChars(databaseName, NULL);
- if (path == NULL) {
- ALOGE("Failure in getDatabaseName(). VM ran out of memory?\n");
- return NULL; // VM would have thrown OutOfMemoryError
- }
- char *dbNameStr = createStr(path, 4);
- if (connNum > 999) { // TODO: if number of pooled connections > 999, fix this line.
- connNum = -1;
- }
- sprintf(dbNameStr + strlen(path), "|%03d", connNum);
- env->ReleaseStringUTFChars(databaseName, path);
- return dbNameStr;
-}
-
-static void sqlTrace(void *databaseName, const char *sql) {
- ALOGI("sql_statement|%s|%s\n", (char *)databaseName, sql);
-}
-
-/* public native void enableSqlTracing(); */
-static void enableSqlTracing(JNIEnv* env, jobject object, jstring databaseName, jshort connType)
-{
- sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
- sqlite3_trace(handle, &sqlTrace, (void *)getDatabaseName(env, handle, databaseName, connType));
-}
-
-static void sqlProfile(void *databaseName, const char *sql, sqlite3_uint64 tm) {
- double d = tm/1000000.0;
- ALOGI("elapsedTime4Sql|%s|%.3f ms|%s\n", (char *)databaseName, d, sql);
-}
-
-/* public native void enableSqlProfiling(); */
-static void enableSqlProfiling(JNIEnv* env, jobject object, jstring databaseName, jshort connType)
-{
- sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
- sqlite3_profile(handle, &sqlProfile, (void *)getDatabaseName(env, handle, databaseName,
- connType));
-}
-
-/* public native void close(); */
-static void dbclose(JNIEnv* env, jobject object)
-{
- sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
-
- if (handle != NULL) {
- // release the memory associated with the traceFuncArg in enableSqlTracing function
- void *traceFuncArg = sqlite3_trace(handle, &sqlTrace, NULL);
- if (traceFuncArg != NULL) {
- free(traceFuncArg);
- }
- // release the memory associated with the traceFuncArg in enableSqlProfiling function
- traceFuncArg = sqlite3_profile(handle, &sqlProfile, NULL);
- if (traceFuncArg != NULL) {
- free(traceFuncArg);
- }
- ALOGV("Closing database: handle=%p\n", handle);
- int result = sqlite3_close(handle);
- if (result == SQLITE_OK) {
- ALOGV("Closed %p\n", handle);
- env->SetIntField(object, offset_db_handle, 0);
- } else {
- // This can happen if sub-objects aren't closed first. Make sure the caller knows.
- throw_sqlite3_exception(env, handle);
- ALOGE("sqlite3_close(%p) failed: %d\n", handle, result);
- }
- }
-}
-
-/* native int native_getDbLookaside(); */
-static jint native_getDbLookaside(JNIEnv* env, jobject object)
-{
- sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
- int pCur = -1;
- int unused;
- sqlite3_db_status(handle, SQLITE_DBSTATUS_LOOKASIDE_USED, &pCur, &unused, 0);
- return pCur;
-}
-
-/* set locale in the android_metadata table, install localized collators, and rebuild indexes */
-static void native_setLocale(JNIEnv* env, jobject object, jstring localeString, jint flags)
-{
- if ((flags & NO_LOCALIZED_COLLATORS)) return;
-
- int err;
- char const* locale8 = env->GetStringUTFChars(localeString, NULL);
- sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
- sqlite3_stmt* stmt = NULL;
- char** meta = NULL;
- int rowCount, colCount;
- char* dbLocale = NULL;
-
- // create the table, if necessary and possible
- if (!(flags & OPEN_READONLY)) {
- static const char *createSql ="CREATE TABLE IF NOT EXISTS " ANDROID_TABLE " (locale TEXT)";
- err = sqlite3_exec(handle, createSql, NULL, NULL, NULL);
- if (err != SQLITE_OK) {
- ALOGE("CREATE TABLE " ANDROID_TABLE " failed\n");
- throw_sqlite3_exception(env, handle);
- goto done;
- }
- }
-
- // try to read from the table
- static const char *selectSql = "SELECT locale FROM " ANDROID_TABLE " LIMIT 1";
- err = sqlite3_get_table(handle, selectSql, &meta, &rowCount, &colCount, NULL);
- if (err != SQLITE_OK) {
- ALOGE("SELECT locale FROM " ANDROID_TABLE " failed\n");
- throw_sqlite3_exception(env, handle);
- goto done;
- }
-
- dbLocale = (rowCount >= 1) ? meta[colCount] : NULL;
-
- if (dbLocale != NULL && !strcmp(dbLocale, locale8)) {
- // database locale is the same as the desired locale; set up the collators and go
- err = register_localized_collators(handle, locale8, UTF16_STORAGE);
- if (err != SQLITE_OK) throw_sqlite3_exception(env, handle);
- goto done; // no database changes needed
- }
-
- if ((flags & OPEN_READONLY)) {
- // read-only database, so we're going to have to put up with whatever we got
- // For registering new index. Not for modifing the read-only database.
- err = register_localized_collators(handle, locale8, UTF16_STORAGE);
- if (err != SQLITE_OK) throw_sqlite3_exception(env, handle);
- goto done;
- }
-
- // need to update android_metadata and indexes atomically, so use a transaction...
- err = sqlite3_exec(handle, "BEGIN TRANSACTION", NULL, NULL, NULL);
- if (err != SQLITE_OK) {
- ALOGE("BEGIN TRANSACTION failed setting locale\n");
- throw_sqlite3_exception(env, handle);
- goto done;
- }
-
- err = register_localized_collators(handle, locale8, UTF16_STORAGE);
- if (err != SQLITE_OK) {
- ALOGE("register_localized_collators() failed setting locale\n");
- throw_sqlite3_exception(env, handle);
- goto rollback;
- }
-
- err = sqlite3_exec(handle, "DELETE FROM " ANDROID_TABLE, NULL, NULL, NULL);
- if (err != SQLITE_OK) {
- ALOGE("DELETE failed setting locale\n");
- throw_sqlite3_exception(env, handle);
- goto rollback;
- }
-
- static const char *sql = "INSERT INTO " ANDROID_TABLE " (locale) VALUES(?);";
- err = sqlite3_prepare_v2(handle, sql, -1, &stmt, NULL);
- if (err != SQLITE_OK) {
- ALOGE("sqlite3_prepare_v2(\"%s\") failed\n", sql);
- throw_sqlite3_exception(env, handle);
- goto rollback;
- }
-
- err = sqlite3_bind_text(stmt, 1, locale8, -1, SQLITE_TRANSIENT);
- if (err != SQLITE_OK) {
- ALOGE("sqlite3_bind_text() failed setting locale\n");
- throw_sqlite3_exception(env, handle);
- goto rollback;
- }
-
- err = sqlite3_step(stmt);
- if (err != SQLITE_OK && err != SQLITE_DONE) {
- ALOGE("sqlite3_step(\"%s\") failed setting locale\n", sql);
- throw_sqlite3_exception(env, handle);
- goto rollback;
- }
-
- err = sqlite3_exec(handle, "REINDEX LOCALIZED", NULL, NULL, NULL);
- if (err != SQLITE_OK) {
- ALOGE("REINDEX LOCALIZED failed\n");
- throw_sqlite3_exception(env, handle);
- goto rollback;
- }
-
- // all done, yay!
- err = sqlite3_exec(handle, "COMMIT TRANSACTION", NULL, NULL, NULL);
- if (err != SQLITE_OK) {
- ALOGE("COMMIT TRANSACTION failed setting locale\n");
- throw_sqlite3_exception(env, handle);
- goto done;
- }
-
-rollback:
- if (err != SQLITE_OK) {
- sqlite3_exec(handle, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
- }
-
-done:
- if (locale8 != NULL) env->ReleaseStringUTFChars(localeString, locale8);
- if (stmt != NULL) sqlite3_finalize(stmt);
- if (meta != NULL) sqlite3_free_table(meta);
-}
-
-static void native_setSqliteSoftHeapLimit(JNIEnv* env, jobject clazz, jint limit) {
- sSqliteSoftHeapLimit = limit;
-}
-
-static jint native_releaseMemory(JNIEnv *env, jobject clazz)
-{
- // Attempt to release as much memory from the
- return sqlite3_release_memory(sSqliteSoftHeapLimit);
-}
-
-static void native_finalize(JNIEnv* env, jobject object, jint statementId)
-{
- if (statementId > 0) {
- sqlite3_finalize((sqlite3_stmt *)statementId);
- }
-}
-
-static void custom_function_callback(sqlite3_context * context, int argc, sqlite3_value ** argv) {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- if (!env) {
- ALOGE("custom_function_callback cannot call into Java on this thread");
- return;
- }
- // get global ref to CustomFunction object from our user data
- jobject function = (jobject)sqlite3_user_data(context);
-
- // pack up the arguments into a string array
- jobjectArray strArray = env->NewObjectArray(argc, string_class, NULL);
- if (!strArray)
- goto done;
- for (int i = 0; i < argc; i++) {
- char* arg = (char *)sqlite3_value_text(argv[i]);
- if (!arg) {
- ALOGE("NULL argument in custom_function_callback. This should not happen.");
- return;
- }
- jobject obj = env->NewStringUTF(arg);
- if (!obj)
- goto done;
- env->SetObjectArrayElement(strArray, i, obj);
- env->DeleteLocalRef(obj);
- }
-
- env->CallVoidMethod(function, method_custom_function_callback, strArray);
- env->DeleteLocalRef(strArray);
-
-done:
- if (env->ExceptionCheck()) {
- ALOGE("An exception was thrown by custom sqlite3 function.");
- LOGE_EX(env);
- env->ExceptionClear();
- }
-}
-
-static jint native_addCustomFunction(JNIEnv* env, jobject object,
- jstring name, jint numArgs, jobject function)
-{
- sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
- char const *nameStr = env->GetStringUTFChars(name, NULL);
- jobject ref = env->NewGlobalRef(function);
- ALOGD_IF(DEBUG_JNI, "native_addCustomFunction %s ref: %p", nameStr, ref);
- int err = sqlite3_create_function(handle, nameStr, numArgs, SQLITE_UTF8,
- (void *)ref, custom_function_callback, NULL, NULL);
- env->ReleaseStringUTFChars(name, nameStr);
-
- if (err == SQLITE_OK)
- return (int)ref;
- else {
- ALOGE("sqlite3_create_function returned %d", err);
- env->DeleteGlobalRef(ref);
- throw_sqlite3_exception(env, handle);
- return 0;
- }
-}
-
-static void native_releaseCustomFunction(JNIEnv* env, jobject object, jint ref)
-{
- ALOGD_IF(DEBUG_JNI, "native_releaseCustomFunction %d", ref);
- env->DeleteGlobalRef((jobject)ref);
-}
-
-static JNINativeMethod sMethods[] =
-{
- /* name, signature, funcPtr */
- {"dbopen", "(Ljava/lang/String;I)V", (void *)dbopen},
- {"dbclose", "()V", (void *)dbclose},
- {"enableSqlTracing", "(Ljava/lang/String;S)V", (void *)enableSqlTracing},
- {"enableSqlProfiling", "(Ljava/lang/String;S)V", (void *)enableSqlProfiling},
- {"native_setLocale", "(Ljava/lang/String;I)V", (void *)native_setLocale},
- {"native_getDbLookaside", "()I", (void *)native_getDbLookaside},
- {"native_setSqliteSoftHeapLimit", "(I)V", (void *)native_setSqliteSoftHeapLimit},
- {"releaseMemory", "()I", (void *)native_releaseMemory},
- {"native_finalize", "(I)V", (void *)native_finalize},
- {"native_addCustomFunction",
- "(Ljava/lang/String;ILandroid/database/sqlite/SQLiteDatabase$CustomFunction;)I",
- (void *)native_addCustomFunction},
- {"native_releaseCustomFunction", "(I)V", (void *)native_releaseCustomFunction},
-};
-
-int register_android_database_SQLiteDatabase(JNIEnv *env)
-{
- jclass clazz;
-
- clazz = env->FindClass("android/database/sqlite/SQLiteDatabase");
- if (clazz == NULL) {
- ALOGE("Can't find android/database/sqlite/SQLiteDatabase\n");
- return -1;
- }
-
- string_class = (jclass)env->NewGlobalRef(env->FindClass("java/lang/String"));
- if (string_class == NULL) {
- ALOGE("Can't find java/lang/String\n");
- return -1;
- }
-
- offset_db_handle = env->GetFieldID(clazz, "mNativeHandle", "I");
- if (offset_db_handle == NULL) {
- ALOGE("Can't find SQLiteDatabase.mNativeHandle\n");
- return -1;
- }
-
- clazz = env->FindClass("android/database/sqlite/SQLiteDatabase$CustomFunction");
- if (clazz == NULL) {
- ALOGE("Can't find android/database/sqlite/SQLiteDatabase$CustomFunction\n");
- return -1;
- }
- method_custom_function_callback = env->GetMethodID(clazz, "callback", "([Ljava/lang/String;)V");
- if (method_custom_function_callback == NULL) {
- ALOGE("Can't find method SQLiteDatabase.CustomFunction.callback\n");
- return -1;
- }
-
- return AndroidRuntime::registerNativeMethods(env, "android/database/sqlite/SQLiteDatabase",
- sMethods, NELEM(sMethods));
-}
-
-/* throw a SQLiteException with a message appropriate for the error in handle */
-void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle) {
- throw_sqlite3_exception(env, handle, NULL);
-}
-
-/* throw a SQLiteException with the given message */
-void throw_sqlite3_exception(JNIEnv* env, const char* message) {
- throw_sqlite3_exception(env, NULL, message);
-}
-
-/* throw a SQLiteException with a message appropriate for the error in handle
- concatenated with the given message
- */
-void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle, const char* message) {
- if (handle) {
- throw_sqlite3_exception(env, sqlite3_errcode(handle),
- sqlite3_errmsg(handle), message);
- } else {
- // we use SQLITE_OK so that a generic SQLiteException is thrown;
- // any code not specified in the switch statement below would do.
- throw_sqlite3_exception(env, SQLITE_OK, "unknown error", message);
- }
-}
-
-/* throw a SQLiteException for a given error code */
-void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* message) {
- if (errcode == SQLITE_DONE) {
- throw_sqlite3_exception(env, errcode, NULL, message);
- } else {
- char temp[21];
- sprintf(temp, "error code %d", errcode);
- throw_sqlite3_exception(env, errcode, temp, message);
- }
-}
-
-/* throw a SQLiteException for a given error code, sqlite3message, and
- user message
- */
-void throw_sqlite3_exception(JNIEnv* env, int errcode,
- const char* sqlite3Message, const char* message) {
- const char* exceptionClass;
- switch (errcode) {
- case SQLITE_IOERR:
- exceptionClass = "android/database/sqlite/SQLiteDiskIOException";
- break;
- case SQLITE_CORRUPT:
- case SQLITE_NOTADB: // treat "unsupported file format" error as corruption also
- exceptionClass = "android/database/sqlite/SQLiteDatabaseCorruptException";
- break;
- case SQLITE_CONSTRAINT:
- exceptionClass = "android/database/sqlite/SQLiteConstraintException";
- break;
- case SQLITE_ABORT:
- exceptionClass = "android/database/sqlite/SQLiteAbortException";
- break;
- case SQLITE_DONE:
- exceptionClass = "android/database/sqlite/SQLiteDoneException";
- break;
- case SQLITE_FULL:
- exceptionClass = "android/database/sqlite/SQLiteFullException";
- break;
- case SQLITE_MISUSE:
- exceptionClass = "android/database/sqlite/SQLiteMisuseException";
- break;
- case SQLITE_PERM:
- exceptionClass = "android/database/sqlite/SQLiteAccessPermException";
- break;
- case SQLITE_BUSY:
- exceptionClass = "android/database/sqlite/SQLiteDatabaseLockedException";
- break;
- case SQLITE_LOCKED:
- exceptionClass = "android/database/sqlite/SQLiteTableLockedException";
- break;
- case SQLITE_READONLY:
- exceptionClass = "android/database/sqlite/SQLiteReadOnlyDatabaseException";
- break;
- case SQLITE_CANTOPEN:
- exceptionClass = "android/database/sqlite/SQLiteCantOpenDatabaseException";
- break;
- case SQLITE_TOOBIG:
- exceptionClass = "android/database/sqlite/SQLiteBlobTooBigException";
- break;
- case SQLITE_RANGE:
- exceptionClass = "android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException";
- break;
- case SQLITE_NOMEM:
- exceptionClass = "android/database/sqlite/SQLiteOutOfMemoryException";
- break;
- case SQLITE_MISMATCH:
- exceptionClass = "android/database/sqlite/SQLiteDatatypeMismatchException";
- break;
- case SQLITE_UNCLOSED:
- exceptionClass = "android/database/sqlite/SQLiteUnfinalizedObjectsException";
- break;
- default:
- exceptionClass = "android/database/sqlite/SQLiteException";
- break;
- }
-
- if (sqlite3Message != NULL && message != NULL) {
- char* fullMessage = (char *)malloc(strlen(sqlite3Message) + strlen(message) + 3);
- if (fullMessage != NULL) {
- strcpy(fullMessage, sqlite3Message);
- strcat(fullMessage, ": ");
- strcat(fullMessage, message);
- jniThrowException(env, exceptionClass, fullMessage);
- free(fullMessage);
- } else {
- jniThrowException(env, exceptionClass, sqlite3Message);
- }
- } else if (sqlite3Message != NULL) {
- jniThrowException(env, exceptionClass, sqlite3Message);
- } else {
- jniThrowException(env, exceptionClass, message);
- }
-}
-
-
-} // namespace android
diff --git a/core/jni/android_database_SQLiteGlobal.cpp b/core/jni/android_database_SQLiteGlobal.cpp
new file mode 100644
index 0000000000000..82cae5aa7502a
--- /dev/null
+++ b/core/jni/android_database_SQLiteGlobal.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "SQLiteGlobal"
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include "android_database_SQLiteCommon.h"
+
+namespace android {
+
+// Called each time a message is logged.
+static void sqliteLogCallback(void* data, int iErrCode, const char* zMsg) {
+ bool verboseLog = !!data;
+ if (iErrCode == 0 || iErrCode == SQLITE_CONSTRAINT) {
+ if (verboseLog) {
+ ALOGV(LOG_VERBOSE, SQLITE_LOG_TAG, "(%d) %s\n", iErrCode, zMsg);
+ }
+ } else {
+ ALOG(LOG_ERROR, SQLITE_LOG_TAG, "(%d) %s\n", iErrCode, zMsg);
+ }
+}
+
+// Sets the global SQLite configuration.
+// This must be called before any other SQLite functions are called. */
+static void nativeConfig(JNIEnv* env, jclass clazz, jboolean verboseLog, jint softHeapLimit) {
+ // Enable multi-threaded mode. In this mode, SQLite is safe to use by multiple
+ // threads as long as no two threads use the same database connection at the same
+ // time (which we guarantee in the SQLite database wrappers).
+ sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
+
+ // Redirect SQLite log messages to the Android log.
+ sqlite3_config(SQLITE_CONFIG_LOG, &sqliteLogCallback, verboseLog ? (void*)1 : NULL);
+
+ // The soft heap limit prevents the page cache allocations from growing
+ // beyond the given limit, no matter what the max page cache sizes are
+ // set to. The limit does not, as of 3.5.0, affect any other allocations.
+ sqlite3_soft_heap_limit(softHeapLimit);
+}
+
+static jint nativeReleaseMemory(JNIEnv* env, jclass clazz, jint bytesToFree) {
+ return sqlite3_release_memory(bytesToFree);
+}
+
+static JNINativeMethod sMethods[] =
+{
+ /* name, signature, funcPtr */
+ { "nativeConfig", "(ZI)V",
+ (void*)nativeConfig },
+ { "nativeReleaseMemory", "(I)I",
+ (void*)nativeReleaseMemory },
+};
+
+int register_android_database_SQLiteGlobal(JNIEnv *env)
+{
+ return AndroidRuntime::registerNativeMethods(env, "android/database/sqlite/SQLiteGlobal",
+ sMethods, NELEM(sMethods));
+}
+
+} // namespace android
diff --git a/core/jni/android_database_SQLiteProgram.cpp b/core/jni/android_database_SQLiteProgram.cpp
deleted file mode 100644
index 2e34c0030df91..0000000000000
--- a/core/jni/android_database_SQLiteProgram.cpp
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2006-2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "Cursor"
-
-#include
-#include
-#include
-
-#include
-
-#include
-
-#include
-#include
-#include
-
-#include "sqlite3_exception.h"
-
-
-namespace android {
-
-static jfieldID gHandleField;
-static jfieldID gStatementField;
-
-
-#define GET_STATEMENT(env, object) \
- (sqlite3_stmt *)env->GetIntField(object, gStatementField)
-#define GET_HANDLE(env, object) \
- (sqlite3 *)env->GetIntField(object, gHandleField)
-
-static void native_compile(JNIEnv* env, jobject object, jstring sqlString)
-{
- char buf[65];
- strcpy(buf, "android_database_SQLiteProgram->native_compile() not implemented");
- throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
- return;
-}
-
-static void native_bind_null(JNIEnv* env, jobject object,
- jint index)
-{
- int err;
- sqlite3_stmt * statement = GET_STATEMENT(env, object);
-
- err = sqlite3_bind_null(statement, index);
- if (err != SQLITE_OK) {
- char buf[32];
- sprintf(buf, "handle %p", statement);
- throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
- return;
- }
-}
-
-static void native_bind_long(JNIEnv* env, jobject object,
- jint index, jlong value)
-{
- int err;
- sqlite3_stmt * statement = GET_STATEMENT(env, object);
-
- err = sqlite3_bind_int64(statement, index, value);
- if (err != SQLITE_OK) {
- char buf[32];
- sprintf(buf, "handle %p", statement);
- throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
- return;
- }
-}
-
-static void native_bind_double(JNIEnv* env, jobject object,
- jint index, jdouble value)
-{
- int err;
- sqlite3_stmt * statement = GET_STATEMENT(env, object);
-
- err = sqlite3_bind_double(statement, index, value);
- if (err != SQLITE_OK) {
- char buf[32];
- sprintf(buf, "handle %p", statement);
- throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
- return;
- }
-}
-
-static void native_bind_string(JNIEnv* env, jobject object,
- jint index, jstring sqlString)
-{
- int err;
- jchar const * sql;
- jsize sqlLen;
- sqlite3_stmt * statement= GET_STATEMENT(env, object);
-
- sql = env->GetStringChars(sqlString, NULL);
- sqlLen = env->GetStringLength(sqlString);
- err = sqlite3_bind_text16(statement, index, sql, sqlLen * 2, SQLITE_TRANSIENT);
- env->ReleaseStringChars(sqlString, sql);
- if (err != SQLITE_OK) {
- char buf[32];
- sprintf(buf, "handle %p", statement);
- throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
- return;
- }
-}
-
-static void native_bind_blob(JNIEnv* env, jobject object,
- jint index, jbyteArray value)
-{
- int err;
- jchar const * sql;
- jsize sqlLen;
- sqlite3_stmt * statement= GET_STATEMENT(env, object);
-
- jint len = env->GetArrayLength(value);
- jbyte * bytes = env->GetByteArrayElements(value, NULL);
-
- err = sqlite3_bind_blob(statement, index, bytes, len, SQLITE_TRANSIENT);
- env->ReleaseByteArrayElements(value, bytes, JNI_ABORT);
-
- if (err != SQLITE_OK) {
- char buf[32];
- sprintf(buf, "statement %p", statement);
- throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
- return;
- }
-}
-
-static void native_clear_bindings(JNIEnv* env, jobject object)
-{
- int err;
- sqlite3_stmt * statement = GET_STATEMENT(env, object);
-
- err = sqlite3_clear_bindings(statement);
- if (err != SQLITE_OK) {
- throw_sqlite3_exception(env, GET_HANDLE(env, object));
- return;
- }
-}
-
-static void native_finalize(JNIEnv* env, jobject object)
-{
- char buf[66];
- strcpy(buf, "android_database_SQLiteProgram->native_finalize() not implemented");
- throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
- return;
-}
-
-
-static JNINativeMethod sMethods[] =
-{
- /* name, signature, funcPtr */
- {"native_bind_null", "(I)V", (void *)native_bind_null},
- {"native_bind_long", "(IJ)V", (void *)native_bind_long},
- {"native_bind_double", "(ID)V", (void *)native_bind_double},
- {"native_bind_string", "(ILjava/lang/String;)V", (void *)native_bind_string},
- {"native_bind_blob", "(I[B)V", (void *)native_bind_blob},
- {"native_clear_bindings", "()V", (void *)native_clear_bindings},
-};
-
-int register_android_database_SQLiteProgram(JNIEnv * env)
-{
- jclass clazz;
-
- clazz = env->FindClass("android/database/sqlite/SQLiteProgram");
- if (clazz == NULL) {
- ALOGE("Can't find android/database/sqlite/SQLiteProgram");
- return -1;
- }
-
- gHandleField = env->GetFieldID(clazz, "nHandle", "I");
- gStatementField = env->GetFieldID(clazz, "nStatement", "I");
-
- if (gHandleField == NULL || gStatementField == NULL) {
- ALOGE("Error locating fields");
- return -1;
- }
-
- return AndroidRuntime::registerNativeMethods(env,
- "android/database/sqlite/SQLiteProgram", sMethods, NELEM(sMethods));
-}
-
-} // namespace android
diff --git a/core/jni/android_database_SQLiteQuery.cpp b/core/jni/android_database_SQLiteQuery.cpp
deleted file mode 100644
index da7ccf02907e2..0000000000000
--- a/core/jni/android_database_SQLiteQuery.cpp
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "SqliteCursor.cpp"
-
-#include
-#include
-#include
-
-#include
-
-#include
-
-#include
-#include
-#include
-
-#include "binder/CursorWindow.h"
-#include "sqlite3_exception.h"
-
-
-namespace android {
-
-enum CopyRowResult {
- CPR_OK,
- CPR_FULL,
- CPR_ERROR,
-};
-
-static CopyRowResult copyRow(JNIEnv* env, CursorWindow* window,
- sqlite3_stmt* statement, int numColumns, int startPos, int addedRows) {
- // Allocate a new field directory for the row. This pointer is not reused
- // since it may be possible for it to be relocated on a call to alloc() when
- // the field data is being allocated.
- status_t status = window->allocRow();
- if (status) {
- LOG_WINDOW("Failed allocating fieldDir at startPos %d row %d, error=%d",
- startPos, addedRows, status);
- return CPR_FULL;
- }
-
- // Pack the row into the window.
- CopyRowResult result = CPR_OK;
- for (int i = 0; i < numColumns; i++) {
- int type = sqlite3_column_type(statement, i);
- if (type == SQLITE_TEXT) {
- // TEXT data
- const char* text = reinterpret_cast(
- sqlite3_column_text(statement, i));
- // SQLite does not include the NULL terminator in size, but does
- // ensure all strings are NULL terminated, so increase size by
- // one to make sure we store the terminator.
- size_t sizeIncludingNull = sqlite3_column_bytes(statement, i) + 1;
- status = window->putString(addedRows, i, text, sizeIncludingNull);
- if (status) {
- LOG_WINDOW("Failed allocating %u bytes for text at %d,%d, error=%d",
- sizeIncludingNull, startPos + addedRows, i, status);
- result = CPR_FULL;
- break;
- }
- LOG_WINDOW("%d,%d is TEXT with %u bytes",
- startPos + addedRows, i, sizeIncludingNull);
- } else if (type == SQLITE_INTEGER) {
- // INTEGER data
- int64_t value = sqlite3_column_int64(statement, i);
- status = window->putLong(addedRows, i, value);
- if (status) {
- LOG_WINDOW("Failed allocating space for a long in column %d, error=%d",
- i, status);
- result = CPR_FULL;
- break;
- }
- LOG_WINDOW("%d,%d is INTEGER 0x%016llx", startPos + addedRows, i, value);
- } else if (type == SQLITE_FLOAT) {
- // FLOAT data
- double value = sqlite3_column_double(statement, i);
- status = window->putDouble(addedRows, i, value);
- if (status) {
- LOG_WINDOW("Failed allocating space for a double in column %d, error=%d",
- i, status);
- result = CPR_FULL;
- break;
- }
- LOG_WINDOW("%d,%d is FLOAT %lf", startPos + addedRows, i, value);
- } else if (type == SQLITE_BLOB) {
- // BLOB data
- const void* blob = sqlite3_column_blob(statement, i);
- size_t size = sqlite3_column_bytes(statement, i);
- status = window->putBlob(addedRows, i, blob, size);
- if (status) {
- LOG_WINDOW("Failed allocating %u bytes for blob at %d,%d, error=%d",
- size, startPos + addedRows, i, status);
- result = CPR_FULL;
- break;
- }
- LOG_WINDOW("%d,%d is Blob with %u bytes",
- startPos + addedRows, i, size);
- } else if (type == SQLITE_NULL) {
- // NULL field
- status = window->putNull(addedRows, i);
- if (status) {
- LOG_WINDOW("Failed allocating space for a null in column %d, error=%d",
- i, status);
- result = CPR_FULL;
- break;
- }
-
- LOG_WINDOW("%d,%d is NULL", startPos + addedRows, i);
- } else {
- // Unknown data
- ALOGE("Unknown column type when filling database window");
- throw_sqlite3_exception(env, "Unknown column type when filling window");
- result = CPR_ERROR;
- break;
- }
- }
-
- // Free the last row if if was not successfully copied.
- if (result != CPR_OK) {
- window->freeLastRow();
- }
- return result;
-}
-
-static jlong nativeFillWindow(JNIEnv* env, jclass clazz, jint databasePtr,
- jint statementPtr, jint windowPtr, jint offsetParam,
- jint startPos, jint requiredPos, jboolean countAllRows) {
- sqlite3* database = reinterpret_cast(databasePtr);
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
- CursorWindow* window = reinterpret_cast(windowPtr);
-
- // Only do the binding if there is a valid offsetParam. If no binding needs to be done
- // offsetParam will be set to 0, an invalid value.
- if (offsetParam > 0) {
- // Bind the offset parameter, telling the program which row to start with
- // If an offset parameter is used, we cannot simply clear the window if it
- // turns out that the requiredPos won't fit because the result set may
- // depend on startPos, so we set startPos to requiredPos.
- startPos = requiredPos;
- int err = sqlite3_bind_int(statement, offsetParam, startPos);
- if (err != SQLITE_OK) {
- ALOGE("Unable to bind offset position, offsetParam = %d", offsetParam);
- throw_sqlite3_exception(env, database);
- return 0;
- }
- LOG_WINDOW("Bound offset position to startPos %d", startPos);
- }
-
- // We assume numRows is initially 0.
- LOG_WINDOW("Window: numRows = %d, size = %d, freeSpace = %d",
- window->getNumRows(), window->size(), window->freeSpace());
-
- int numColumns = sqlite3_column_count(statement);
- status_t status = window->setNumColumns(numColumns);
- if (status) {
- ALOGE("Failed to change column count from %d to %d", window->getNumColumns(), numColumns);
- jniThrowException(env, "java/lang/IllegalStateException", "numColumns mismatch");
- return 0;
- }
-
- int retryCount = 0;
- int totalRows = 0;
- int addedRows = 0;
- bool windowFull = false;
- bool gotException = false;
- while (!gotException && (!windowFull || countAllRows)) {
- int err = sqlite3_step(statement);
- if (err == SQLITE_ROW) {
- LOG_WINDOW("Stepped statement %p to row %d", statement, totalRows);
- retryCount = 0;
- totalRows += 1;
-
- // Skip the row if the window is full or we haven't reached the start position yet.
- if (startPos >= totalRows || windowFull) {
- continue;
- }
-
- CopyRowResult cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);
- if (cpr == CPR_FULL && addedRows && startPos + addedRows < requiredPos) {
- // We filled the window before we got to the one row that we really wanted.
- // Clear the window and start filling it again from here.
- // TODO: Would be nicer if we could progressively replace earlier rows.
- window->clear();
- window->setNumColumns(numColumns);
- startPos += addedRows;
- addedRows = 0;
- cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);
- }
-
- if (cpr == CPR_OK) {
- addedRows += 1;
- } else if (cpr == CPR_FULL) {
- windowFull = true;
- } else {
- gotException = true;
- }
- } else if (err == SQLITE_DONE) {
- // All rows processed, bail
- LOG_WINDOW("Processed all rows");
- break;
- } else if (err == SQLITE_LOCKED || err == SQLITE_BUSY) {
- // The table is locked, retry
- LOG_WINDOW("Database locked, retrying");
- if (retryCount > 50) {
- ALOGE("Bailing on database busy retry");
- throw_sqlite3_exception(env, database, "retrycount exceeded");
- gotException = true;
- } else {
- // Sleep to give the thread holding the lock a chance to finish
- usleep(1000);
- retryCount++;
- }
- } else {
- throw_sqlite3_exception(env, database);
- gotException = true;
- }
- }
-
- LOG_WINDOW("Resetting statement %p after fetching %d rows and adding %d rows"
- "to the window in %d bytes",
- statement, totalRows, addedRows, window->size() - window->freeSpace());
- sqlite3_reset(statement);
-
- // Report the total number of rows on request.
- if (startPos > totalRows) {
- ALOGE("startPos %d > actual rows %d", startPos, totalRows);
- }
- jlong result = jlong(startPos) << 32 | jlong(totalRows);
- return result;
-}
-
-static jint nativeColumnCount(JNIEnv* env, jclass clazz, jint statementPtr) {
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
- return sqlite3_column_count(statement);
-}
-
-static jstring nativeColumnName(JNIEnv* env, jclass clazz, jint statementPtr,
- jint columnIndex) {
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
- const char* name = sqlite3_column_name(statement, columnIndex);
- return env->NewStringUTF(name);
-}
-
-
-static JNINativeMethod sMethods[] =
-{
- /* name, signature, funcPtr */
- { "nativeFillWindow", "(IIIIIIZ)J",
- (void*)nativeFillWindow },
- { "nativeColumnCount", "(I)I",
- (void*)nativeColumnCount},
- { "nativeColumnName", "(II)Ljava/lang/String;",
- (void*)nativeColumnName},
-};
-
-int register_android_database_SQLiteQuery(JNIEnv * env)
-{
- return AndroidRuntime::registerNativeMethods(env,
- "android/database/sqlite/SQLiteQuery", sMethods, NELEM(sMethods));
-}
-
-} // namespace android
diff --git a/core/jni/android_database_SQLiteStatement.cpp b/core/jni/android_database_SQLiteStatement.cpp
deleted file mode 100644
index e376258f52d17..0000000000000
--- a/core/jni/android_database_SQLiteStatement.cpp
+++ /dev/null
@@ -1,286 +0,0 @@
-/* //device/libs/android_runtime/android_database_SQLiteCursor.cpp
-**
-** Copyright 2006, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-#undef LOG_TAG
-#define LOG_TAG "SQLiteStatementCpp"
-
-#include "android_util_Binder.h"
-
-#include
-#include
-#include
-
-#include
-
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "sqlite3_exception.h"
-
-namespace android {
-
-
-sqlite3_stmt * compile(JNIEnv* env, jobject object,
- sqlite3 * handle, jstring sqlString);
-
-static jfieldID gHandleField;
-static jfieldID gStatementField;
-
-
-#define GET_STATEMENT(env, object) \
- (sqlite3_stmt *)env->GetIntField(object, gStatementField)
-#define GET_HANDLE(env, object) \
- (sqlite3 *)env->GetIntField(object, gHandleField)
-
-
-static jint native_execute(JNIEnv* env, jobject object)
-{
- int err;
- sqlite3 * handle = GET_HANDLE(env, object);
- sqlite3_stmt * statement = GET_STATEMENT(env, object);
- int numChanges = -1;
-
- // Execute the statement
- err = sqlite3_step(statement);
-
- // Throw an exception if an error occurred
- if (err == SQLITE_ROW) {
- throw_sqlite3_exception(env,
- "Queries can be performed using SQLiteDatabase query or rawQuery methods only.");
- } else if (err != SQLITE_DONE) {
- throw_sqlite3_exception_errcode(env, err, sqlite3_errmsg(handle));
- } else {
- numChanges = sqlite3_changes(handle);
- }
-
- // Reset the statement so it's ready to use again
- sqlite3_reset(statement);
- return numChanges;
-}
-
-static jlong native_executeInsert(JNIEnv* env, jobject object)
-{
- sqlite3 * handle = GET_HANDLE(env, object);
- jint numChanges = native_execute(env, object);
- if (numChanges > 0) {
- return sqlite3_last_insert_rowid(handle);
- } else {
- return -1;
- }
-}
-
-static jlong native_1x1_long(JNIEnv* env, jobject object)
-{
- int err;
- sqlite3 * handle = GET_HANDLE(env, object);
- sqlite3_stmt * statement = GET_STATEMENT(env, object);
- jlong value = -1;
-
- // Execute the statement
- err = sqlite3_step(statement);
-
- // Handle the result
- if (err == SQLITE_ROW) {
- // No errors, read the data and return it
- value = sqlite3_column_int64(statement, 0);
- } else {
- throw_sqlite3_exception_errcode(env, err, sqlite3_errmsg(handle));
- }
-
- // Reset the statment so it's ready to use again
- sqlite3_reset(statement);
-
- return value;
-}
-
-static jstring native_1x1_string(JNIEnv* env, jobject object)
-{
- int err;
- sqlite3 * handle = GET_HANDLE(env, object);
- sqlite3_stmt * statement = GET_STATEMENT(env, object);
- jstring value = NULL;
-
- // Execute the statement
- err = sqlite3_step(statement);
-
- // Handle the result
- if (err == SQLITE_ROW) {
- // No errors, read the data and return it
- char const * text = (char const *)sqlite3_column_text(statement, 0);
- value = env->NewStringUTF(text);
- } else {
- throw_sqlite3_exception_errcode(env, err, sqlite3_errmsg(handle));
- }
-
- // Reset the statment so it's ready to use again
- sqlite3_reset(statement);
-
- return value;
-}
-
-static jobject createParcelFileDescriptor(JNIEnv * env, int fd)
-{
- // Create FileDescriptor object
- jobject fileDesc = jniCreateFileDescriptor(env, fd);
- if (fileDesc == NULL) {
- // FileDescriptor constructor has thrown an exception
- close(fd);
- return NULL;
- }
-
- // Wrap it in a ParcelFileDescriptor
- jobject parcelFileDesc = newParcelFileDescriptor(env, fileDesc);
- if (parcelFileDesc == NULL) {
- // ParcelFileDescriptor constructor has thrown an exception
- close(fd);
- return NULL;
- }
-
- return parcelFileDesc;
-}
-
-// Creates an ashmem area, copies some data into it, and returns
-// a ParcelFileDescriptor for the ashmem area.
-static jobject create_ashmem_region_with_data(JNIEnv * env,
- const void * data, int length)
-{
- // Create ashmem area
- int fd = ashmem_create_region(NULL, length);
- if (fd < 0) {
- ALOGE("ashmem_create_region failed: %s", strerror(errno));
- jniThrowIOException(env, errno);
- return NULL;
- }
-
- if (length > 0) {
- // mmap the ashmem area
- void * ashmem_ptr =
- mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
- if (ashmem_ptr == MAP_FAILED) {
- ALOGE("mmap failed: %s", strerror(errno));
- jniThrowIOException(env, errno);
- close(fd);
- return NULL;
- }
-
- // Copy data to ashmem area
- memcpy(ashmem_ptr, data, length);
-
- // munmap ashmem area
- if (munmap(ashmem_ptr, length) < 0) {
- ALOGE("munmap failed: %s", strerror(errno));
- jniThrowIOException(env, errno);
- close(fd);
- return NULL;
- }
- }
-
- // Make ashmem area read-only
- if (ashmem_set_prot_region(fd, PROT_READ) < 0) {
- ALOGE("ashmem_set_prot_region failed: %s", strerror(errno));
- jniThrowIOException(env, errno);
- close(fd);
- return NULL;
- }
-
- // Wrap it in a ParcelFileDescriptor
- return createParcelFileDescriptor(env, fd);
-}
-
-static jobject native_1x1_blob_ashmem(JNIEnv* env, jobject object)
-{
- int err;
- sqlite3 * handle = GET_HANDLE(env, object);
- sqlite3_stmt * statement = GET_STATEMENT(env, object);
- jobject value = NULL;
-
- // Execute the statement
- err = sqlite3_step(statement);
-
- // Handle the result
- if (err == SQLITE_ROW) {
- // No errors, read the data and return it
- const void * blob = sqlite3_column_blob(statement, 0);
- if (blob != NULL) {
- int len = sqlite3_column_bytes(statement, 0);
- if (len >= 0) {
- value = create_ashmem_region_with_data(env, blob, len);
- }
- }
- } else {
- throw_sqlite3_exception_errcode(env, err, sqlite3_errmsg(handle));
- }
-
- // Reset the statment so it's ready to use again
- sqlite3_reset(statement);
-
- return value;
-}
-
-static void native_executeSql(JNIEnv* env, jobject object, jstring sql)
-{
- char const* sqlString = env->GetStringUTFChars(sql, NULL);
- sqlite3 * handle = GET_HANDLE(env, object);
- int err = sqlite3_exec(handle, sqlString, NULL, NULL, NULL);
- if (err != SQLITE_OK) {
- throw_sqlite3_exception(env, handle);
- }
- env->ReleaseStringUTFChars(sql, sqlString);
-}
-
-static JNINativeMethod sMethods[] =
-{
- /* name, signature, funcPtr */
- {"native_execute", "()I", (void *)native_execute},
- {"native_executeInsert", "()J", (void *)native_executeInsert},
- {"native_1x1_long", "()J", (void *)native_1x1_long},
- {"native_1x1_string", "()Ljava/lang/String;", (void *)native_1x1_string},
- {"native_1x1_blob_ashmem", "()Landroid/os/ParcelFileDescriptor;", (void *)native_1x1_blob_ashmem},
- {"native_executeSql", "(Ljava/lang/String;)V", (void *)native_executeSql},
-};
-
-int register_android_database_SQLiteStatement(JNIEnv * env)
-{
- jclass clazz;
-
- clazz = env->FindClass("android/database/sqlite/SQLiteStatement");
- if (clazz == NULL) {
- ALOGE("Can't find android/database/sqlite/SQLiteStatement");
- return -1;
- }
-
- gHandleField = env->GetFieldID(clazz, "nHandle", "I");
- gStatementField = env->GetFieldID(clazz, "nStatement", "I");
-
- if (gHandleField == NULL || gStatementField == NULL) {
- ALOGE("Error locating fields");
- return -1;
- }
-
- return AndroidRuntime::registerNativeMethods(env,
- "android/database/sqlite/SQLiteStatement", sMethods, NELEM(sMethods));
-}
-
-} // namespace android
diff --git a/core/jni/sqlite3_exception.h b/core/jni/sqlite3_exception.h
deleted file mode 100644
index 13735a15e0e3e..0000000000000
--- a/core/jni/sqlite3_exception.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/* //device/libs/include/android_runtime/sqlite3_exception.h
-**
-** Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-#ifndef _SQLITE3_EXCEPTION_H
-#define _SQLITE3_EXCEPTION_H 1
-
-#include
-#include
-//#include
-
-#include
-
-namespace android {
-
-/* throw a SQLiteException with a message appropriate for the error in handle */
-void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle);
-
-/* throw a SQLiteException with the given message */
-void throw_sqlite3_exception(JNIEnv* env, const char* message);
-
-/* throw a SQLiteException with a message appropriate for the error in handle
- concatenated with the given message
- */
-void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle, const char* message);
-
-/* throw a SQLiteException for a given error code */
-void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* message);
-
-void throw_sqlite3_exception(JNIEnv* env, int errcode,
- const char* sqlite3Message, const char* message);
-}
-
-#endif // _SQLITE3_EXCEPTION_H
diff --git a/core/tests/coretests/src/android/database/sqlite/DatabaseConnectionPoolTest.java b/core/tests/coretests/src/android/database/sqlite/DatabaseConnectionPoolTest.java
deleted file mode 100644
index 525dd2db0adca..0000000000000
--- a/core/tests/coretests/src/android/database/sqlite/DatabaseConnectionPoolTest.java
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.database.sqlite;
-
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabaseTest.ClassToTestSqlCompilationAndCaching;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.HashMap;
-
-public class DatabaseConnectionPoolTest extends AndroidTestCase {
- private static final String TAG = "DatabaseConnectionPoolTest";
-
- private static final int MAX_CONN = 5;
- private static final String TEST_SQL = "select * from test where i = ? AND j = 1";
- private static final String[] TEST_SQLS = new String[] {
- TEST_SQL, TEST_SQL + 1, TEST_SQL + 2, TEST_SQL + 3, TEST_SQL + 4
- };
-
- private SQLiteDatabase mDatabase;
- private File mDatabaseFile;
- private DatabaseConnectionPool mTestPool;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
- mDatabaseFile = new File(dbDir, "database_test.db");
- if (mDatabaseFile.exists()) {
- mDatabaseFile.delete();
- }
- mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
- assertNotNull(mDatabase);
- mDatabase.execSQL("create table test (i int, j int);");
- mTestPool = new DatabaseConnectionPool(mDatabase);
- assertNotNull(mTestPool);
- }
-
- @Override
- protected void tearDown() throws Exception {
- mTestPool.close();
- mDatabase.close();
- mDatabaseFile.delete();
- super.tearDown();
- }
-
- @SmallTest
- public void testGetAndRelease() {
- mTestPool.setMaxPoolSize(MAX_CONN);
- // connections should be lazily created.
- assertEquals(0, mTestPool.getSize());
- // MAX pool size should be set to MAX_CONN
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
- // get a connection
- SQLiteDatabase db = mTestPool.get(TEST_SQL);
- // pool size should be one - since only one should be allocated for the above get()
- assertEquals(1, mTestPool.getSize());
- assertEquals(mDatabase, db.mParentConnObj);
- // no free connections should be available
- assertEquals(0, mTestPool.getFreePoolSize());
- assertFalse(mTestPool.isDatabaseObjFree(db));
- // release the connection
- mTestPool.release(db);
- assertEquals(1, mTestPool.getFreePoolSize());
- assertEquals(1, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
- assertTrue(mTestPool.isDatabaseObjFree(db));
- // release the same object again and expect IllegalStateException
- try {
- mTestPool.release(db);
- fail("illegalStateException expected");
- } catch (IllegalStateException e ) {
- // expected.
- }
- }
-
- /**
- * get all connections from the pool and ask for one more.
- * should get one of the connections already got so far.
- */
- @SmallTest
- public void testGetAllConnAndOneMore() {
- mTestPool.setMaxPoolSize(MAX_CONN);
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
- ArrayList dbObjs = new ArrayList();
- for (int i = 0; i < MAX_CONN; i++) {
- SQLiteDatabase db = mTestPool.get(TEST_SQL);
- assertFalse(dbObjs.contains(db));
- dbObjs.add(db);
- assertEquals(mDatabase, db.mParentConnObj);
- }
- assertEquals(0, mTestPool.getFreePoolSize());
- assertEquals(MAX_CONN, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
- // pool is maxed out and no free connections. ask for one more connection
- SQLiteDatabase db1 = mTestPool.get(TEST_SQL);
- // make sure db1 is one of the existing ones
- assertTrue(dbObjs.contains(db1));
- // pool size should remain at MAX_CONN
- assertEquals(0, mTestPool.getFreePoolSize());
- assertEquals(MAX_CONN, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
- // release db1 but since it is allocated 2 times, it should still remain 'busy'
- mTestPool.release(db1);
- assertFalse(mTestPool.isDatabaseObjFree(db1));
- assertEquals(0, mTestPool.getFreePoolSize());
- assertEquals(MAX_CONN, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
- // release all connections
- for (int i = 0; i < MAX_CONN; i++) {
- mTestPool.release(dbObjs.get(i));
- }
- // all objects in the pool should be freed now
- assertEquals(MAX_CONN, mTestPool.getFreePoolSize());
- assertEquals(MAX_CONN, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
- }
-
- /**
- * same as above except that each connection has different SQL statement associated with it.
- */
- @SmallTest
- public void testConnRetrievalForPreviouslySeenSql() {
- mTestPool.setMaxPoolSize(MAX_CONN);
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-
- HashMap dbObjs = new HashMap();
- for (int i = 0; i < MAX_CONN; i++) {
- SQLiteDatabase db = mTestPool.get(TEST_SQLS[i]);
- executeSqlOnDatabaseConn(db, TEST_SQLS[i]);
- assertFalse(dbObjs.values().contains(db));
- dbObjs.put(TEST_SQLS[i], db);
- }
- assertEquals(0, mTestPool.getFreePoolSize());
- assertEquals(MAX_CONN, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
- // pool is maxed out and no free connections. ask for one more connection
- // use a previously seen SQL statement
- String testSql = TEST_SQLS[MAX_CONN - 1];
- SQLiteDatabase db1 = mTestPool.get(testSql);
- assertEquals(0, mTestPool.getFreePoolSize());
- assertEquals(MAX_CONN, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
- // make sure db1 is one of the existing ones
- assertTrue(dbObjs.values().contains(db1));
- assertEquals(db1, dbObjs.get(testSql));
- // do the same again
- SQLiteDatabase db2 = mTestPool.get(testSql);
- // make sure db1 is one of the existing ones
- assertEquals(db2, dbObjs.get(testSql));
-
- // pool size should remain at MAX_CONN
- assertEquals(0, mTestPool.getFreePoolSize());
- assertEquals(MAX_CONN, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-
- // release db1 but since the same connection is allocated 3 times,
- // it should still remain 'busy'
- mTestPool.release(db1);
- assertFalse(mTestPool.isDatabaseObjFree(dbObjs.get(testSql)));
- assertEquals(0, mTestPool.getFreePoolSize());
- assertEquals(MAX_CONN, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-
- // release db2 but since the same connection is allocated 2 times,
- // it should still remain 'busy'
- mTestPool.release(db2);
- assertFalse(mTestPool.isDatabaseObjFree(dbObjs.get(testSql)));
- assertEquals(0, mTestPool.getFreePoolSize());
- assertEquals(MAX_CONN, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-
- // release all connections
- for (int i = 0; i < MAX_CONN; i++) {
- mTestPool.release(dbObjs.get(TEST_SQLS[i]));
- }
- // all objects in the pool should be freed now
- assertEquals(MAX_CONN, mTestPool.getFreePoolSize());
- assertEquals(MAX_CONN, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
- }
-
- private void executeSqlOnDatabaseConn(SQLiteDatabase db, String sql) {
- // get the given sql be compiled on the given database connection.
- // this will help DatabaseConenctionPool figure out if a given SQL statement
- // is already cached by a database connection.
- ClassToTestSqlCompilationAndCaching c =
- ClassToTestSqlCompilationAndCaching.create(db, sql);
- c.close();
- }
-
- /**
- * get a connection for a SQL statement 'blah'. (connection_s)
- * make sure the pool has at least one free connection even after this get().
- * and get a connection for the same SQL again.
- * this connection should be different from connection_s.
- * even though there is a connection with the given SQL pre-compiled, since is it not free
- * AND since the pool has free connections available, should get a new connection.
- */
- @SmallTest
- public void testGetConnForTheSameSql() {
- mTestPool.setMaxPoolSize(MAX_CONN);
-
- SQLiteDatabase db = mTestPool.get(TEST_SQL);
- executeSqlOnDatabaseConn(db, TEST_SQL);
- assertEquals(0, mTestPool.getFreePoolSize());
- assertEquals(1, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-
- assertFalse(mTestPool.isDatabaseObjFree(db));
-
- SQLiteDatabase db1 = mTestPool.get(TEST_SQL);
- assertEquals(0, mTestPool.getFreePoolSize());
- assertEquals(2, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-
- assertFalse(mTestPool.isDatabaseObjFree(db1));
- assertFalse(db1.equals(db));
-
- mTestPool.release(db);
- assertEquals(1, mTestPool.getFreePoolSize());
- assertEquals(2, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-
- mTestPool.release(db1);
- assertEquals(2, mTestPool.getFreePoolSize());
- assertEquals(2, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
- }
-
- /**
- * get the same connection N times and release it N times.
- * this tests DatabaseConnectionPool.PoolObj.mNumHolders
- */
- @SmallTest
- public void testGetSameConnNtimesAndReleaseItNtimes() {
- mTestPool.setMaxPoolSize(MAX_CONN);
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-
- HashMap dbObjs = new HashMap();
- for (int i = 0; i < MAX_CONN; i++) {
- SQLiteDatabase db = mTestPool.get(TEST_SQLS[i]);
- executeSqlOnDatabaseConn(db, TEST_SQLS[i]);
- assertFalse(dbObjs.values().contains(db));
- dbObjs.put(TEST_SQLS[i], db);
- }
- assertEquals(0, mTestPool.getFreePoolSize());
- assertEquals(MAX_CONN, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
- // every connection in the pool should have numHolders = 1
- for (int i = 0; i < MAX_CONN; i ++) {
- assertEquals(1, mTestPool.getPool().get(i).getNumHolders());
- }
- // pool is maxed out and no free connections. ask for one more connection
- // use a previously seen SQL statement
- String testSql = TEST_SQLS[MAX_CONN - 1];
- SQLiteDatabase db1 = mTestPool.get(testSql);
- assertEquals(0, mTestPool.getFreePoolSize());
- assertEquals(MAX_CONN, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
- // make sure db1 is one of the existing ones
- assertTrue(dbObjs.values().contains(db1));
- assertEquals(db1, dbObjs.get(testSql));
- assertFalse(mTestPool.isDatabaseObjFree(db1));
- DatabaseConnectionPool.PoolObj poolObj = mTestPool.getPool().get(db1.mConnectionNum - 1);
- int numHolders = poolObj.getNumHolders();
- assertEquals(2, numHolders);
- assertEquals(0, mTestPool.getFreePoolSize());
- assertEquals(MAX_CONN, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
- // get the same connection N times more
- int N = 100;
- for (int i = 0; i < N; i++) {
- SQLiteDatabase db2 = mTestPool.get(testSql);
- assertEquals(db1, db2);
- assertFalse(mTestPool.isDatabaseObjFree(db2));
- // numHolders for this object should be now up by 1
- int prev = numHolders;
- numHolders = poolObj.getNumHolders();
- assertEquals(prev + 1, numHolders);
- }
- // release it N times
- for (int i = 0; i < N; i++) {
- mTestPool.release(db1);
- int prev = numHolders;
- numHolders = poolObj.getNumHolders();
- assertEquals(prev - 1, numHolders);
- assertFalse(mTestPool.isDatabaseObjFree(db1));
- }
- // the connection should still have 2 more holders
- assertFalse(mTestPool.isDatabaseObjFree(db1));
- assertEquals(2, poolObj.getNumHolders());
- assertEquals(0, mTestPool.getFreePoolSize());
- assertEquals(MAX_CONN, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
- // release 2 more times
- mTestPool.release(db1);
- mTestPool.release(db1);
- assertEquals(0, poolObj.getNumHolders());
- assertEquals(1, mTestPool.getFreePoolSize());
- assertEquals(MAX_CONN, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
- assertTrue(mTestPool.isDatabaseObjFree(db1));
- }
-
- @SmallTest
- public void testStressTest() {
- mTestPool.setMaxPoolSize(MAX_CONN);
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-
- HashMap dbMap = new HashMap();
- for (int i = 0; i < MAX_CONN; i++) {
- SQLiteDatabase db = mTestPool.get(TEST_SQLS[i]);
- assertFalse(dbMap.containsKey(db));
- dbMap.put(db, 1);
- executeSqlOnDatabaseConn(db, TEST_SQLS[i]);
- }
- assertEquals(0, mTestPool.getFreePoolSize());
- assertEquals(MAX_CONN, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
- // ask for lot more connections but since the pool is maxed out, we should start receiving
- // connections that we already got so far
- for (int i = MAX_CONN; i < 1000; i++) {
- SQLiteDatabase db = mTestPool.get(TEST_SQL + i);
- assertTrue(dbMap.containsKey(db));
- int k = dbMap.get(db);
- dbMap.put(db, ++k);
- }
- assertEquals(0, mTestPool.getFreePoolSize());
- assertEquals(MAX_CONN, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
- // print the distribution of the database connection handles received, should be uniform.
- for (SQLiteDatabase d : dbMap.keySet()) {
- Log.i(TAG, "connection # " + d.mConnectionNum + ", numHolders: " + dbMap.get(d));
- }
- // print the pool info
- Log.i(TAG, mTestPool.toString());
- // release all
- for (SQLiteDatabase d : dbMap.keySet()) {
- int num = dbMap.get(d);
- for (int i = 0; i < num; i++) {
- mTestPool.release(d);
- }
- }
- assertEquals(MAX_CONN, mTestPool.getFreePoolSize());
- assertEquals(MAX_CONN, mTestPool.getSize());
- assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
- }
-}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
index f6b1d04c8487d..9ccc6e8185df6 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
@@ -21,8 +21,6 @@ import android.content.Context;
import android.database.Cursor;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
import java.io.File;
import java.util.HashSet;
@@ -54,52 +52,8 @@ public class SQLiteCursorTest extends AndroidTestCase {
super.tearDown();
}
- @SmallTest
- public void testQueryObjReassignment() {
- mDatabase.enableWriteAheadLogging();
- // have a few connections in the database connection pool
- DatabaseConnectionPool pool = mDatabase.mConnectionPool;
- pool.setMaxPoolSize(5);
- SQLiteCursor cursor =
- (SQLiteCursor) mDatabase.rawQuery("select * from " + TABLE_NAME, null);
- assertNotNull(cursor);
- // it should use a pooled database connection
- SQLiteDatabase db = cursor.getDatabase();
- assertTrue(db.mConnectionNum > 0);
- assertFalse(mDatabase.equals(db));
- assertEquals(mDatabase, db.mParentConnObj);
- assertTrue(pool.getConnectionList().contains(db));
- assertTrue(db.isOpen());
- // do a requery. cursor should continue to use the above pooled connection
- cursor.requery();
- SQLiteDatabase dbAgain = cursor.getDatabase();
- assertEquals(db, dbAgain);
- // disable WAL so that the pooled connection held by the above cursor is closed
- mDatabase.disableWriteAheadLogging();
- assertFalse(db.isOpen());
- assertNull(mDatabase.mConnectionPool);
- // requery - which should make the cursor use mDatabase connection since the pooled
- // connection is no longer available
- cursor.requery();
- SQLiteDatabase db1 = cursor.getDatabase();
- assertTrue(db1.mConnectionNum == 0);
- assertEquals(mDatabase, db1);
- assertNull(mDatabase.mConnectionPool);
- assertTrue(db1.isOpen());
- assertFalse(mDatabase.equals(db));
- // enable WAL and requery - this time a pooled connection should be used
- mDatabase.enableWriteAheadLogging();
- cursor.requery();
- db = cursor.getDatabase();
- assertTrue(db.mConnectionNum > 0);
- assertFalse(mDatabase.equals(db));
- assertEquals(mDatabase, db.mParentConnObj);
- assertTrue(mDatabase.mConnectionPool.getConnectionList().contains(db));
- assertTrue(db.isOpen());
- }
-
/**
- * this test could take a while to execute. so, designate it as LargetTest
+ * this test could take a while to execute. so, designate it as LargeTest
*/
@LargeTest
public void testFillWindow() {
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
deleted file mode 100644
index 5ef8d11c73746..0000000000000
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ /dev/null
@@ -1,971 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.database.sqlite;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.DatabaseErrorHandler;
-import android.database.DatabaseUtils;
-import android.database.DefaultDatabaseErrorHandler;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteDatabase.CursorFactory;
-import android.database.sqlite.SQLiteStatement;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.test.suitebuilder.annotation.Suppress;
-import android.util.Log;
-import android.util.Pair;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-public class SQLiteDatabaseTest extends AndroidTestCase {
- private static final String TAG = "DatabaseGeneralTest";
- private static final String TEST_TABLE = "test";
- private static final int CURRENT_DATABASE_VERSION = 42;
- private SQLiteDatabase mDatabase;
- private File mDatabaseFile;
- private static final int INSERT = 1;
- private static final int UPDATE = 2;
- private static final int DELETE = 3;
- private static final String DB_NAME = "database_test.db";
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- dbSetUp();
- }
-
- @Override
- protected void tearDown() throws Exception {
- dbTeardown();
- super.tearDown();
- }
-
- private void dbTeardown() throws Exception {
- mDatabase.close();
- mDatabaseFile.delete();
- }
-
- private void dbSetUp() throws Exception {
- File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
- mDatabaseFile = new File(dbDir, DB_NAME);
- if (mDatabaseFile.exists()) {
- mDatabaseFile.delete();
- }
- mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null, null);
- assertNotNull(mDatabase);
- mDatabase.setVersion(CURRENT_DATABASE_VERSION);
- }
-
- @SmallTest
- public void testEnableWriteAheadLogging() {
- mDatabase.disableWriteAheadLogging();
- assertNull(mDatabase.mConnectionPool);
- mDatabase.enableWriteAheadLogging();
- DatabaseConnectionPool pool = mDatabase.mConnectionPool;
- assertNotNull(pool);
- // make the same call again and make sure the pool already setup is not re-created
- mDatabase.enableWriteAheadLogging();
- assertEquals(pool, mDatabase.mConnectionPool);
- }
-
- @SmallTest
- public void testDisableWriteAheadLogging() {
- mDatabase.execSQL("create table test (i int);");
- mDatabase.enableWriteAheadLogging();
- assertNotNull(mDatabase.mConnectionPool);
- // get a pooled database connection
- SQLiteDatabase db = mDatabase.getDbConnection("select * from test");
- assertNotNull(db);
- assertFalse(mDatabase.equals(db));
- assertTrue(db.isOpen());
- // disable WAL - which should close connection pool and all pooled connections
- mDatabase.disableWriteAheadLogging();
- assertNull(mDatabase.mConnectionPool);
- assertFalse(db.isOpen());
- }
-
- @SmallTest
- public void testCursorsWithClosedDbConnAfterDisableWriteAheadLogging() {
- mDatabase.disableWriteAheadLogging();
- mDatabase.beginTransactionNonExclusive();
- mDatabase.execSQL("create table test (i int);");
- mDatabase.execSQL("insert into test values(1);");
- mDatabase.setTransactionSuccessful();
- mDatabase.endTransaction();
- mDatabase.enableWriteAheadLogging();
- assertNotNull(mDatabase.mConnectionPool);
- assertEquals(0, mDatabase.mConnectionPool.getSize());
- assertEquals(0, mDatabase.mConnectionPool.getFreePoolSize());
- // get a cursor which should use pooled database connection
- Cursor c = mDatabase.rawQuery("select * from test", null);
- assertEquals(1, c.getCount());
- assertEquals(1, mDatabase.mConnectionPool.getSize());
- assertEquals(1, mDatabase.mConnectionPool.getFreePoolSize());
- SQLiteDatabase db = mDatabase.mConnectionPool.getConnectionList().get(0);
- assertTrue(mDatabase.mConnectionPool.isDatabaseObjFree(db));
- // disable WAL - which should close connection pool and all pooled connections
- mDatabase.disableWriteAheadLogging();
- assertNull(mDatabase.mConnectionPool);
- assertFalse(db.isOpen());
- // cursor data should still be accessible because it is fetching data from CursorWindow
- c.moveToNext();
- assertEquals(1, c.getInt(0));
- c.requery();
- assertEquals(1, c.getCount());
- c.moveToNext();
- assertEquals(1, c.getInt(0));
- c.close();
- }
-
- /**
- * a transaction should be started before a standalone-update/insert/delete statement
- */
- @SmallTest
- public void testStartXactBeforeUpdateSql() throws InterruptedException {
- runTestForStartXactBeforeUpdateSql(INSERT);
- runTestForStartXactBeforeUpdateSql(UPDATE);
- runTestForStartXactBeforeUpdateSql(DELETE);
- }
- private void runTestForStartXactBeforeUpdateSql(int stmtType) throws InterruptedException {
- createTableAndClearCache();
-
- ContentValues values = new ContentValues();
- // make some changes to data in TEST_TABLE
- for (int i = 0; i < 5; i++) {
- values.put("i", i);
- values.put("j", "i" + System.currentTimeMillis());
- mDatabase.insert(TEST_TABLE, null, values);
- switch (stmtType) {
- case UPDATE:
- values.put("j", "u" + System.currentTimeMillis());
- mDatabase.update(TEST_TABLE, values, "i = " + i, null);
- break;
- case DELETE:
- mDatabase.delete(TEST_TABLE, "i = 1", null);
- break;
- }
- }
- // do a query. even though query uses a different database connection,
- // it should still see the above changes to data because the above standalone
- // insert/update/deletes are done in transactions automatically.
- String sql = "select count(*) from " + TEST_TABLE;
- SQLiteStatement stmt = mDatabase.compileStatement(sql);
- final int expectedValue = (stmtType == DELETE) ? 4 : 5;
- assertEquals(expectedValue, stmt.simpleQueryForLong());
- stmt.close();
- Cursor c = mDatabase.rawQuery(sql, null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- assertEquals(expectedValue, c.getLong(0));
- c.close();
-
- // do 5 more changes in a transaction but do a query before and after the commit
- mDatabase.beginTransaction();
- for (int i = 10; i < 15; i++) {
- values.put("i", i);
- values.put("j", "i" + System.currentTimeMillis());
- mDatabase.insert(TEST_TABLE, null, values);
- switch (stmtType) {
- case UPDATE:
- values.put("j", "u" + System.currentTimeMillis());
- mDatabase.update(TEST_TABLE, values, "i = " + i, null);
- break;
- case DELETE:
- mDatabase.delete(TEST_TABLE, "i = 1", null);
- break;
- }
- }
- mDatabase.setTransactionSuccessful();
- // do a query before commit - should still have 5 rows
- // this query should run in a different thread to force it to use a different database
- // connection
- Thread t = new Thread() {
- @Override public void run() {
- String sql = "select count(*) from " + TEST_TABLE;
- SQLiteStatement stmt = getDb().compileStatement(sql);
- assertEquals(expectedValue, stmt.simpleQueryForLong());
- stmt.close();
- Cursor c = getDb().rawQuery(sql, null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- assertEquals(expectedValue, c.getLong(0));
- c.close();
- }
- };
- t.start();
- // wait until the above thread is done
- t.join();
- // commit and then query. should see changes from the transaction
- mDatabase.endTransaction();
- stmt = mDatabase.compileStatement(sql);
- final int expectedValue2 = (stmtType == DELETE) ? 9 : 10;
- assertEquals(expectedValue2, stmt.simpleQueryForLong());
- stmt.close();
- c = mDatabase.rawQuery(sql, null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- assertEquals(expectedValue2, c.getLong(0));
- c.close();
- }
- private synchronized SQLiteDatabase getDb() {
- return mDatabase;
- }
-
- /**
- * Test to ensure that readers are able to read the database data (old versions)
- * EVEN WHEN the writer is in a transaction on the same database.
- *
- * This test starts 1 Writer and 2 Readers and sets up connection pool for readers
- * by calling the method {@link SQLiteDatabase#enableWriteAheadLogging()}.
- *
- * Writer does the following in a tight loop
- *
- * begin transaction
- * insert into table_1
- * insert into table_2
- * commit
- *
- *
- * As long a the writer is alive, Readers do the following in a tight loop at the same time
- *
- * Reader_K does "select count(*) from table_K" where K = 1 or 2
- *
- *
- * The test is run for TIME_TO_RUN_WAL_TEST_FOR sec.
- *
- * The test is repeated for different connection-pool-sizes (1..3)
- *
- * And at the end of of each test, the following statistics are printed
- *
- * - connection-pool-size
- * - number-of-transactions by writer
- * - number of reads by reader_K while the writer is IN or NOT-IN xaction
- *
- */
- @LargeTest
- @Suppress // run this test only if you need to collect the numbers from this test
- public void testConcurrencyEffectsOfConnPool() throws Exception {
- // run the test with sqlite WAL enable
- runConnectionPoolTest(true);
-
- // run the same test WITHOUT sqlite WAL enabled
- runConnectionPoolTest(false);
- }
-
- private void runConnectionPoolTest(boolean useWal) throws Exception {
- int M = 3;
- StringBuilder[] buff = new StringBuilder[M];
- for (int i = 0; i < M; i++) {
- if (useWal) {
- // set up connection pool
- mDatabase.enableWriteAheadLogging();
- mDatabase.mConnectionPool.setMaxPoolSize(i + 1);
- } else {
- mDatabase.disableWriteAheadLogging();
- }
- mDatabase.execSQL("CREATE TABLE t1 (i int, j int);");
- mDatabase.execSQL("CREATE TABLE t2 (i int, j int);");
- mDatabase.beginTransaction();
- for (int k = 0; k < 5; k++) {
- mDatabase.execSQL("insert into t1 values(?,?);", new String[] {k+"", k+""});
- mDatabase.execSQL("insert into t2 values(?,?);", new String[] {k+"", k+""});
- }
- mDatabase.setTransactionSuccessful();
- mDatabase.endTransaction();
-
- // start a writer
- Writer w = new Writer(mDatabase);
-
- // initialize an array of counters to be passed to the readers
- Reader r1 = new Reader(mDatabase, "t1", w, 0);
- Reader r2 = new Reader(mDatabase, "t2", w, 1);
- w.start();
- r1.start();
- r2.start();
-
- // wait for all threads to die
- w.join();
- r1.join();
- r2.join();
-
- // print the stats
- int[][] counts = getCounts();
- buff[i] = new StringBuilder();
- buff[i].append("connpool-size = ");
- buff[i].append(i + 1);
- buff[i].append(", num xacts by writer = ");
- buff[i].append(getNumXacts());
- buff[i].append(", num-reads-in-xact/NOT-in-xact by reader1 = ");
- buff[i].append(counts[0][1] + "/" + counts[0][0]);
- buff[i].append(", by reader2 = ");
- buff[i].append(counts[1][1] + "/" + counts[1][0]);
-
- Log.i(TAG, "done testing for conn-pool-size of " + (i+1));
-
- dbTeardown();
- dbSetUp();
- }
- Log.i(TAG, "duration of test " + TIME_TO_RUN_WAL_TEST_FOR + " sec");
- for (int i = 0; i < M; i++) {
- Log.i(TAG, buff[i].toString());
- }
- }
-
- private boolean inXact = false;
- private int numXacts;
- private static final int TIME_TO_RUN_WAL_TEST_FOR = 15; // num sec this test should run
- private int[][] counts = new int[2][2];
-
- private synchronized boolean inXact() {
- return inXact;
- }
-
- private synchronized void setInXactFlag(boolean flag) {
- inXact = flag;
- }
-
- private synchronized void setCounts(int readerNum, int[] numReads) {
- counts[readerNum][0] = numReads[0];
- counts[readerNum][1] = numReads[1];
- }
-
- private synchronized int[][] getCounts() {
- return counts;
- }
-
- private synchronized void setNumXacts(int num) {
- numXacts = num;
- }
-
- private synchronized int getNumXacts() {
- return numXacts;
- }
-
- private class Writer extends Thread {
- private SQLiteDatabase db = null;
- public Writer(SQLiteDatabase db) {
- this.db = db;
- }
- @Override public void run() {
- // in a loop, for N sec, do the following
- // BEGIN transaction
- // insert into table t1, t2
- // Commit
- long now = System.currentTimeMillis();
- int k;
- for (k = 0;(System.currentTimeMillis() - now) / 1000 < TIME_TO_RUN_WAL_TEST_FOR; k++) {
- db.beginTransactionNonExclusive();
- setInXactFlag(true);
- for (int i = 0; i < 10; i++) {
- db.execSQL("insert into t1 values(?,?);", new String[] {i+"", i+""});
- db.execSQL("insert into t2 values(?,?);", new String[] {i+"", i+""});
- }
- db.setTransactionSuccessful();
- setInXactFlag(false);
- db.endTransaction();
- }
- setNumXacts(k);
- }
- }
-
- private class Reader extends Thread {
- private SQLiteDatabase db = null;
- private String table = null;
- private Writer w = null;
- private int readerNum;
- private int[] numReads = new int[2];
- public Reader(SQLiteDatabase db, String table, Writer w, int readerNum) {
- this.db = db;
- this.table = table;
- this.w = w;
- this.readerNum = readerNum;
- }
- @Override public void run() {
- // while the write is alive, in a loop do the query on a table
- while (w.isAlive()) {
- for (int i = 0; i < 10; i++) {
- DatabaseUtils.longForQuery(db, "select count(*) from " + this.table, null);
- // update count of reads
- numReads[inXact() ? 1 : 0] += 1;
- }
- }
- setCounts(readerNum, numReads);
- }
- }
-
- public static class ClassToTestSqlCompilationAndCaching extends SQLiteProgram {
- private ClassToTestSqlCompilationAndCaching(SQLiteDatabase db, String sql) {
- super(db, sql);
- }
- public static ClassToTestSqlCompilationAndCaching create(SQLiteDatabase db, String sql) {
- db.lock();
- try {
- return new ClassToTestSqlCompilationAndCaching(db, sql);
- } finally {
- db.unlock();
- }
- }
- }
-
- @SmallTest
- public void testLruCachingOfSqliteCompiledSqlObjs() {
- createTableAndClearCache();
- // set cache size
- int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
- mDatabase.setMaxSqlCacheSize(N);
-
- // do N+1 queries - and when the 0th entry is removed from LRU cache due to the
- // insertion of (N+1)th entry, make sure 0th entry is closed
- ArrayList stmtObjs = new ArrayList();
- ArrayList sqlStrings = new ArrayList();
- int stmt0 = 0;
- for (int i = 0; i < N+1; i++) {
- String s = "insert into test values(" + i + ",?);";
- sqlStrings.add(s);
- ClassToTestSqlCompilationAndCaching c =
- ClassToTestSqlCompilationAndCaching.create(mDatabase, s);
- int n = c.getSqlStatementId();
- stmtObjs.add(i, n);
- if (i == 0) {
- // save the statementId of this obj. we want to make sure it is thrown out of
- // the cache at the end of this test.
- stmt0 = n;
- }
- c.close();
- }
- // is 0'th entry out of the cache? it should be in the list of statementIds
- // corresponding to the pre-compiled sql statements to be finalized.
- assertTrue(mDatabase.getQueuedUpStmtList().contains(stmt0));
- for (int i = 1; i < N+1; i++) {
- SQLiteCompiledSql compSql = mDatabase.getCompiledStatementForSql(sqlStrings.get(i));
- assertNotNull(compSql);
- assertTrue(stmtObjs.contains(compSql.nStatement));
- }
- }
-
- @MediumTest
- public void testDbCloseReleasingAllCachedSql() {
- mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " +
- "num1 INTEGER, num2 INTEGER, image BLOB);");
- final String statement = "DELETE FROM test WHERE _id=?;";
- SQLiteStatement statementDoNotClose = mDatabase.compileStatement(statement);
- statementDoNotClose.bindLong(1, 1);
- /* do not close statementDoNotClose object.
- * That should leave it in SQLiteDatabase.mPrograms.
- * mDatabase.close() in tearDown() should release it.
- */
- }
-
- private void createTableAndClearCache() {
- mDatabase.disableWriteAheadLogging();
- mDatabase.execSQL("DROP TABLE IF EXISTS " + TEST_TABLE);
- mDatabase.execSQL("CREATE TABLE " + TEST_TABLE + " (i int, j int);");
- mDatabase.enableWriteAheadLogging();
- mDatabase.lock();
- // flush the above statement from cache and close all the pending statements to be released
- mDatabase.deallocCachedSqlStatements();
- mDatabase.closePendingStatements();
- mDatabase.unlock();
- assertEquals(0, mDatabase.getQueuedUpStmtList().size());
- }
-
- /**
- * test to make sure the statement finalizations are not done right away but
- * piggy-backed onto the next sql statement execution on the same database.
- */
- @SmallTest
- public void testStatementClose() {
- createTableAndClearCache();
- // fill up statement cache in mDatabase
- int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
- mDatabase.setMaxSqlCacheSize(N);
- SQLiteStatement stmt;
- int stmt0Id = 0;
- for (int i = 0; i < N; i ++) {
- ClassToTestSqlCompilationAndCaching c =
- ClassToTestSqlCompilationAndCaching.create(mDatabase,
- "insert into test values(" + i + ", ?);");
- // keep track of 0th entry
- if (i == 0) {
- stmt0Id = c.getSqlStatementId();
- }
- c.close();
- }
-
- // add one more to the cache - and the above 'stmt0Id' should fall out of cache
- ClassToTestSqlCompilationAndCaching stmt1 =
- ClassToTestSqlCompilationAndCaching.create(mDatabase,
- "insert into test values(100, ?);");
- stmt1.close();
-
- // the above close() should have queuedUp the statement for finalization
- ArrayList statementIds = mDatabase.getQueuedUpStmtList();
- assertTrue(statementIds.contains(stmt0Id));
-
- // execute something to see if this statement gets finalized
- mDatabase.execSQL("delete from test where i = 10;");
- statementIds = mDatabase.getQueuedUpStmtList();
- assertFalse(statementIds.contains(stmt0Id));
- }
-
- /**
- * same as above - except that the statement to be finalized is from Thread # 1.
- * and it is eventually finalized in Thread # 2 when it executes a SQL statement.
- * @throws InterruptedException
- */
- @LargeTest
- public void testStatementCloseDiffThread() throws InterruptedException {
- createTableAndClearCache();
- final int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
- mDatabase.setMaxSqlCacheSize(N);
- // fill up statement cache in mDatabase in a thread
- Thread t1 = new Thread() {
- @Override public void run() {
- SQLiteStatement stmt;
- for (int i = 0; i < N; i++) {
- ClassToTestSqlCompilationAndCaching c =
- ClassToTestSqlCompilationAndCaching.create(getDb(),
- "insert into test values(" + i + ", ?);");
- // keep track of 0th entry
- if (i == 0) {
- stmt0Id = c.getSqlStatementId();
- }
- c.close();
- }
- }
- };
- t1.start();
- // wait for the thread to finish
- t1.join();
- // mDatabase shouldn't have any statements to be released
- assertEquals(0, mDatabase.getQueuedUpStmtList().size());
-
- // add one more to the cache - and the above 'stmt0Id' should fall out of cache
- // just for the heck of it, do it in a separate thread
- Thread t2 = new Thread() {
- @Override public void run() {
- ClassToTestSqlCompilationAndCaching stmt1 =
- ClassToTestSqlCompilationAndCaching.create(getDb(),
- "insert into test values(100, ?);");
- stmt1.bindLong(1, 1);
- stmt1.close();
- }
- };
- t2.start();
- t2.join();
-
- // close() in the above thread should have queuedUp the stmt0Id for finalization
- ArrayList statementIds = getDb().getQueuedUpStmtList();
- assertTrue(statementIds.contains(getStmt0Id()));
- assertEquals(1, statementIds.size());
-
- // execute something to see if this statement gets finalized
- // again do it in a separate thread
- Thread t3 = new Thread() {
- @Override public void run() {
- getDb().execSQL("delete from test where i = 10;");
- }
- };
- t3.start();
- t3.join();
-
- // is the statement finalized?
- statementIds = getDb().getQueuedUpStmtList();
- assertFalse(statementIds.contains(getStmt0Id()));
- }
-
- private volatile int stmt0Id = 0;
- private synchronized int getStmt0Id() {
- return this.stmt0Id;
- }
-
- /**
- * same as above - except that the queue of statements to be finalized are finalized
- * by database close() operation.
- */
- @LargeTest
- public void testStatementCloseByDbClose() throws InterruptedException {
- createTableAndClearCache();
- // fill up statement cache in mDatabase in a thread
- Thread t1 = new Thread() {
- @Override public void run() {
- int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
- getDb().setMaxSqlCacheSize(N);
- SQLiteStatement stmt;
- for (int i = 0; i < N; i ++) {
- ClassToTestSqlCompilationAndCaching c =
- ClassToTestSqlCompilationAndCaching.create(getDb(),
- "insert into test values(" + i + ", ?);");
- // keep track of 0th entry
- if (i == 0) {
- stmt0Id = c.getSqlStatementId();
- }
- c.close();
- }
- }
- };
- t1.start();
- // wait for the thread to finish
- t1.join();
-
- // add one more to the cache - and the above 'stmt0Id' should fall out of cache
- // just for the heck of it, do it in a separate thread
- Thread t2 = new Thread() {
- @Override public void run() {
- ClassToTestSqlCompilationAndCaching stmt1 =
- ClassToTestSqlCompilationAndCaching.create(getDb(),
- "insert into test values(100, ?);");
- stmt1.bindLong(1, 1);
- stmt1.close();
- }
- };
- t2.start();
- t2.join();
-
- // close() in the above thread should have queuedUp the statement for finalization
- ArrayList statementIds = getDb().getQueuedUpStmtList();
- assertTrue(getStmt0Id() > 0);
- assertTrue(statementIds.contains(stmt0Id));
- assertEquals(1, statementIds.size());
-
- // close the database. everything from mClosedStatementIds in mDatabase
- // should be finalized and cleared from the list
- // again do it in a separate thread
- Thread t3 = new Thread() {
- @Override public void run() {
- getDb().close();
- }
- };
- t3.start();
- t3.join();
-
- // check mClosedStatementIds in mDatabase. it should be empty
- statementIds = getDb().getQueuedUpStmtList();
- assertEquals(0, statementIds.size());
- }
-
- /**
- * This test tests usage execSQL() to begin transaction works in the following way
- * Thread #1 does
- * execSQL("begin transaction");
- * insert()
- * Thread # 2
- * query()
- * Thread#1 ("end transaction")
- * Thread # 2 query will execute - because java layer will not have locked the SQLiteDatabase
- * object and sqlite will consider this query to be part of the transaction.
- *
- * but if thread # 1 uses beginTransaction() instead of execSQL() to start transaction,
- * then Thread # 2's query will have been blocked by java layer
- * until Thread#1 ends transaction.
- *
- * @throws InterruptedException
- */
- @SmallTest
- public void testExecSqlToStartAndEndTransaction() throws InterruptedException {
- runExecSqlToStartAndEndTransaction("END");
- // same as above, instead now do "COMMIT" or "ROLLBACK" instead of "END" transaction
- runExecSqlToStartAndEndTransaction("COMMIT");
- runExecSqlToStartAndEndTransaction("ROLLBACK");
- }
- private void runExecSqlToStartAndEndTransaction(String str) throws InterruptedException {
- createTableAndClearCache();
- // disable WAL just so queries and updates use the same database connection
- mDatabase.disableWriteAheadLogging();
- mDatabase.execSQL("BEGIN transaction");
- // even though mDatabase.beginTransaction() is not called to start transaction,
- // mDatabase connection should now be in transaction as a result of
- // mDatabase.execSQL("BEGIN transaction")
- // but mDatabase.mLock should not be held by any thread
- assertTrue(mDatabase.inTransaction());
- assertFalse(mDatabase.isDbLockedByCurrentThread());
- assertFalse(mDatabase.isDbLockedByOtherThreads());
- assertTrue(mDatabase.amIInTransaction());
- mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
- assertTrue(mDatabase.inTransaction());
- assertFalse(mDatabase.isDbLockedByCurrentThread());
- assertFalse(mDatabase.isDbLockedByOtherThreads());
- assertTrue(mDatabase.amIInTransaction());
- Thread t = new Thread() {
- @Override public void run() {
- assertTrue(mDatabase.amIInTransaction());
- assertEquals(999, DatabaseUtils.longForQuery(getDb(),
- "select j from " + TEST_TABLE + " WHERE i = 10", null));
- assertTrue(getDb().inTransaction());
- assertFalse(getDb().isDbLockedByCurrentThread());
- assertFalse(getDb().isDbLockedByOtherThreads());
- assertTrue(mDatabase.amIInTransaction());
- }
- };
- t.start();
- t.join();
- assertTrue(mDatabase.amIInTransaction());
- assertTrue(mDatabase.inTransaction());
- assertFalse(mDatabase.isDbLockedByCurrentThread());
- assertFalse(mDatabase.isDbLockedByOtherThreads());
- mDatabase.execSQL(str);
- assertFalse(mDatabase.amIInTransaction());
- assertFalse(mDatabase.inTransaction());
- assertFalse(mDatabase.isDbLockedByCurrentThread());
- assertFalse(mDatabase.isDbLockedByOtherThreads());
- }
-
- /**
- * test the following
- * http://b/issue?id=2871037
- * Cursor cursor = db.query(...);
- * // with WAL enabled, the above uses a pooled database connection
- * db.beginTransaction()
- * try {
- * db.insert(......);
- * cursor.requery();
- * // since the cursor uses pooled database connection, the above requery
- * // will not return the results that were inserted above since the insert is
- * // done using main database connection AND the transaction is not committed yet.
- * // fix is to make the above cursor use the main database connection - and NOT
- * // the pooled database connection
- * db.setTransactionSuccessful()
- * } finally {
- * db.endTransaction()
- * }
- *
- * @throws InterruptedException
- */
- @SmallTest
- public void testTransactionAndWalInterplay1() throws InterruptedException {
- createTableAndClearCache();
- mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
- String sql = "select * from " + TEST_TABLE;
- Cursor c = mDatabase.rawQuery(sql, null);
- // should have 1 row in the table
- assertEquals(1, c.getCount());
- mDatabase.beginTransactionNonExclusive();
- try {
- mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
- assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
- "select count(*) from " + TEST_TABLE, null));
- // requery on the previously opened cursor
- // cursor should now use the main database connection and see 2 rows
- c.requery();
- assertEquals(2, c.getCount());
- mDatabase.setTransactionSuccessful();
- } finally {
- mDatabase.endTransaction();
- }
- c.close();
-
- // do the same test but now do the requery in a separate thread.
- createTableAndClearCache();
- mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
- final Cursor c1 = mDatabase.rawQuery("select count(*) from " + TEST_TABLE, null);
- // should have 1 row in the table
- assertEquals(1, c1.getCount());
- mDatabase.beginTransactionNonExclusive();
- try {
- mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
- assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
- "select count(*) from " + TEST_TABLE, null));
- // query in a different thread. that causes the cursor to use a pooled connection
- // and since this thread hasn't committed its changes, the cursor should still see only
- // 1 row
- Thread t = new Thread() {
- @Override public void run() {
- c1.requery();
- assertEquals(1, c1.getCount());
- }
- };
- t.start();
- t.join();
- // should be 2 rows now - including the the row inserted above
- mDatabase.setTransactionSuccessful();
- } finally {
- mDatabase.endTransaction();
- }
- c1.close();
- }
-
- /**
- * This test is same as {@link #testTransactionAndWalInterplay1()} except the following:
- * instead of mDatabase.beginTransactionNonExclusive(), use execSQL("BEGIN transaction")
- * and instead of mDatabase.endTransaction(), use execSQL("END");
- */
- @SmallTest
- public void testTransactionAndWalInterplay2() throws InterruptedException {
- createTableAndClearCache();
- mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
- String sql = "select * from " + TEST_TABLE;
- Cursor c = mDatabase.rawQuery(sql, null);
- // should have 1 row in the table
- assertEquals(1, c.getCount());
- mDatabase.execSQL("BEGIN transaction");
- try {
- mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
- assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
- "select count(*) from " + TEST_TABLE, null));
- // requery on the previously opened cursor
- // cursor should now use the main database connection and see 2 rows
- c.requery();
- assertEquals(2, c.getCount());
- } finally {
- mDatabase.execSQL("commit;");
- }
- c.close();
-
- // do the same test but now do the requery in a separate thread.
- createTableAndClearCache();
- mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
- final Cursor c1 = mDatabase.rawQuery("select count(*) from " + TEST_TABLE, null);
- // should have 1 row in the table
- assertEquals(1, c1.getCount());
- mDatabase.execSQL("BEGIN transaction");
- try {
- mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
- assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
- "select count(*) from " + TEST_TABLE, null));
- // query in a different thread. but since the transaction is started using
- // execSQ() instead of beginTransaction(), cursor's query is considered part of
- // the same transaction - and hence it should see the above inserted row
- Thread t = new Thread() {
- @Override public void run() {
- c1.requery();
- assertEquals(1, c1.getCount());
- }
- };
- t.start();
- t.join();
- // should be 2 rows now - including the the row inserted above
- } finally {
- mDatabase.execSQL("commit");
- }
- c1.close();
- }
-
- /**
- * This test is same as {@link #testTransactionAndWalInterplay2()} except the following:
- * instead of committing the data, do rollback and make sure the data seen by the query
- * within the transaction is now gone.
- */
- @SmallTest
- public void testTransactionAndWalInterplay3() {
- createTableAndClearCache();
- mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
- String sql = "select * from " + TEST_TABLE;
- Cursor c = mDatabase.rawQuery(sql, null);
- // should have 1 row in the table
- assertEquals(1, c.getCount());
- mDatabase.execSQL("BEGIN transaction");
- try {
- mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
- assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
- "select count(*) from " + TEST_TABLE, null));
- // requery on the previously opened cursor
- // cursor should now use the main database connection and see 2 rows
- c.requery();
- assertEquals(2, c.getCount());
- } finally {
- // rollback the change
- mDatabase.execSQL("rollback;");
- }
- // since the change is rolled back, do the same query again and should now find only 1 row
- c.requery();
- assertEquals(1, c.getCount());
- assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "select count(*) from " + TEST_TABLE, null));
- c.close();
- }
-
- @SmallTest
- public void testAttachDb() {
- String newDb = "/sdcard/mydata.db";
- File f = new File(newDb);
- if (f.exists()) {
- f.delete();
- }
- assertFalse(f.exists());
- SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(newDb, null);
- db.execSQL("create table test1 (i int);");
- db.execSQL("insert into test1 values(1);");
- db.execSQL("insert into test1 values(11);");
- Cursor c = null;
- try {
- c = db.rawQuery("select * from test1", null);
- int count = c.getCount();
- Log.i(TAG, "count: " + count);
- assertEquals(2, count);
- } finally {
- c.close();
- db.close();
- c = null;
- }
-
- mDatabase.execSQL("attach database ? as newDb" , new String[]{newDb});
- Cursor c1 = null;
- try {
- c1 = mDatabase.rawQuery("select * from newDb.test1", null);
- assertEquals(2, c1.getCount());
- } catch (Exception e) {
- fail("unexpected exception: " + e.getMessage());
- } finally {
- if (c1 != null) {
- c1.close();
- }
- }
- List> dbs = mDatabase.getAttachedDbs();
- for (Pair p: dbs) {
- Log.i(TAG, "attached dbs: " + p.first + " : " + p.second);
- }
- assertEquals(2, dbs.size());
- }
-
- /**
- * http://b/issue?id=2943028
- * SQLiteOpenHelper maintains a Singleton even if it is in bad state.
- */
- @SmallTest
- public void testCloseAndReopen() {
- mDatabase.close();
- TestOpenHelper helper = new TestOpenHelper(getContext(), DB_NAME, null,
- CURRENT_DATABASE_VERSION, new DefaultDatabaseErrorHandler());
- mDatabase = helper.getWritableDatabase();
- createTableAndClearCache();
- mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
- Cursor c = mDatabase.query(TEST_TABLE, new String[]{"i", "j"}, null, null, null, null, null);
- assertEquals(1, c.getCount());
- c.close();
- mDatabase.close();
- assertFalse(mDatabase.isOpen());
- mDatabase = helper.getReadableDatabase();
- assertTrue(mDatabase.isOpen());
- c = mDatabase.query(TEST_TABLE, new String[]{"i", "j"}, null, null, null, null, null);
- assertEquals(1, c.getCount());
- c.close();
- }
- private class TestOpenHelper extends SQLiteOpenHelper {
- public TestOpenHelper(Context context, String name, CursorFactory factory, int version,
- DatabaseErrorHandler errorHandler) {
- super(context, name, factory, version, errorHandler);
- }
- @Override public void onCreate(SQLiteDatabase db) {}
- @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
- }
-}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
deleted file mode 100644
index 955336af19aa3..0000000000000
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.database.sqlite;
-
-import android.content.Context;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import java.io.File;
-import java.util.Random;
-import java.util.concurrent.locks.ReentrantLock;
-
-public class SQLiteStatementTest extends AndroidTestCase {
- private SQLiteDatabase mDatabase;
- private File mDatabaseFile;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
- mDatabaseFile = new File(dbDir, "database_test.db");
- if (mDatabaseFile.exists()) {
- mDatabaseFile.delete();
- }
- mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
- assertNotNull(mDatabase);
- }
-
- @Override
- protected void tearDown() throws Exception {
- mDatabase.close();
- mDatabaseFile.delete();
- super.tearDown();
- }
-
- /**
- * Start 2 threads to repeatedly execute the above SQL statement.
- * Even though 2 threads are executing the same SQL, they each should get their own copy of
- * prepared SQL statement id and there SHOULD NOT be an error from sqlite or android.
- * @throws InterruptedException thrown if the test threads started by this test are interrupted
- */
- @LargeTest
- public void testUseOfSameSqlStatementBy2Threads() throws InterruptedException {
- mDatabase.execSQL("CREATE TABLE test_pstmt (i INTEGER PRIMARY KEY, j text);");
- final String stmt = "SELECT * FROM test_pstmt WHERE i = ?";
- class RunStmtThread extends Thread {
- @Override public void run() {
- // do it enough times to make sure there are no corner cases going untested
- for (int i = 0; i < 1000; i++) {
- SQLiteStatement s1 = mDatabase.compileStatement(stmt);
- s1.bindLong(1, i);
- s1.execute();
- s1.close();
- }
- }
- }
- RunStmtThread t1 = new RunStmtThread();
- t1.start();
- RunStmtThread t2 = new RunStmtThread();
- t2.start();
- while (t1.isAlive() || t2.isAlive()) {
- Thread.sleep(10);
- }
- }
-
- /**
- * A simple test: start 2 threads to repeatedly execute the same {@link SQLiteStatement}.
- * The 2 threads take turns to use the {@link SQLiteStatement}; i.e., it is NOT in use
- * by both the threads at the same time.
- *
- * @throws InterruptedException thrown if the test threads started by this test are interrupted
- */
- @LargeTest
- public void testUseOfSameSqliteStatementBy2Threads() throws InterruptedException {
- mDatabase.execSQL("CREATE TABLE test_pstmt (i INTEGER PRIMARY KEY, j text);");
- final String stmt = "SELECT * FROM test_pstmt WHERE i = ?";
- final SQLiteStatement s1 = mDatabase.compileStatement(stmt);
- class RunStmtThread extends Thread {
- @Override public void run() {
- // do it enough times to make sure there are no corner cases going untested
- for (int i = 0; i < 1000; i++) {
- lock();
- try {
- s1.bindLong(1, i);
- s1.execute();
- } finally {
- unlock();
- }
- Thread.yield();
- }
- }
- }
- RunStmtThread t1 = new RunStmtThread();
- t1.start();
- RunStmtThread t2 = new RunStmtThread();
- t2.start();
- while (t1.isAlive() || t2.isAlive()) {
- Thread.sleep(10);
- }
- }
- /** Synchronize on this when accessing the SqliteStatemet in the above */
- private final ReentrantLock mLock = new ReentrantLock(true);
- private void lock() {
- mLock.lock();
- }
- private void unlock() {
- mLock.unlock();
- }
-
- /**
- * Tests the following: a {@link SQLiteStatement} object should not refer to a
- * pre-compiled SQL statement id except in during the period of binding the arguments
- * and executing the SQL statement.
- */
- @LargeTest
- public void testReferenceToPrecompiledStatementId() {
- mDatabase.execSQL("create table t (i int, j text);");
- verifyReferenceToPrecompiledStatementId(false);
- verifyReferenceToPrecompiledStatementId(true);
-
- // a small stress test to make sure there are no side effects of
- // the acquire & release of pre-compiled statement id by SQLiteStatement object.
- for (int i = 0; i < 100; i++) {
- verifyReferenceToPrecompiledStatementId(false);
- verifyReferenceToPrecompiledStatementId(true);
- }
- }
-
- @SuppressWarnings("deprecation")
- private void verifyReferenceToPrecompiledStatementId(boolean wal) {
- if (wal) {
- mDatabase.enableWriteAheadLogging();
- } else {
- mDatabase.disableWriteAheadLogging();
- }
- // test with INSERT statement - doesn't use connection pool, if WAL is set
- SQLiteStatement stmt = mDatabase.compileStatement("insert into t values(?,?);");
- assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
- assertEquals(mDatabase, stmt.mDatabase);
- // sql statement should not be compiled yet
- assertEquals(0, stmt.nStatement);
- assertEquals(0, stmt.getSqlStatementId());
- int colValue = new Random().nextInt();
- stmt.bindLong(1, colValue);
- // verify that the sql statement is still not compiled
- assertEquals(0, stmt.getSqlStatementId());
- // should still be using the mDatabase connection - verify
- assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
- assertEquals(mDatabase, stmt.mDatabase);
- stmt.bindString(2, "blah" + colValue);
- // verify that the sql statement is still not compiled
- assertEquals(0, stmt.getSqlStatementId());
- assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
- assertEquals(mDatabase, stmt.mDatabase);
- stmt.executeInsert();
- // now that the statement is executed, pre-compiled statement should be released
- assertEquals(0, stmt.nStatement);
- assertEquals(0, stmt.getSqlStatementId());
- assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
- assertEquals(mDatabase, stmt.mDatabase);
- stmt.close();
- // pre-compiled SQL statement should still remain released from this object
- assertEquals(0, stmt.nStatement);
- assertEquals(0, stmt.getSqlStatementId());
- // but the database handle should still be the same
- assertEquals(mDatabase, stmt.mDatabase);
-
- // test with a SELECT statement - uses connection pool if WAL is set
- stmt = mDatabase.compileStatement("select i from t where j=?;");
- // sql statement should not be compiled yet
- assertEquals(0, stmt.nStatement);
- assertEquals(0, stmt.getSqlStatementId());
- assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
- assertEquals(mDatabase, stmt.mDatabase);
- stmt.bindString(1, "blah" + colValue);
- // verify that the sql statement is still not compiled
- assertEquals(0, stmt.nStatement);
- assertEquals(0, stmt.getSqlStatementId());
- assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
- assertEquals(mDatabase, stmt.mDatabase);
- // execute the statement
- Long l = stmt.simpleQueryForLong();
- assertEquals(colValue, l.intValue());
- // now that the statement is executed, pre-compiled statement should be released
- assertEquals(0, stmt.nStatement);
- assertEquals(0, stmt.getSqlStatementId());
- assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
- assertEquals(mDatabase, stmt.mDatabase);
- stmt.close();
- // pre-compiled SQL statement should still remain released from this object
- assertEquals(0, stmt.nStatement);
- assertEquals(0, stmt.getSqlStatementId());
- // but the database handle should still remain attached to the statement
- assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
- assertEquals(mDatabase, stmt.mDatabase);
- }
-}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteUnfinalizedExceptionTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteUnfinalizedExceptionTest.java
deleted file mode 100644
index cd2005dd16ff9..0000000000000
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteUnfinalizedExceptionTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.database.sqlite;
-
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabaseTest.ClassToTestSqlCompilationAndCaching;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import java.io.File;
-
-public class SQLiteUnfinalizedExceptionTest extends AndroidTestCase {
- private SQLiteDatabase mDatabase;
- private File mDatabaseFile;
- private static final String TABLE_NAME = "testCursor";
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
- mDatabaseFile = new File(dbDir, "UnfinalizedExceptionTest.db");
- if (mDatabaseFile.exists()) {
- mDatabaseFile.delete();
- }
- mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
- assertNotNull(mDatabase);
- }
-
- @Override
- protected void tearDown() throws Exception {
- mDatabase.close();
- mDatabaseFile.delete();
- super.tearDown();
- }
-
- @SmallTest
- public void testUnfinalizedExceptionNotExcpected() {
- mDatabase.execSQL("CREATE TABLE " + TABLE_NAME + " (i int, j int);");
- // the above statement should be in SQLiteDatabase.mPrograms
- // and should automatically be finalized when database is closed
- mDatabase.lock();
- try {
- mDatabase.closeDatabase();
- } finally {
- mDatabase.unlock();
- }
- }
-
- @SmallTest
- public void testUnfinalizedException() {
- mDatabase.execSQL("CREATE TABLE " + TABLE_NAME + " (i int, j int);");
- mDatabase.lock();
- mDatabase.closePendingStatements(); // clears the above from finalizer queue in mdatabase
- mDatabase.unlock();
- ClassToTestSqlCompilationAndCaching.create(mDatabase, "select * from " + TABLE_NAME);
- // since the above is NOT closed, closing database should fail
- mDatabase.lock();
- try {
- mDatabase.closeDatabase();
- fail("exception expected");
- } catch (SQLiteUnfinalizedObjectsException e) {
- // expected
- } finally {
- mDatabase.unlock();
- }
- }
-}