M7350v1_en_gpl

This commit is contained in:
T
2024-09-09 08:52:07 +00:00
commit f9cc65cfda
65988 changed files with 26357421 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
/*
* 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;
/**
* An exception that indicates that garbage-collector is finalizing a database object
* that is not explicitly closed
* @hide
*/
public class DatabaseObjectNotClosedException extends RuntimeException
{
private static final String s = "Application did not close the cursor or database object " +
"that was opened here";
public DatabaseObjectNotClosedException()
{
super(s);
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 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.
*/
package android.database.sqlite;
/**
* An exception that indicates that the SQLite program was aborted.
* This can happen either through a call to ABORT in a trigger,
* or as the result of using the ABORT conflict clause.
*/
public class SQLiteAbortException extends SQLiteException {
public SQLiteAbortException() {}
public SQLiteAbortException(String error) {
super(error);
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.
*/
package android.database.sqlite;
import android.database.CursorWindow;
/**
* An object created from a SQLiteDatabase that can be closed.
*/
public abstract class SQLiteClosable {
private int mReferenceCount = 1;
private Object mLock = new Object();
protected abstract void onAllReferencesReleased();
protected void onAllReferencesReleasedFromContainer() {}
public void acquireReference() {
synchronized(mLock) {
if (mReferenceCount <= 0) {
throw new IllegalStateException(
"attempt to re-open an already-closed object: " + getObjInfo());
}
mReferenceCount++;
}
}
public void releaseReference() {
synchronized(mLock) {
mReferenceCount--;
if (mReferenceCount == 0) {
onAllReferencesReleased();
}
}
}
public void releaseReferenceFromContainer() {
synchronized(mLock) {
mReferenceCount--;
if (mReferenceCount == 0) {
onAllReferencesReleasedFromContainer();
}
}
}
private String getObjInfo() {
StringBuilder buff = new StringBuilder();
buff.append(this.getClass().getName());
buff.append(" (");
if (this instanceof SQLiteDatabase) {
buff.append("database = ");
buff.append(((SQLiteDatabase)this).getPath());
} else if (this instanceof SQLiteProgram || this instanceof SQLiteStatement ||
this instanceof SQLiteQuery) {
buff.append("mSql = ");
buff.append(((SQLiteProgram)this).mSql);
} else if (this instanceof CursorWindow) {
buff.append("mStartPos = ");
buff.append(((CursorWindow)this).getStartPosition());
}
buff.append(") ");
return buff.toString();
}
}

View File

@@ -0,0 +1,171 @@
/*
* Copyright (C) 2009 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.StrictMode;
import android.util.Log;
/**
* This class encapsulates compilation of sql statement and release of the compiled statement obj.
* Once a sql statement is compiled, it is cached in {@link SQLiteDatabase}
* and it is released in one of the 2 following ways
* 1. when {@link SQLiteDatabase} object is closed.
* 2. if this is not cached in {@link SQLiteDatabase}, {@link android.database.Cursor#close()}
* releaases this obj.
*/
/* package */ class SQLiteCompiledSql {
private static final String TAG = "SQLiteCompiledSql";
/** The database this program is compiled against. */
/* package */ SQLiteDatabase mDatabase;
/**
* Native linkage, do not modify. This comes from the database.
*/
/* package */ int nHandle = 0;
/**
* Native linkage, do not modify. When non-0 this holds a reference to a valid
* sqlite3_statement object. It is only updated by the native code, but may be
* checked in this class when the database lock is held to determine if there
* is a valid native-side program or not.
*/
/* package */ int nStatement = 0;
/** the following are for debugging purposes */
private String mSqlStmt = null;
private Throwable mStackTrace = null;
/** when in cache and is in use, this member is set */
private boolean mInUse = false;
/* package */ SQLiteCompiledSql(SQLiteDatabase db, String sql) {
if (!db.isOpen()) {
throw new IllegalStateException("database " + db.getPath() + " already closed");
}
mDatabase = db;
mSqlStmt = sql;
mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
this.nHandle = db.mNativeHandle;
compile(sql, true);
}
/**
* Compiles the given SQL into a SQLite byte code program using sqlite3_prepare_v2(). If
* this method has been called previously without a call to close and forCompilation is set
* to false the previous compilation will be used. Setting forceCompilation to true will
* always re-compile the program and should be done if you pass differing SQL strings to this
* method.
*
* <P>Note: this method acquires the database lock.</P>
*
* @param sql the SQL string to compile
* @param forceCompilation forces the SQL to be recompiled in the event that there is an
* existing compiled SQL program already around
*/
private void compile(String sql, boolean forceCompilation) {
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
// Only compile if we don't have a valid statement already or the caller has
// explicitly requested a recompile.
if (forceCompilation) {
mDatabase.lock();
try {
// Note that the native_compile() takes care of destroying any previously
// existing programs before it compiles.
native_compile(sql);
} finally {
mDatabase.unlock();
}
}
}
/* package */ void releaseSqlStatement() {
// Note that native_finalize() checks to make sure that nStatement is
// non-null before destroying it.
if (nStatement != 0) {
if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
Log.v(TAG, "closed and deallocated DbObj (id#" + nStatement +")");
}
try {
mDatabase.lock();
native_finalize();
nStatement = 0;
} finally {
mDatabase.unlock();
}
}
}
/**
* returns true if acquire() succeeds. false otherwise.
*/
/* package */ synchronized boolean acquire() {
if (mInUse) {
// someone already has acquired it.
return false;
}
mInUse = true;
if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
Log.v(TAG, "Acquired DbObj (id#" + nStatement + ") from DB cache");
}
return true;
}
/* package */ synchronized void release() {
if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
Log.v(TAG, "Released DbObj (id#" + nStatement + ") back to DB cache");
}
mInUse = false;
}
/**
* Make sure that the native resource is cleaned up.
*/
@Override
protected void finalize() throws Throwable {
try {
if (nStatement == 0) return;
// finalizer should NEVER get called
if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
Log.v(TAG, "** warning ** Finalized DbObj (id#" + nStatement + ")");
}
if (StrictMode.vmSqliteObjectLeaksEnabled()) {
int len = mSqlStmt.length();
StrictMode.onSqliteObjectLeaked(
"Releasing statement in a finalizer. Please ensure " +
"that you explicitly call close() on your cursor: " +
mSqlStmt.substring(0, (len > 100) ? 100 : len),
mStackTrace);
}
releaseSqlStatement();
} finally {
super.finalize();
}
}
/**
* Compiles SQL into a SQLite program.
*
* <P>The database lock must be held when calling this method.
* @param sql The SQL to compile.
*/
private final native void native_compile(String sql);
private final native void native_finalize();
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 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.
*/
package android.database.sqlite;
/**
* An exception that indicates that an integrity constraint was violated.
*/
public class SQLiteConstraintException extends SQLiteException {
public SQLiteConstraintException() {}
public SQLiteConstraintException(String error) {
super(error);
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2009 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.res.AssetFileDescriptor;
import android.database.Cursor;
import android.os.MemoryFile;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* Some helper functions for using SQLite database to implement content providers.
*
* @hide
*/
public class SQLiteContentHelper {
/**
* Runs an SQLite query and returns an AssetFileDescriptor for the
* blob in column 0 of the first row. If the first column does
* not contain a blob, an unspecified exception is thrown.
*
* @param db Handle to a readable database.
* @param sql SQL query, possibly with query arguments.
* @param selectionArgs Query argument values, or {@code null} for no argument.
* @return If no exception is thrown, a non-null AssetFileDescriptor is returned.
* @throws FileNotFoundException If the query returns no results or the
* value of column 0 is NULL, or if there is an error creating the
* asset file descriptor.
*/
public static AssetFileDescriptor getBlobColumnAsAssetFile(SQLiteDatabase db, String sql,
String[] selectionArgs) throws FileNotFoundException {
try {
MemoryFile file = simpleQueryForBlobMemoryFile(db, sql, selectionArgs);
if (file == null) {
throw new FileNotFoundException("No results.");
}
return AssetFileDescriptor.fromMemoryFile(file);
} catch (IOException ex) {
throw new FileNotFoundException(ex.toString());
}
}
/**
* Runs an SQLite query and returns a MemoryFile for the
* blob in column 0 of the first row. If the first column does
* not contain a blob, an unspecified exception is thrown.
*
* @return A memory file, or {@code null} if the query returns no results
* or the value column 0 is NULL.
* @throws IOException If there is an error creating the memory file.
*/
// TODO: make this native and use the SQLite blob API to reduce copying
private static MemoryFile simpleQueryForBlobMemoryFile(SQLiteDatabase db, String sql,
String[] selectionArgs) throws IOException {
Cursor cursor = db.rawQuery(sql, selectionArgs);
if (cursor == null) {
return null;
}
try {
if (!cursor.moveToFirst()) {
return null;
}
byte[] bytes = cursor.getBlob(0);
if (bytes == null) {
return null;
}
MemoryFile file = new MemoryFile(null, bytes.length);
file.writeBytes(bytes, 0, 0, bytes.length);
file.deactivate();
return file;
} finally {
cursor.close();
}
}
}

View File

@@ -0,0 +1,606 @@
/*
* 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.database.AbstractWindowedCursor;
import android.database.CursorWindow;
import android.database.DataSetObserver;
import android.database.SQLException;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
import android.os.StrictMode;
import android.text.TextUtils;
import android.util.Config;
import android.util.Log;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
/**
* A Cursor implementation that exposes results from a query on a
* {@link SQLiteDatabase}.
*
* SQLiteCursor is not internally synchronized so code using a SQLiteCursor from multiple
* threads should perform its own synchronization when using the SQLiteCursor.
*/
public class SQLiteCursor extends AbstractWindowedCursor {
static final String TAG = "Cursor";
static final int NO_COUNT = -1;
/** The name of the table to edit */
private String mEditTable;
/** The names of the columns in the rows */
private String[] mColumns;
/** The query object for the cursor */
private SQLiteQuery mQuery;
/** The database the cursor was created from */
private SQLiteDatabase mDatabase;
/** The compiled query this cursor came from */
private SQLiteCursorDriver mDriver;
/** The number of rows in the cursor */
private int mCount = NO_COUNT;
/** A mapping of column names to column indices, to speed up lookups */
private Map<String, Integer> mColumnNameMap;
/** Used to find out where a cursor was allocated in case it never got released. */
private Throwable mStackTrace;
/**
* mMaxRead is the max items that each cursor window reads
* default to a very high value
*/
private int mMaxRead = Integer.MAX_VALUE;
private int mInitialRead = Integer.MAX_VALUE;
private int mCursorState = 0;
private ReentrantLock mLock = null;
private boolean mPendingData = false;
/**
* support for a cursor variant that doesn't always read all results
* initialRead is the initial number of items that cursor window reads
* if query contains more than this number of items, a thread will be
* created and handle the left over items so that caller can show
* results as soon as possible
* @param initialRead initial number of items that cursor read
* @param maxRead leftover items read at maxRead items per time
* @hide
*/
public void setLoadStyle(int initialRead, int maxRead) {
mMaxRead = maxRead;
mInitialRead = initialRead;
mLock = new ReentrantLock(true);
}
private void queryThreadLock() {
if (mLock != null) {
mLock.lock();
}
}
private void queryThreadUnlock() {
if (mLock != null) {
mLock.unlock();
}
}
/**
* @hide
*/
final private class QueryThread implements Runnable {
private final int mThreadState;
QueryThread(int version) {
mThreadState = version;
}
private void sendMessage() {
if (mNotificationHandler != null) {
mNotificationHandler.sendEmptyMessage(1);
mPendingData = false;
} else {
mPendingData = true;
}
}
public void run() {
// use cached mWindow, to avoid get null mWindow
CursorWindow cw = mWindow;
Process.setThreadPriority(Process.myTid(), Process.THREAD_PRIORITY_BACKGROUND);
// the cursor's state doesn't change
while (true) {
mLock.lock();
if (mCursorState != mThreadState) {
mLock.unlock();
break;
}
try {
int count = mQuery.fillWindow(cw, mMaxRead, mCount);
// return -1 means not finished
if (count != 0) {
if (count == NO_COUNT){
mCount += mMaxRead;
sendMessage();
} else {
mCount = count;
sendMessage();
break;
}
} else {
break;
}
} catch (Exception e) {
// end the tread when the cursor is close
break;
} finally {
mLock.unlock();
}
}
}
}
/**
* @hide
*/
protected class MainThreadNotificationHandler extends Handler {
public void handleMessage(Message msg) {
notifyDataSetChange();
}
}
/**
* @hide
*/
protected MainThreadNotificationHandler mNotificationHandler;
public void registerDataSetObserver(DataSetObserver observer) {
super.registerDataSetObserver(observer);
if ((Integer.MAX_VALUE != mMaxRead || Integer.MAX_VALUE != mInitialRead) &&
mNotificationHandler == null) {
queryThreadLock();
try {
mNotificationHandler = new MainThreadNotificationHandler();
if (mPendingData) {
notifyDataSetChange();
mPendingData = false;
}
} finally {
queryThreadUnlock();
}
}
}
/**
* Execute a query and provide access to its result set through a Cursor
* interface. For a query such as: {@code SELECT name, birth, phone FROM
* myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth,
* phone) would be in the projection argument and everything from
* {@code FROM} onward would be in the params argument. This constructor
* has package scope.
*
* @param db a reference to a Database object that is already constructed
* and opened
* @param editTable the name of the table used for this query
* @param query the rest of the query terms
* cursor is finalized
*/
public SQLiteCursor(SQLiteDatabase db, SQLiteCursorDriver driver,
String editTable, SQLiteQuery query) {
// The AbstractCursor constructor needs to do some setup.
super();
mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
mDatabase = db;
mDriver = driver;
mEditTable = editTable;
mColumnNameMap = null;
mQuery = query;
try {
db.lock();
// Setup the list of columns
int columnCount = mQuery.columnCountLocked();
mColumns = new String[columnCount];
// Read in all column names
for (int i = 0; i < columnCount; i++) {
String columnName = mQuery.columnNameLocked(i);
mColumns[i] = columnName;
if (Config.LOGV) {
Log.v("DatabaseWindow", "mColumns[" + i + "] is "
+ mColumns[i]);
}
// Make note of the row ID column index for quick access to it
if ("_id".equals(columnName)) {
mRowIdColumnIndex = i;
}
}
} finally {
db.unlock();
}
}
/**
* @return the SQLiteDatabase that this cursor is associated with.
*/
public SQLiteDatabase getDatabase() {
return mDatabase;
}
@Override
public boolean onMove(int oldPosition, int newPosition) {
// Make sure the row at newPosition is present in the window
if (mWindow == null || newPosition < mWindow.getStartPosition() ||
newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) {
fillWindow(newPosition);
}
return true;
}
@Override
public int getCount() {
if (mCount == NO_COUNT) {
fillWindow(0);
}
return mCount;
}
private void fillWindow (int startPos) {
if (mWindow == null) {
// If there isn't a window set already it will only be accessed locally
mWindow = new CursorWindow(true /* the window is local only */);
} else {
mCursorState++;
queryThreadLock();
try {
mWindow.clear();
} finally {
queryThreadUnlock();
}
}
mWindow.setStartPosition(startPos);
mCount = mQuery.fillWindow(mWindow, mInitialRead, 0);
// return -1 means not finished
if (mCount == NO_COUNT){
mCount = startPos + mInitialRead;
Thread t = new Thread(new QueryThread(mCursorState), "query thread");
t.start();
}
}
@Override
public int getColumnIndex(String columnName) {
// Create mColumnNameMap on demand
if (mColumnNameMap == null) {
String[] columns = mColumns;
int columnCount = columns.length;
HashMap<String, Integer> map = new HashMap<String, Integer>(columnCount, 1);
for (int i = 0; i < columnCount; i++) {
map.put(columns[i], i);
}
mColumnNameMap = map;
}
// Hack according to bug 903852
final int periodIndex = columnName.lastIndexOf('.');
if (periodIndex != -1) {
Exception e = new Exception();
Log.e(TAG, "requesting column name with table name -- " + columnName, e);
columnName = columnName.substring(periodIndex + 1);
}
Integer i = mColumnNameMap.get(columnName);
if (i != null) {
return i.intValue();
} else {
return -1;
}
}
/**
* @hide
* @deprecated
*/
@Override
public boolean deleteRow() {
checkPosition();
// Only allow deletes if there is an ID column, and the ID has been read from it
if (mRowIdColumnIndex == -1 || mCurrentRowID == null) {
Log.e(TAG,
"Could not delete row because either the row ID column is not available or it" +
"has not been read.");
return false;
}
boolean success;
/*
* Ensure we don't change the state of the database when another
* thread is holding the database lock. requery() and moveTo() are also
* synchronized here to make sure they get the state of the database
* immediately following the DELETE.
*/
mDatabase.lock();
try {
try {
mDatabase.delete(mEditTable, mColumns[mRowIdColumnIndex] + "=?",
new String[] {mCurrentRowID.toString()});
success = true;
} catch (SQLException e) {
success = false;
}
int pos = mPos;
requery();
/*
* Ensure proper cursor state. Note that mCurrentRowID changes
* in this call.
*/
moveToPosition(pos);
} finally {
mDatabase.unlock();
}
if (success) {
onChange(true);
return true;
} else {
return false;
}
}
@Override
public String[] getColumnNames() {
return mColumns;
}
/**
* @hide
* @deprecated
*/
@Override
public boolean supportsUpdates() {
return super.supportsUpdates() && !TextUtils.isEmpty(mEditTable);
}
/**
* @hide
* @deprecated
*/
@Override
public boolean commitUpdates(Map<? extends Long,
? extends Map<String, Object>> additionalValues) {
if (!supportsUpdates()) {
Log.e(TAG, "commitUpdates not supported on this cursor, did you "
+ "include the _id column?");
return false;
}
/*
* Prevent other threads from changing the updated rows while they're
* being processed here.
*/
synchronized (mUpdatedRows) {
if (additionalValues != null) {
mUpdatedRows.putAll(additionalValues);
}
if (mUpdatedRows.size() == 0) {
return true;
}
/*
* Prevent other threads from changing the database state while
* we process the updated rows, and prevents us from changing the
* database behind the back of another thread.
*/
mDatabase.beginTransaction();
try {
StringBuilder sql = new StringBuilder(128);
// For each row that has been updated
for (Map.Entry<Long, Map<String, Object>> rowEntry :
mUpdatedRows.entrySet()) {
Map<String, Object> values = rowEntry.getValue();
Long rowIdObj = rowEntry.getKey();
if (rowIdObj == null || values == null) {
throw new IllegalStateException("null rowId or values found! rowId = "
+ rowIdObj + ", values = " + values);
}
if (values.size() == 0) {
continue;
}
long rowId = rowIdObj.longValue();
Iterator<Map.Entry<String, Object>> valuesIter =
values.entrySet().iterator();
sql.setLength(0);
sql.append("UPDATE " + mEditTable + " SET ");
// For each column value that has been updated
Object[] bindings = new Object[values.size()];
int i = 0;
while (valuesIter.hasNext()) {
Map.Entry<String, Object> entry = valuesIter.next();
sql.append(entry.getKey());
sql.append("=?");
bindings[i] = entry.getValue();
if (valuesIter.hasNext()) {
sql.append(", ");
}
i++;
}
sql.append(" WHERE " + mColumns[mRowIdColumnIndex]
+ '=' + rowId);
sql.append(';');
mDatabase.execSQL(sql.toString(), bindings);
mDatabase.rowUpdated(mEditTable, rowId);
}
mDatabase.setTransactionSuccessful();
} finally {
mDatabase.endTransaction();
}
mUpdatedRows.clear();
}
// Let any change observers know about the update
onChange(true);
return true;
}
private void deactivateCommon() {
if (Config.LOGV) Log.v(TAG, "<<< Releasing cursor " + this);
mCursorState = 0;
if (mWindow != null) {
mWindow.close();
mWindow = null;
}
if (Config.LOGV) Log.v("DatabaseWindow", "closing window in release()");
}
@Override
public void deactivate() {
super.deactivate();
deactivateCommon();
mDriver.cursorDeactivated();
}
@Override
public void close() {
super.close();
deactivateCommon();
mQuery.close();
mDriver.cursorClosed();
}
@Override
public boolean requery() {
if (isClosed()) {
return false;
}
long timeStart = 0;
if (Config.LOGV) {
timeStart = System.currentTimeMillis();
}
/*
* Synchronize on the database lock to ensure that mCount matches the
* results of mQuery.requery().
*/
mDatabase.lock();
try {
if (mWindow != null) {
mWindow.clear();
}
mPos = -1;
// This one will recreate the temp table, and get its count
mDriver.cursorRequeried(this);
mCount = NO_COUNT;
mCursorState++;
queryThreadLock();
try {
mQuery.requery();
} finally {
queryThreadUnlock();
}
} finally {
mDatabase.unlock();
}
if (Config.LOGV) {
Log.v("DatabaseWindow", "closing window in requery()");
Log.v(TAG, "--- Requery()ed cursor " + this + ": " + mQuery);
}
boolean result = super.requery();
if (Config.LOGV) {
long timeEnd = System.currentTimeMillis();
Log.v(TAG, "requery (" + (timeEnd - timeStart) + " ms): " + mDriver.toString());
}
return result;
}
@Override
public void setWindow(CursorWindow window) {
if (mWindow != null) {
mCursorState++;
queryThreadLock();
try {
mWindow.close();
} finally {
queryThreadUnlock();
}
mCount = NO_COUNT;
}
mWindow = window;
}
/**
* Changes the selection arguments. The new values take effect after a call to requery().
*/
public void setSelectionArguments(String[] selectionArgs) {
mDriver.setBindArguments(selectionArgs);
}
/**
* Release the native resources, if they haven't been released yet.
*/
@Override
protected void finalize() {
try {
// if the cursor hasn't been closed yet, close it first
if (mWindow != null) {
if (StrictMode.vmSqliteObjectLeaksEnabled()) {
int len = mQuery.mSql.length();
StrictMode.onSqliteObjectLeaked(
"Finalizing a Cursor that has not been deactivated or closed. " +
"database = " + mDatabase.getPath() + ", table = " + mEditTable +
", query = " + mQuery.mSql.substring(0, (len > 100) ? 100 : len),
mStackTrace);
}
close();
SQLiteDebug.notifyActiveCursorFinalized();
} else {
if (Config.LOGV) {
Log.v(TAG, "Finalizing cursor on database = " + mDatabase.getPath() +
", table = " + mEditTable + ", query = " + mQuery.mSql);
}
}
} finally {
super.finalize();
}
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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.
*/
package android.database.sqlite;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
/**
* A driver for SQLiteCursors that is used to create them and gets notified
* by the cursors it creates on significant events in their lifetimes.
*/
public interface SQLiteCursorDriver {
/**
* Executes the query returning a Cursor over the result set.
*
* @param factory The CursorFactory to use when creating the Cursors, or
* null if standard SQLiteCursors should be returned.
* @return a Cursor over the result set
*/
Cursor query(CursorFactory factory, String[] bindArgs);
/**
* Called by a SQLiteCursor when it is released.
*/
void cursorDeactivated();
/**
* Called by a SQLiteCursor when it is requeryed.
*
* @return The new count value.
*/
void cursorRequeried(Cursor cursor);
/**
* Called by a SQLiteCursor when it it closed to destroy this object as well.
*/
void cursorClosed();
/**
* Set new bind arguments. These will take effect in cursorRequeried().
* @param bindArgs the new arguments
*/
public void setBindArguments(String[] bindArgs);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
/*
* 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;
/**
* An exception that indicates that the SQLite database file is corrupt.
*/
public class SQLiteDatabaseCorruptException extends SQLiteException {
public SQLiteDatabaseCorruptException() {}
public SQLiteDatabaseCorruptException(String error) {
super(error);
}
}

View File

@@ -0,0 +1,197 @@
/*
* 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.
*/
package android.database.sqlite;
import java.util.ArrayList;
import android.util.Log;
/**
* Provides debugging info about all SQLite databases running in the current process.
*
* {@hide}
*/
public final class SQLiteDebug {
/**
* Controls the printing of SQL statements as they are executed.
*/
public static final boolean DEBUG_SQL_STATEMENTS =
Log.isLoggable("SQLiteStatements", Log.VERBOSE);
/**
* Controls the printing of wall-clock time taken to execute SQL statements
* as they are executed.
*/
public static final boolean DEBUG_SQL_TIME =
Log.isLoggable("SQLiteTime", Log.VERBOSE);
/**
* Controls the printing of compiled-sql-statement cache stats.
*/
public static final boolean DEBUG_SQL_CACHE =
Log.isLoggable("SQLiteCompiledSql", Log.VERBOSE);
/**
* Controls the stack trace reporting of active cursors being
* finalized.
*/
public static final boolean DEBUG_ACTIVE_CURSOR_FINALIZATION =
Log.isLoggable("SQLiteCursorClosing", Log.VERBOSE);
/**
* Controls the tracking of time spent holding the database lock.
*/
public static final boolean DEBUG_LOCK_TIME_TRACKING =
Log.isLoggable("SQLiteLockTime", Log.VERBOSE);
/**
* Controls the printing of stack traces when tracking the time spent holding the database lock.
*/
public static final boolean DEBUG_LOCK_TIME_TRACKING_STACK_TRACE =
Log.isLoggable("SQLiteLockStackTrace", Log.VERBOSE);
/**
* Contains statistics about the active pagers in the current process.
*
* @see #getPagerStats(PagerStats)
*/
public static class PagerStats {
/** The total number of bytes in all pagers in the current process
* @deprecated not used any longer
*/
@Deprecated
public long totalBytes;
/** The number of bytes in referenced pages in all pagers in the current process
* @deprecated not used any longer
* */
@Deprecated
public long referencedBytes;
/** The number of bytes in all database files opened in the current process
* @deprecated not used any longer
*/
@Deprecated
public long databaseBytes;
/** The number of pagers opened in the current process
* @deprecated not used any longer
*/
@Deprecated
public int numPagers;
/** the current amount of memory checked out by sqlite using sqlite3_malloc().
* documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
*/
public int memoryUsed;
/** the number of bytes of page cache allocation which could not be sattisfied by the
* SQLITE_CONFIG_PAGECACHE buffer and where forced to overflow to sqlite3_malloc().
* The returned value includes allocations that overflowed because they where too large
* (they were larger than the "sz" parameter to SQLITE_CONFIG_PAGECACHE) and allocations
* that overflowed because no space was left in the page cache.
* documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
*/
public int pageCacheOverflo;
/** records the largest memory allocation request handed to sqlite3.
* documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
*/
public int largestMemAlloc;
/** a list of {@link DbStats} - one for each main database opened by the applications
* running on the android device
*/
public ArrayList<DbStats> dbStats;
}
/**
* contains statistics about a database
*/
public static class DbStats {
/** name of the database */
public String dbName;
/** the page size for the database */
public long pageSize;
/** the database size */
public long dbSize;
/** documented here http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html */
public int lookaside;
public DbStats(String dbName, long pageCount, long pageSize, int lookaside) {
this.dbName = dbName;
this.pageSize = pageSize / 1024;
dbSize = (pageCount * pageSize) / 1024;
this.lookaside = lookaside;
}
}
/**
* return all pager and database stats for the current process.
* @return {@link PagerStats}
*/
public static PagerStats getDatabaseInfo() {
PagerStats stats = new PagerStats();
getPagerStats(stats);
stats.dbStats = SQLiteDatabase.getDbStats();
return stats;
}
/**
* Gathers statistics about all pagers in the current process.
*/
public static native void getPagerStats(PagerStats stats);
/**
* Returns the size of the SQLite heap.
* @return The size of the SQLite heap in bytes.
*/
public static native long getHeapSize();
/**
* Returns the amount of allocated memory in the SQLite heap.
* @return The allocated size in bytes.
*/
public static native long getHeapAllocatedSize();
/**
* Returns the amount of free memory in the SQLite heap.
* @return The freed size in bytes.
*/
public static native long getHeapFreeSize();
/**
* Determines the number of dirty belonging to the SQLite
* heap segments of this process. pages[0] returns the number of
* shared pages, pages[1] returns the number of private pages
*/
public static native void getHeapDirtyPages(int[] pages);
private static int sNumActiveCursorsFinalized = 0;
/**
* Returns the number of active cursors that have been finalized. This depends on the GC having
* run but is still useful for tests.
*/
public static int getNumActiveCursorsFinalized() {
return sNumActiveCursorsFinalized;
}
static synchronized void notifyActiveCursorFinalized() {
sNumActiveCursorsFinalized++;
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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.
*/
package android.database.sqlite;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
/**
* A cursor driver that uses the given query directly.
*
* @hide
*/
public class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
private String mEditTable;
private SQLiteDatabase mDatabase;
private Cursor mCursor;
private String mSql;
private SQLiteQuery mQuery;
public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable) {
mDatabase = db;
mEditTable = editTable;
mSql = sql;
}
public Cursor query(CursorFactory factory, String[] selectionArgs) {
// Compile the query
SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs);
try {
// Arg binding
int numArgs = selectionArgs == null ? 0 : selectionArgs.length;
for (int i = 0; i < numArgs; i++) {
query.bindString(i + 1, selectionArgs[i]);
}
// Create the cursor
if (factory == null) {
mCursor = new SQLiteCursor(mDatabase, this, mEditTable, query);
} else {
mCursor = 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();
}
}
public void cursorClosed() {
mCursor = null;
}
public void setBindArguments(String[] bindArgs) {
final int numArgs = bindArgs.length;
for (int i = 0; i < numArgs; i++) {
mQuery.bindString(i + 1, bindArgs[i]);
}
}
public void cursorDeactivated() {
// Do nothing
}
public void cursorRequeried(Cursor cursor) {
// Do nothing
}
@Override
public String toString() {
return "SQLiteDirectCursorDriver: " + mSql;
}
}

View File

@@ -0,0 +1,29 @@
/*
* 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;
/**
* An exception that indicates that an IO error occured while accessing the
* SQLite database file.
*/
public class SQLiteDiskIOException extends SQLiteException {
public SQLiteDiskIOException() {}
public SQLiteDiskIOException(String error) {
super(error);
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 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.
*/
package android.database.sqlite;
/**
* An exception that indicates that the SQLite program is done.
* Thrown when an operation that expects a row (such as {@link
* SQLiteStatement#simpleQueryForString} or {@link
* SQLiteStatement#simpleQueryForLong}) does not get one.
*/
public class SQLiteDoneException extends SQLiteException {
public SQLiteDoneException() {}
public SQLiteDoneException(String error) {
super(error);
}
}

View File

@@ -0,0 +1,30 @@
/*
* 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.database.SQLException;
/**
* A SQLite exception that indicates there was an error with SQL parsing or execution.
*/
public class SQLiteException extends SQLException {
public SQLiteException() {}
public SQLiteException(String error) {
super(error);
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 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.
*/
package android.database.sqlite;
/**
* An exception that indicates that the SQLite database is full.
*/
public class SQLiteFullException extends SQLiteException {
public SQLiteFullException() {}
public SQLiteFullException(String error) {
super(error);
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 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.
*/
package android.database.sqlite;
public class SQLiteMisuseException extends SQLiteException {
public SQLiteMisuseException() {}
public SQLiteMisuseException(String error) {
super(error);
}
}

View File

@@ -0,0 +1,258 @@
/*
* 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.
*/
package android.database.sqlite;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.util.Log;
/**
* A helper class to manage database creation and version management.
*
* <p>You create a subclass implementing {@link #onCreate}, {@link #onUpgrade} and
* optionally {@link #onOpen}, and this class takes care of opening the database
* if it exists, creating it if it does not, and upgrading it as necessary.
* Transactions are used to make sure the database is always in a sensible state.
*
* <p>This class makes it easy for {@link android.content.ContentProvider}
* implementations to defer opening and upgrading the database until first use,
* to avoid blocking application startup with long-running database upgrades.
*
* <p>For an example, see the NotePadProvider class in the NotePad sample application,
* in the <em>samples/</em> directory of the SDK.</p>
*
* <p class="note"><strong>Note:</strong> this class assumes
* monotonically increasing version numbers for upgrades. Also, there
* is no concept of a database downgrade; installing a new version of
* your app which uses a lower version number than a
* previously-installed version will result in undefined behavior.</p>
*/
public abstract class SQLiteOpenHelper {
private static final String TAG = SQLiteOpenHelper.class.getSimpleName();
private final Context mContext;
private final String mName;
private final CursorFactory mFactory;
private final int mNewVersion;
private SQLiteDatabase mDatabase = null;
private boolean mIsInitializing = false;
/**
* Create a helper object to create, open, and/or manage a database.
* This method always returns very quickly. The database is not actually
* created or opened until one of {@link #getWritableDatabase} or
* {@link #getReadableDatabase} is called.
*
* @param context to use to open or create the database
* @param name of the database file, or null for an in-memory database
* @param factory to use for creating cursor objects, or null for the default
* @param version number of the database (starting at 1); if the database is older,
* {@link #onUpgrade} will be used to upgrade the database
*/
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
mContext = context;
mName = name;
mFactory = factory;
mNewVersion = version;
}
/**
* Create and/or open a database that will be used for reading and writing.
* The first time this is called, the database will be opened and
* {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be
* called.
*
* <p>Once opened successfully, the database is cached, so you can
* call this method every time you need to write to the database.
* (Make sure to call {@link #close} when you no longer need the database.)
* Errors such as bad permissions or a full disk may cause this method
* to fail, but future attempts may succeed if the problem is fixed.</p>
*
* <p class="caution">Database upgrade may take a long time, you
* should not call this method from the application main thread, including
* from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
*
* @throws SQLiteException if the database cannot be opened for writing
* @return a read/write database object valid until {@link #close} is called
*/
public synchronized SQLiteDatabase getWritableDatabase() {
if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
return mDatabase; // The database is already open for business
}
if (mIsInitializing) {
throw new IllegalStateException("getWritableDatabase called recursively");
}
// 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.
boolean success = false;
SQLiteDatabase db = null;
if (mDatabase != null) mDatabase.lock();
try {
mIsInitializing = true;
if (mName == null) {
db = SQLiteDatabase.create(null);
} else {
db = mContext.openOrCreateDatabase(mName, 0, mFactory);
}
int version = db.getVersion();
if (version != mNewVersion) {
db.beginTransaction();
try {
if (version == 0) {
onCreate(db);
} else {
if (version > mNewVersion) {
Log.wtf(TAG, "Can't downgrade read-only database from version " +
version + " to " + mNewVersion + ": " + db.getPath());
}
onUpgrade(db, version, mNewVersion);
}
db.setVersion(mNewVersion);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
onOpen(db);
success = true;
return db;
} finally {
mIsInitializing = false;
if (success) {
if (mDatabase != null) {
try { mDatabase.close(); } catch (Exception e) { }
mDatabase.unlock();
}
mDatabase = db;
} else {
if (mDatabase != null) mDatabase.unlock();
if (db != null) db.close();
}
}
}
/**
* Create and/or open a database. This will be the same object returned by
* {@link #getWritableDatabase} unless some problem, such as a full disk,
* requires the database to be opened read-only. In that case, a read-only
* database object will be returned. If the problem is fixed, a future call
* to {@link #getWritableDatabase} may succeed, in which case the read-only
* database object will be closed and the read/write object will be returned
* in the future.
*
* <p class="caution">Like {@link #getWritableDatabase}, this method may
* take a long time to return, so you should not call it from the
* application main thread, including from
* {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
*
* @throws SQLiteException if the database cannot be opened
* @return a database object valid until {@link #getWritableDatabase}
* or {@link #close} is called.
*/
public synchronized SQLiteDatabase getReadableDatabase() {
if (mDatabase != null && mDatabase.isOpen()) {
return mDatabase; // The database is already open for business
}
if (mIsInitializing) {
throw new IllegalStateException("getReadableDatabase called recursively");
}
try {
return getWritableDatabase();
} catch (SQLiteException e) {
if (mName == null) throw e; // Can't open a temp database read-only!
Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e);
}
SQLiteDatabase db = null;
try {
mIsInitializing = true;
String path = mContext.getDatabasePath(mName).getPath();
db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);
if (db.getVersion() != mNewVersion) {
throw new SQLiteException("Can't upgrade read-only database from version " +
db.getVersion() + " to " + mNewVersion + ": " + path);
}
onOpen(db);
Log.w(TAG, "Opened " + mName + " in read-only mode");
mDatabase = db;
return mDatabase;
} finally {
mIsInitializing = false;
if (db != null && db != mDatabase) db.close();
}
}
/**
* Close any open database object.
*/
public synchronized void close() {
if (mIsInitializing) throw new IllegalStateException("Closed during initialization");
if (mDatabase != null && mDatabase.isOpen()) {
mDatabase.close();
mDatabase = null;
}
}
/**
* Called when the database is created for the first time. This is where the
* creation of tables and the initial population of the tables should happen.
*
* @param db The database.
*/
public abstract void onCreate(SQLiteDatabase db);
/**
* Called when the database needs to be upgraded. The implementation
* should use this method to drop tables, add tables, or do anything else it
* needs to upgrade to the new schema version.
*
* <p>The SQLite ALTER TABLE documentation can be found
* <a href="http://sqlite.org/lang_altertable.html">here</a>. If you add new columns
* you can use ALTER TABLE to insert them into a live table. If you rename or remove columns
* you can use ALTER TABLE to rename the old table, then create the new table and then
* populate the new table with the contents of the old table.
*
* @param db The database.
* @param oldVersion The old database version.
* @param newVersion The new database version.
*/
public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
/**
* Called when the database has been opened. The implementation
* should check {@link SQLiteDatabase#isReadOnly} before updating the
* database.
*
* @param db The database.
*/
public void onOpen(SQLiteDatabase db) {}
}

View File

@@ -0,0 +1,325 @@
/*
* 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.util.Log;
/**
* 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.
*/
public abstract class SQLiteProgram extends SQLiteClosable {
private static final String TAG = "SQLiteProgram";
/** The database this program is compiled against.
* @deprecated do not use this
*/
@Deprecated
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.
* @deprecated do not use this
*/
@Deprecated
protected int nHandle = 0;
/**
* the SQLiteCompiledSql object for the given sql statement.
*/
private SQLiteCompiledSql mCompiledSql;
/**
* SQLiteCompiledSql statement id is populated with the corresponding object from the above
* member. This member is used by the native_bind_* methods
* @deprecated do not use this
*/
@Deprecated
protected int nStatement = 0;
/* package */ SQLiteProgram(SQLiteDatabase db, String sql) {
mDatabase = db;
mSql = sql.trim();
db.acquireReference();
db.addSQLiteClosable(this);
this.nHandle = db.mNativeHandle;
// only cache CRUD statements
String prefixSql = mSql.substring(0, 6);
if (!prefixSql.equalsIgnoreCase("INSERT") && !prefixSql.equalsIgnoreCase("UPDATE") &&
!prefixSql.equalsIgnoreCase("REPLAC") &&
!prefixSql.equalsIgnoreCase("DELETE") && !prefixSql.equalsIgnoreCase("SELECT")) {
mCompiledSql = new SQLiteCompiledSql(db, sql);
nStatement = mCompiledSql.nStatement;
// since it is not in the cache, no need to acquire() it.
return;
}
// it is not pragma
mCompiledSql = db.getCompiledStatementForSql(sql);
if (mCompiledSql == null) {
// create a new compiled-sql obj
mCompiledSql = new SQLiteCompiledSql(db, sql);
// 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();
db.addToCompiledQueries(sql, mCompiledSql);
if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
Log.v(TAG, "Created DbObj (id#" + mCompiledSql.nStatement +
") for sql: " + sql);
}
} 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(db, sql);
if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
Log.v(TAG, "** possible bug ** Created NEW DbObj (id#" +
mCompiledSql.nStatement +
") because the previously created DbObj (id#" + last +
") was not released for sql:" + sql);
}
// since it is not in the cache, no need to acquire() it.
}
}
nStatement = mCompiledSql.nStatement;
}
@Override
protected void onAllReferencesReleased() {
releaseCompiledSqlIfNotInCache();
mDatabase.releaseReference();
mDatabase.removeSQLiteClosable(this);
}
@Override
protected void onAllReferencesReleasedFromContainer() {
releaseCompiledSqlIfNotInCache();
mDatabase.releaseReference();
}
private void releaseCompiledSqlIfNotInCache() {
if (mCompiledSql == null) {
return;
}
synchronized(mDatabase.mCompiledQueries) {
if (!mDatabase.mCompiledQueries.containsValue(mCompiledSql)) {
// it is NOT in compiled-sql cache. i.e., responsibility of
// releasing this statement is on me.
mCompiledSql.releaseSqlStatement();
mCompiledSql = null;
nStatement = 0;
} else {
// it is in compiled-sql cache. reset its CompiledSql#mInUse flag
mCompiledSql.release();
}
}
}
/**
* Returns a unique identifier for this program.
*
* @return a unique identifier for this program
*/
public final int getUniqueId() {
return nStatement;
}
/* package */ String getSqlString() {
return mSql;
}
/**
* @deprecated This method is deprecated and must not be used.
*
* @param sql the SQL string to compile
* @param forceCompilation forces the SQL to be recompiled in the event that there is an
* existing compiled SQL program already around
*/
@Deprecated
protected void compile(String sql, boolean forceCompilation) {
// TODO is there a need for this?
}
/**
* Bind a NULL value to this statement. The value remains bound until
* {@link #clearBindings} is called.
*
* @param index The 1-based index to the parameter to bind null to
*/
public void bindNull(int index) {
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
acquireReference();
try {
native_bind_null(index);
} finally {
releaseReference();
}
}
/**
* Bind a long value to this statement. The value remains bound until
* {@link #clearBindings} is called.
*
* @param index The 1-based index to the parameter to bind
* @param value The value to bind
*/
public void bindLong(int index, long value) {
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
acquireReference();
try {
native_bind_long(index, value);
} finally {
releaseReference();
}
}
/**
* Bind a double value to this statement. The value remains bound until
* {@link #clearBindings} is called.
*
* @param index The 1-based index to the parameter to bind
* @param value The value to bind
*/
public void bindDouble(int index, double value) {
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
acquireReference();
try {
native_bind_double(index, value);
} finally {
releaseReference();
}
}
/**
* Bind a String value to this statement. The value remains bound until
* {@link #clearBindings} is called.
*
* @param index The 1-based index to the parameter to bind
* @param value The value to bind
*/
public void bindString(int index, String value) {
if (value == null) {
throw new IllegalArgumentException("the bind value at index " + index + " is null");
}
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
acquireReference();
try {
native_bind_string(index, value);
} finally {
releaseReference();
}
}
/**
* Bind a byte array value to this statement. The value remains bound until
* {@link #clearBindings} is called.
*
* @param index The 1-based index to the parameter to bind
* @param value The value to bind
*/
public void bindBlob(int index, byte[] value) {
if (value == null) {
throw new IllegalArgumentException("the bind value at index " + index + " is null");
}
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
acquireReference();
try {
native_bind_blob(index, value);
} finally {
releaseReference();
}
}
/**
* Clears all existing bindings. Unset bindings are treated as NULL.
*/
public void clearBindings() {
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
acquireReference();
try {
native_clear_bindings();
} finally {
releaseReference();
}
}
/**
* Release this program's resources, making it invalid.
*/
public void close() {
if (!mDatabase.isOpen()) {
return;
}
mDatabase.lock();
try {
releaseReference();
} finally {
mDatabase.unlock();
}
}
/**
* @deprecated This method is deprecated and must not be used.
* Compiles SQL into a SQLite program.
*
* <P>The database lock must be held when calling this method.
* @param sql The SQL to compile.
*/
@Deprecated
protected final native void native_compile(String sql);
/**
* @deprecated This method is deprecated and must not be used.
*/
@Deprecated
protected final native void native_finalize();
protected final native void native_bind_null(int index);
protected final native void native_bind_long(int index, long value);
protected final native void native_bind_double(int index, double value);
protected final native void native_bind_string(int index, String value);
protected final native void native_bind_blob(int index, byte[] value);
private final native void native_clear_bindings();
}

View File

@@ -0,0 +1,194 @@
/*
* 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.database.CursorWindow;
import android.os.SystemClock;
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.
*/
public class SQLiteQuery extends SQLiteProgram {
private static final String TAG = "Cursor";
/** The index of the unbound OFFSET parameter */
private int mOffsetIndex;
/** Args to bind on requery */
private String[] mBindArgs;
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;
mBindArgs = bindArgs;
}
/**
* Reads rows into a buffer. This method acquires the database lock.
*
* @param window The window to fill into
* @return number of total rows in the query
*/
/* package */ int fillWindow(CursorWindow window,
int maxRead, int lastPos) {
long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
mDatabase.logTimeStat(mSql, timeStart, SQLiteDatabase.GET_LOCK_LOG_PREFIX);
try {
acquireReference();
try {
window.acquireReference();
// if the start pos is not equal to 0, then most likely window is
// too small for the data set, loading by another thread
// is not safe in this situation. the native code will ignore maxRead
int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex,
maxRead, lastPos);
// Logging
if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
Log.d(TAG, "fillWindow(): " + mSql);
}
mDatabase.logTimeStat(mSql, timeStart);
return numRows;
} catch (IllegalStateException e){
// simply ignore it
return 0;
} catch (SQLiteDatabaseCorruptException e) {
mDatabase.onCorruption();
throw e;
} 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 native_column_count();
} 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 native_column_name(columnIndex);
} finally {
releaseReference();
}
}
@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 (mBindArgs != null) {
int len = mBindArgs.length;
try {
for (int i = 0; i < len; i++) {
super.bindString(i + 1, mBindArgs[i]);
}
} catch (SQLiteMisuseException e) {
StringBuilder errMsg = new StringBuilder("mSql " + mSql);
for (int i = 0; i < len; i++) {
errMsg.append(" ");
errMsg.append(mBindArgs[i]);
}
errMsg.append(" ");
IllegalStateException leakProgram = new IllegalStateException(
errMsg.toString(), e);
throw leakProgram;
}
}
}
@Override
public void bindNull(int index) {
mBindArgs[index - 1] = null;
if (!mClosed) super.bindNull(index);
}
@Override
public void bindLong(int index, long value) {
mBindArgs[index - 1] = Long.toString(value);
if (!mClosed) super.bindLong(index, value);
}
@Override
public void bindDouble(int index, double value) {
mBindArgs[index - 1] = Double.toString(value);
if (!mClosed) super.bindDouble(index, value);
}
@Override
public void bindString(int index, String value) {
mBindArgs[index - 1] = value;
if (!mClosed) super.bindString(index, value);
}
private final native int native_fill_window(CursorWindow window,
int startPos, int offsetParam, int maxRead, int lastPos);
private final native int native_column_count();
private final native String native_column_name(int columnIndex);
}

View File

@@ -0,0 +1,550 @@
/*
* 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.database.Cursor;
import android.database.DatabaseUtils;
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.regex.Pattern;
/**
* This is a convience class that helps build SQL queries to be sent to
* {@link SQLiteDatabase} objects.
*/
public class SQLiteQueryBuilder
{
private static final String TAG = "SQLiteQueryBuilder";
private static final Pattern sLimitPattern =
Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?");
private Map<String, String> mProjectionMap = null;
private String mTables = "";
private StringBuilder mWhereClause = null; // lazily created
private boolean mDistinct;
private SQLiteDatabase.CursorFactory mFactory;
private boolean mStrictProjectionMap;
public SQLiteQueryBuilder() {
mDistinct = false;
mFactory = null;
}
/**
* Mark the query as DISTINCT.
*
* @param distinct if true the query is DISTINCT, otherwise it isn't
*/
public void setDistinct(boolean distinct) {
mDistinct = distinct;
}
/**
* Returns the list of tables being queried
*
* @return the list of tables being queried
*/
public String getTables() {
return mTables;
}
/**
* Sets the list of tables to query. Multiple tables can be specified to perform a join.
* For example:
* setTables("foo, bar")
* setTables("foo LEFT OUTER JOIN bar ON (foo.id = bar.foo_id)")
*
* @param inTables the list of tables to query on
*/
public void setTables(String inTables) {
mTables = inTables;
}
/**
* Append a chunk to the WHERE clause of the query. All chunks appended are surrounded
* by parenthesis and ANDed with the selection passed to {@link #query}. The final
* WHERE clause looks like:
*
* WHERE (&lt;append chunk 1>&lt;append chunk2>) AND (&lt;query() selection parameter>)
*
* @param inWhere the chunk of text to append to the WHERE clause.
*/
public void appendWhere(CharSequence inWhere) {
if (mWhereClause == null) {
mWhereClause = new StringBuilder(inWhere.length() + 16);
}
if (mWhereClause.length() == 0) {
mWhereClause.append('(');
}
mWhereClause.append(inWhere);
}
/**
* Append a chunk to the WHERE clause of the query. All chunks appended are surrounded
* by parenthesis and ANDed with the selection passed to {@link #query}. The final
* WHERE clause looks like:
*
* WHERE (&lt;append chunk 1>&lt;append chunk2>) AND (&lt;query() selection parameter>)
*
* @param inWhere the chunk of text to append to the WHERE clause. it will be escaped
* to avoid SQL injection attacks
*/
public void appendWhereEscapeString(String inWhere) {
if (mWhereClause == null) {
mWhereClause = new StringBuilder(inWhere.length() + 16);
}
if (mWhereClause.length() == 0) {
mWhereClause.append('(');
}
DatabaseUtils.appendEscapedSQLString(mWhereClause, inWhere);
}
/**
* Sets the projection map for the query. The projection map maps
* from column names that the caller passes into query to database
* column names. This is useful for renaming columns as well as
* disambiguating column names when doing joins. For example you
* could map "name" to "people.name". If a projection map is set
* it must contain all column names the user may request, even if
* the key and value are the same.
*
* @param columnMap maps from the user column names to the database column names
*/
public void setProjectionMap(Map<String, String> columnMap) {
mProjectionMap = columnMap;
}
/**
* Sets the cursor factory to be used for the query. You can use
* one factory for all queries on a database but it is normally
* easier to specify the factory when doing this query. @param
* factory the factor to use
*/
public void setCursorFactory(SQLiteDatabase.CursorFactory factory) {
mFactory = factory;
}
/**
* @hide
*/
public void setStrictProjectionMap(boolean flag) {
mStrictProjectionMap = flag;
}
/**
* Build an SQL query string from the given clauses.
*
* @param distinct true if you want each row to be unique, false otherwise.
* @param tables The table names to compile the query against.
* @param columns A list of which columns to return. Passing null will
* return all columns, which is discouraged to prevent reading
* data from storage that isn't going to be used.
* @param where A filter declaring which rows to return, formatted as an SQL
* WHERE clause (excluding the WHERE itself). Passing null will
* return all rows for the given URL.
* @param groupBy A filter declaring how to group rows, formatted as an SQL
* GROUP BY clause (excluding the GROUP BY itself). Passing null
* will cause the rows to not be grouped.
* @param having A filter declare which row groups to include in the cursor,
* if row grouping is being used, formatted as an SQL HAVING
* clause (excluding the HAVING itself). Passing null will cause
* all row groups to be included, and is required when row
* grouping is not being used.
* @param orderBy How to order the rows, formatted as an SQL ORDER BY clause
* (excluding the ORDER BY itself). Passing null will use the
* default sort order, which may be unordered.
* @param limit Limits the number of rows returned by the query,
* formatted as LIMIT clause. Passing null denotes no LIMIT clause.
* @return the SQL query string
*/
public static String buildQueryString(
boolean distinct, String tables, String[] columns, String where,
String groupBy, String having, String orderBy, String limit) {
if (TextUtils.isEmpty(groupBy) && !TextUtils.isEmpty(having)) {
throw new IllegalArgumentException(
"HAVING clauses are only permitted when using a groupBy clause");
}
if (!TextUtils.isEmpty(limit) && !sLimitPattern.matcher(limit).matches()) {
throw new IllegalArgumentException("invalid LIMIT clauses:" + limit);
}
StringBuilder query = new StringBuilder(120);
query.append("SELECT ");
if (distinct) {
query.append("DISTINCT ");
}
if (columns != null && columns.length != 0) {
appendColumns(query, columns);
} else {
query.append("* ");
}
query.append("FROM ");
query.append(tables);
appendClause(query, " WHERE ", where);
appendClause(query, " GROUP BY ", groupBy);
appendClause(query, " HAVING ", having);
appendClause(query, " ORDER BY ", orderBy);
appendClause(query, " LIMIT ", limit);
return query.toString();
}
private static void appendClause(StringBuilder s, String name, String clause) {
if (!TextUtils.isEmpty(clause)) {
s.append(name);
s.append(clause);
}
}
private static void appendClauseEscapeClause(StringBuilder s, String name, String clause) {
if (!TextUtils.isEmpty(clause)) {
s.append(name);
DatabaseUtils.appendEscapedSQLString(s, clause);
}
}
/**
* Add the names that are non-null in columns to s, separating
* them with commas.
*/
public static void appendColumns(StringBuilder s, String[] columns) {
int n = columns.length;
for (int i = 0; i < n; i++) {
String column = columns[i];
if (column != null) {
if (i > 0) {
s.append(", ");
}
s.append(column);
}
}
s.append(' ');
}
/**
* Perform a query by combining all current settings and the
* information passed into this method.
*
* @param db the database to query on
* @param projectionIn A list of which columns to return. Passing
* null will return all columns, which is discouraged to prevent
* reading data from storage that isn't going to be used.
* @param selection A filter declaring which rows to return,
* formatted as an SQL WHERE clause (excluding the WHERE
* itself). Passing null will return all rows for the given URL.
* @param selectionArgs You may include ?s in selection, which
* will be replaced by the values from selectionArgs, in order
* that they appear in the selection. The values will be bound
* as Strings.
* @param groupBy A filter declaring how to group rows, formatted
* as an SQL GROUP BY clause (excluding the GROUP BY
* itself). Passing null will cause the rows to not be grouped.
* @param having A filter declare which row groups to include in
* the cursor, if row grouping is being used, formatted as an
* SQL HAVING clause (excluding the HAVING itself). Passing
* null will cause all row groups to be included, and is
* required when row grouping is not being used.
* @param sortOrder How to order the rows, formatted as an SQL
* ORDER BY clause (excluding the ORDER BY itself). Passing null
* will use the default sort order, which may be unordered.
* @return a cursor over the result set
* @see android.content.ContentResolver#query(android.net.Uri, String[],
* String, String[], String)
*/
public Cursor query(SQLiteDatabase db, String[] projectionIn,
String selection, String[] selectionArgs, String groupBy,
String having, String sortOrder) {
return query(db, projectionIn, selection, selectionArgs, groupBy, having, sortOrder,
null /* limit */);
}
/**
* Perform a query by combining all current settings and the
* information passed into this method.
*
* @param db the database to query on
* @param projectionIn A list of which columns to return. Passing
* null will return all columns, which is discouraged to prevent
* reading data from storage that isn't going to be used.
* @param selection A filter declaring which rows to return,
* formatted as an SQL WHERE clause (excluding the WHERE
* itself). Passing null will return all rows for the given URL.
* @param selectionArgs You may include ?s in selection, which
* will be replaced by the values from selectionArgs, in order
* that they appear in the selection. The values will be bound
* as Strings.
* @param groupBy A filter declaring how to group rows, formatted
* as an SQL GROUP BY clause (excluding the GROUP BY
* itself). Passing null will cause the rows to not be grouped.
* @param having A filter declare which row groups to include in
* the cursor, if row grouping is being used, formatted as an
* SQL HAVING clause (excluding the HAVING itself). Passing
* null will cause all row groups to be included, and is
* required when row grouping is not being used.
* @param sortOrder How to order the rows, formatted as an SQL
* ORDER BY clause (excluding the ORDER BY itself). Passing null
* will use the default sort order, which may be unordered.
* @param limit Limits the number of rows returned by the query,
* formatted as LIMIT clause. Passing null denotes no LIMIT clause.
* @return a cursor over the result set
* @see android.content.ContentResolver#query(android.net.Uri, String[],
* String, String[], String)
*/
public Cursor query(SQLiteDatabase db, String[] projectionIn,
String selection, String[] selectionArgs, String groupBy,
String having, String sortOrder, String limit) {
if (mTables == null) {
return null;
}
String sql = buildQuery(
projectionIn, selection, selectionArgs, groupBy, having,
sortOrder, limit);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Performing query: " + sql);
}
return db.rawQueryWithFactory(
mFactory, sql, selectionArgs,
SQLiteDatabase.findEditTable(mTables));
}
/**
* Construct a SELECT statement suitable for use in a group of
* SELECT statements that will be joined through UNION operators
* in buildUnionQuery.
*
* @param projectionIn A list of which columns to return. Passing
* null will return all columns, which is discouraged to
* prevent reading data from storage that isn't going to be
* used.
* @param selection A filter declaring which rows to return,
* formatted as an SQL WHERE clause (excluding the WHERE
* itself). Passing null will return all rows for the given
* URL.
* @param selectionArgs You may include ?s in selection, which
* will be replaced by the values from selectionArgs, in order
* that they appear in the selection. The values will be bound
* as Strings.
* @param groupBy A filter declaring how to group rows, formatted
* as an SQL GROUP BY clause (excluding the GROUP BY itself).
* Passing null will cause the rows to not be grouped.
* @param having A filter declare which row groups to include in
* the cursor, if row grouping is being used, formatted as an
* SQL HAVING clause (excluding the HAVING itself). Passing
* null will cause all row groups to be included, and is
* required when row grouping is not being used.
* @param sortOrder How to order the rows, formatted as an SQL
* ORDER BY clause (excluding the ORDER BY itself). Passing null
* will use the default sort order, which may be unordered.
* @param limit Limits the number of rows returned by the query,
* formatted as LIMIT clause. Passing null denotes no LIMIT clause.
* @return the resulting SQL SELECT statement
*/
public String buildQuery(
String[] projectionIn, String selection, String[] selectionArgs,
String groupBy, String having, String sortOrder, String limit) {
String[] projection = computeProjection(projectionIn);
StringBuilder where = new StringBuilder();
boolean hasBaseWhereClause = mWhereClause != null && mWhereClause.length() > 0;
if (hasBaseWhereClause) {
where.append(mWhereClause.toString());
where.append(')');
}
// Tack on the user's selection, if present.
if (selection != null && selection.length() > 0) {
if (hasBaseWhereClause) {
where.append(" AND ");
}
where.append('(');
where.append(selection);
where.append(')');
}
return buildQueryString(
mDistinct, mTables, projection, where.toString(),
groupBy, having, sortOrder, limit);
}
/**
* Construct a SELECT statement suitable for use in a group of
* SELECT statements that will be joined through UNION operators
* in buildUnionQuery.
*
* @param typeDiscriminatorColumn the name of the result column
* whose cells will contain the name of the table from which
* each row was drawn.
* @param unionColumns the names of the columns to appear in the
* result. This may include columns that do not appear in the
* table this SELECT is querying (i.e. mTables), but that do
* appear in one of the other tables in the UNION query that we
* are constructing.
* @param columnsPresentInTable a Set of the names of the columns
* that appear in this table (i.e. in the table whose name is
* mTables). Since columns in unionColumns include columns that
* appear only in other tables, we use this array to distinguish
* which ones actually are present. Other columns will have
* NULL values for results from this subquery.
* @param computedColumnsOffset all columns in unionColumns before
* this index are included under the assumption that they're
* computed and therefore won't appear in columnsPresentInTable,
* e.g. "date * 1000 as normalized_date"
* @param typeDiscriminatorValue the value used for the
* type-discriminator column in this subquery
* @param selection A filter declaring which rows to return,
* formatted as an SQL WHERE clause (excluding the WHERE
* itself). Passing null will return all rows for the given
* URL.
* @param selectionArgs You may include ?s in selection, which
* will be replaced by the values from selectionArgs, in order
* that they appear in the selection. The values will be bound
* as Strings.
* @param groupBy A filter declaring how to group rows, formatted
* as an SQL GROUP BY clause (excluding the GROUP BY itself).
* Passing null will cause the rows to not be grouped.
* @param having A filter declare which row groups to include in
* the cursor, if row grouping is being used, formatted as an
* SQL HAVING clause (excluding the HAVING itself). Passing
* null will cause all row groups to be included, and is
* required when row grouping is not being used.
* @return the resulting SQL SELECT statement
*/
public String buildUnionSubQuery(
String typeDiscriminatorColumn,
String[] unionColumns,
Set<String> columnsPresentInTable,
int computedColumnsOffset,
String typeDiscriminatorValue,
String selection,
String[] selectionArgs,
String groupBy,
String having) {
int unionColumnsCount = unionColumns.length;
String[] projectionIn = new String[unionColumnsCount];
for (int i = 0; i < unionColumnsCount; i++) {
String unionColumn = unionColumns[i];
if (unionColumn.equals(typeDiscriminatorColumn)) {
projectionIn[i] = "'" + typeDiscriminatorValue + "' AS "
+ typeDiscriminatorColumn;
} else if (i <= computedColumnsOffset
|| columnsPresentInTable.contains(unionColumn)) {
projectionIn[i] = unionColumn;
} else {
projectionIn[i] = "NULL AS " + unionColumn;
}
}
return buildQuery(
projectionIn, selection, selectionArgs, groupBy, having,
null /* sortOrder */,
null /* limit */);
}
/**
* Given a set of subqueries, all of which are SELECT statements,
* construct a query that returns the union of what those
* subqueries return.
* @param subQueries an array of SQL SELECT statements, all of
* which must have the same columns as the same positions in
* their results
* @param sortOrder How to order the rows, formatted as an SQL
* ORDER BY clause (excluding the ORDER BY itself). Passing
* null will use the default sort order, which may be unordered.
* @param limit The limit clause, which applies to the entire union result set
*
* @return the resulting SQL SELECT statement
*/
public String buildUnionQuery(String[] subQueries, String sortOrder, String limit) {
StringBuilder query = new StringBuilder(128);
int subQueryCount = subQueries.length;
String unionOperator = mDistinct ? " UNION " : " UNION ALL ";
for (int i = 0; i < subQueryCount; i++) {
if (i > 0) {
query.append(unionOperator);
}
query.append(subQueries[i]);
}
appendClause(query, " ORDER BY ", sortOrder);
appendClause(query, " LIMIT ", limit);
return query.toString();
}
private String[] computeProjection(String[] projectionIn) {
if (projectionIn != null && projectionIn.length > 0) {
if (mProjectionMap != null) {
String[] projection = new String[projectionIn.length];
int length = projectionIn.length;
for (int i = 0; i < length; i++) {
String userColumn = projectionIn[i];
String column = mProjectionMap.get(userColumn);
if (column != null) {
projection[i] = column;
continue;
}
if (!mStrictProjectionMap &&
( userColumn.contains(" AS ") || userColumn.contains(" as "))) {
/* A column alias already exist */
projection[i] = userColumn;
continue;
}
throw new IllegalArgumentException("Invalid column "
+ projectionIn[i]);
}
return projection;
} else {
return projectionIn;
}
} else if (mProjectionMap != null) {
// Return all columns in projection map.
Set<Entry<String, String>> entrySet = mProjectionMap.entrySet();
String[] projection = new String[entrySet.size()];
Iterator<Entry<String, String>> entryIter = entrySet.iterator();
int i = 0;
while (entryIter.hasNext()) {
Entry<String, String> entry = entryIter.next();
// Don't include the _count column when people ask for no projection.
if (entry.getKey().equals(BaseColumns._COUNT)) {
continue;
}
projection[i++] = entry.getValue();
}
return projection;
}
return null;
}
}

View File

@@ -0,0 +1,154 @@
/*
* 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.os.SystemClock;
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.
*/
public class SQLiteStatement extends SQLiteProgram
{
/**
* Don't use SQLiteStatement constructor directly, please use
* {@link SQLiteDatabase#compileStatement(String)}
* @param db
* @param sql
*/
/* package */ SQLiteStatement(SQLiteDatabase db, String sql) {
super(db, sql);
}
/**
* Execute this SQL statement, if it is not a query. For example,
* CREATE TABLE, DELTE, INSERT, etc.
*
* @throws android.database.SQLException If the SQL string is invalid for
* some reason
*/
public void execute() {
BlockGuard.getThreadPolicy().onWriteToDisk();
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
acquireReference();
try {
native_execute();
mDatabase.logTimeStat(mSql, timeStart);
} finally {
releaseReference();
mDatabase.unlock();
}
}
/**
* Execute this SQL statement and return the ID of the row inserted due to this call.
* The SQL statement should be an INSERT for this to be a useful call.
*
* @return the row ID of the last row inserted, if this insert is successful. -1 otherwise.
*
* @throws android.database.SQLException If the SQL string is invalid for
* some reason
*/
public long executeInsert() {
BlockGuard.getThreadPolicy().onWriteToDisk();
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
acquireReference();
try {
native_execute();
mDatabase.logTimeStat(mSql, timeStart);
return (mDatabase.lastChangeCount() > 0) ? mDatabase.lastInsertRow() : -1;
} finally {
releaseReference();
mDatabase.unlock();
}
}
/**
* Execute a statement that returns a 1 by 1 table with a numeric value.
* For example, SELECT COUNT(*) FROM table;
*
* @return The result of the query.
*
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
*/
public long simpleQueryForLong() {
BlockGuard.getThreadPolicy().onReadFromDisk();
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
acquireReference();
try {
long retValue = native_1x1_long();
mDatabase.logTimeStat(mSql, timeStart);
return retValue;
} finally {
releaseReference();
mDatabase.unlock();
}
}
/**
* Execute a statement that returns a 1 by 1 table with a text value.
* For example, SELECT COUNT(*) FROM table;
*
* @return The result of the query.
*
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
*/
public String simpleQueryForString() {
BlockGuard.getThreadPolicy().onReadFromDisk();
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
acquireReference();
try {
String retValue = native_1x1_string();
mDatabase.logTimeStat(mSql, timeStart);
return retValue;
} finally {
releaseReference();
mDatabase.unlock();
}
}
private final native void native_execute();
private final native long native_1x1_long();
private final native String native_1x1_string();
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2009 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;
/**
* A listener for transaction events.
*/
public interface SQLiteTransactionListener {
/**
* Called immediately after the transaction begins.
*/
void onBegin();
/**
* Called immediately before commiting the transaction.
*/
void onCommit();
/**
* Called if the transaction is about to be rolled back.
*/
void onRollback();
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright (C) 2008 Esmertec AG.
* Copyright (C) 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.
*/
package android.database.sqlite;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;
/**
* @hide
*/
public final class SqliteWrapper {
private static final String TAG = "SqliteWrapper";
private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE
= "unable to open database file";
private SqliteWrapper() {
// Forbidden being instantiated.
}
// FIXME: need to optimize this method.
private static boolean isLowMemory(SQLiteException e) {
return e.getMessage().equals(SQLITE_EXCEPTION_DETAIL_MESSAGE);
}
public static void checkSQLiteException(Context context, SQLiteException e) {
if (isLowMemory(e)) {
Toast.makeText(context, com.android.internal.R.string.low_memory,
Toast.LENGTH_SHORT).show();
} else {
throw e;
}
}
public static Cursor query(Context context, ContentResolver resolver, Uri uri,
String[] projection, String selection, String[] selectionArgs, String sortOrder) {
try {
return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
} catch (SQLiteException e) {
Log.e(TAG, "Catch a SQLiteException when query: ", e);
checkSQLiteException(context, e);
return null;
}
}
public static boolean requery(Context context, Cursor cursor) {
try {
return cursor.requery();
} catch (SQLiteException e) {
Log.e(TAG, "Catch a SQLiteException when requery: ", e);
checkSQLiteException(context, e);
return false;
}
}
public static int update(Context context, ContentResolver resolver, Uri uri,
ContentValues values, String where, String[] selectionArgs) {
try {
return resolver.update(uri, values, where, selectionArgs);
} catch (SQLiteException e) {
Log.e(TAG, "Catch a SQLiteException when update: ", e);
checkSQLiteException(context, e);
return -1;
}
}
public static int delete(Context context, ContentResolver resolver, Uri uri,
String where, String[] selectionArgs) {
try {
return resolver.delete(uri, where, selectionArgs);
} catch (SQLiteException e) {
Log.e(TAG, "Catch a SQLiteException when delete: ", e);
checkSQLiteException(context, e);
return -1;
}
}
public static Uri insert(Context context, ContentResolver resolver,
Uri uri, ContentValues values) {
try {
return resolver.insert(uri, values);
} catch (SQLiteException e) {
Log.e(TAG, "Catch a SQLiteException when insert: ", e);
checkSQLiteException(context, e);
return null;
}
}
}

View File

@@ -0,0 +1,20 @@
<HTML>
<BODY>
Contains the SQLite database management
classes that an application would use to manage its own private database.
<p>
Applications use these classes to maange private databases. If creating a
content provider, you will probably have to use these classes to create and
manage your own database to store content. See <a
href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a> to learn
the conventions for implementing a content provider. See the
NotePadProvider class in the NotePad sample application in the SDK for an
example of a content provider. Android ships with SQLite version 3.4.0
<p>If you are working with data sent to you by a provider, you will not use
these SQLite classes, but instead use the generic {@link android.database}
classes.
<p>Android ships with the sqlite3 database tool in the <code>tools/</code>
folder. You can use this tool to browse or run SQL commands on the device. Run by
typing <code>sqlite3</code> in a shell window.
</BODY>
</HTML>