Merge changes Idbfeb3cc,I03e8e2e7,Iff9eed78
* changes: Fix regression in CursorWindow.getString() Bug: 5332296 Clean up CursorWindow lifetime. Bug: 5332296 Fix regression in CursorWindow.copyStingToBuffer. Bug: 5332296
This commit is contained in:
@@ -64,7 +64,10 @@ public abstract class AbstractCursor implements CrossProcessCursor {
|
|||||||
/* Methods that may optionally be implemented by subclasses */
|
/* Methods that may optionally be implemented by subclasses */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns a pre-filled window, return NULL if no such window
|
* If the cursor is backed by a {@link CursorWindow}, returns a pre-filled
|
||||||
|
* window with the contents of the cursor, otherwise null.
|
||||||
|
*
|
||||||
|
* @return The pre-filled window that backs this cursor, or null if none.
|
||||||
*/
|
*/
|
||||||
public CursorWindow getWindow() {
|
public CursorWindow getWindow() {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -18,8 +18,22 @@ package android.database;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A base class for Cursors that store their data in {@link CursorWindow}s.
|
* A base class for Cursors that store their data in {@link CursorWindow}s.
|
||||||
|
* <p>
|
||||||
|
* Subclasses are responsible for filling the cursor window with data during
|
||||||
|
* {@link #onMove(int, int)}, allocating a new cursor window if necessary.
|
||||||
|
* During {@link #requery()}, the existing cursor window should be cleared and
|
||||||
|
* filled with new data.
|
||||||
|
* </p><p>
|
||||||
|
* If the contents of the cursor change or become invalid, the old window must be closed
|
||||||
|
* (because it is owned by the cursor) and set to null.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractWindowedCursor extends AbstractCursor {
|
public abstract class AbstractWindowedCursor extends AbstractCursor {
|
||||||
|
/**
|
||||||
|
* The cursor window owned by this cursor.
|
||||||
|
*/
|
||||||
|
protected CursorWindow mWindow;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getBlob(int columnIndex) {
|
public byte[] getBlob(int columnIndex) {
|
||||||
checkPosition();
|
checkPosition();
|
||||||
@@ -128,23 +142,42 @@ public abstract class AbstractWindowedCursor extends AbstractCursor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a new cursor window to cursor, usually set a remote cursor window
|
* Sets a new cursor window for the cursor to use.
|
||||||
* @param window cursor window
|
* <p>
|
||||||
|
* The cursor takes ownership of the provided cursor window; the cursor window
|
||||||
|
* will be closed when the cursor is closed or when the cursor adopts a new
|
||||||
|
* cursor window.
|
||||||
|
* </p><p>
|
||||||
|
* If the cursor previously had a cursor window, then it is closed when the
|
||||||
|
* new cursor window is assigned.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param window The new cursor window, typically a remote cursor window.
|
||||||
*/
|
*/
|
||||||
public void setWindow(CursorWindow window) {
|
public void setWindow(CursorWindow window) {
|
||||||
if (mWindow != null) {
|
if (window != mWindow) {
|
||||||
mWindow.close();
|
closeWindow();
|
||||||
|
mWindow = window;
|
||||||
}
|
}
|
||||||
mWindow = window;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the cursor has an associated cursor window.
|
||||||
|
*
|
||||||
|
* @return True if the cursor has an associated cursor window.
|
||||||
|
*/
|
||||||
public boolean hasWindow() {
|
public boolean hasWindow() {
|
||||||
return mWindow != null;
|
return mWindow != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This needs be updated in {@link #onMove} by subclasses, and
|
* Closes the cursor window and sets {@link #mWindow} to null.
|
||||||
* needs to be set to NULL when the contents of the cursor change.
|
* @hide
|
||||||
*/
|
*/
|
||||||
protected CursorWindow mWindow;
|
protected void closeWindow() {
|
||||||
|
if (mWindow != null) {
|
||||||
|
mWindow.close();
|
||||||
|
mWindow = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,10 +154,7 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor {
|
|||||||
false /* the window will be accessed across processes */));
|
false /* the window will be accessed across processes */));
|
||||||
if (mCount != -1) {
|
if (mCount != -1) {
|
||||||
mPos = -1;
|
mPos = -1;
|
||||||
if (mWindow != null) {
|
closeWindow();
|
||||||
mWindow.close();
|
|
||||||
mWindow = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// super.requery() will call onChanged. Do it here instead of relying on the
|
// super.requery() will call onChanged. Do it here instead of relying on the
|
||||||
// observer from the far side so that observers can see a correct value for mCount
|
// observer from the far side so that observers can see a correct value for mCount
|
||||||
|
|||||||
@@ -105,8 +105,12 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void finalize() {
|
protected void finalize() throws Throwable {
|
||||||
dispose();
|
try {
|
||||||
|
dispose();
|
||||||
|
} finally {
|
||||||
|
super.finalize();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dispose() {
|
private void dispose() {
|
||||||
@@ -145,10 +149,12 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the start position of this cursor window.
|
* Gets the start position of this cursor window.
|
||||||
* The start position is the index of the first row that this window contains
|
* <p>
|
||||||
|
* The start position is the zero-based index of the first row that this window contains
|
||||||
* relative to the entire result set of the {@link Cursor}.
|
* relative to the entire result set of the {@link Cursor}.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @return The start position.
|
* @return The zero-based start position.
|
||||||
*/
|
*/
|
||||||
public int getStartPosition() {
|
public int getStartPosition() {
|
||||||
return mStartPos;
|
return mStartPos;
|
||||||
@@ -156,10 +162,12 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the start position of this cursor window.
|
* Sets the start position of this cursor window.
|
||||||
* The start position is the index of the first row that this window contains
|
* <p>
|
||||||
|
* The start position is the zero-based index of the first row that this window contains
|
||||||
* relative to the entire result set of the {@link Cursor}.
|
* relative to the entire result set of the {@link Cursor}.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @param pos The new start position.
|
* @param pos The new zero-based start position.
|
||||||
*/
|
*/
|
||||||
public void setStartPosition(int pos) {
|
public void setStartPosition(int pos) {
|
||||||
mStartPos = pos;
|
mStartPos = pos;
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ public class CursorWrapper implements Cursor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the wrapped cursor
|
* Gets the underlying cursor that is wrapped by this instance.
|
||||||
|
*
|
||||||
|
* @return The wrapped cursor.
|
||||||
*/
|
*/
|
||||||
public Cursor getWrappedCursor() {
|
public Cursor getWrappedCursor() {
|
||||||
return mCursor;
|
return mCursor;
|
||||||
|
|||||||
@@ -18,16 +18,11 @@ package android.database.sqlite;
|
|||||||
|
|
||||||
import android.database.AbstractWindowedCursor;
|
import android.database.AbstractWindowedCursor;
|
||||||
import android.database.CursorWindow;
|
import android.database.CursorWindow;
|
||||||
import android.database.DataSetObserver;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Message;
|
|
||||||
import android.os.Process;
|
|
||||||
import android.os.StrictMode;
|
import android.os.StrictMode;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Cursor implementation that exposes results from a query on a
|
* A Cursor implementation that exposes results from a query on a
|
||||||
@@ -61,138 +56,6 @@ public class SQLiteCursor extends AbstractWindowedCursor {
|
|||||||
/** Used to find out where a cursor was allocated in case it never got released. */
|
/** Used to find out where a cursor was allocated in case it never got released. */
|
||||||
private final Throwable mStackTrace;
|
private final 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();
|
|
||||||
try {
|
|
||||||
if (mCursorState != mThreadState) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
int count = getQuery().fillWindow(cw, mMaxRead, mCount);
|
|
||||||
// return -1 means there is still more data to be retrieved from the resultset
|
|
||||||
if (count != 0) {
|
|
||||||
if (count == NO_COUNT){
|
|
||||||
mCount += mMaxRead;
|
|
||||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
|
||||||
Log.d(TAG, "received -1 from native_fill_window. read " +
|
|
||||||
mCount + " rows so far");
|
|
||||||
}
|
|
||||||
sendMessage();
|
|
||||||
} else {
|
|
||||||
mCount += count;
|
|
||||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
|
||||||
Log.d(TAG, "received all data from native_fill_window. read " +
|
|
||||||
mCount + " rows.");
|
|
||||||
}
|
|
||||||
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
|
* 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
|
* interface. For a query such as: {@code SELECT name, birth, phone FROM
|
||||||
@@ -293,36 +156,23 @@ public class SQLiteCursor extends AbstractWindowedCursor {
|
|||||||
return mCount;
|
return mCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillWindow (int startPos) {
|
private void fillWindow(int startPos) {
|
||||||
if (mWindow == null) {
|
if (mWindow == null) {
|
||||||
// If there isn't a window set already it will only be accessed locally
|
// If there isn't a window set already it will only be accessed locally
|
||||||
mWindow = new CursorWindow(true /* the window is local only */);
|
mWindow = new CursorWindow(true /* the window is local only */);
|
||||||
} else {
|
} else {
|
||||||
mCursorState++;
|
mWindow.clear();
|
||||||
queryThreadLock();
|
|
||||||
try {
|
|
||||||
mWindow.clear();
|
|
||||||
} finally {
|
|
||||||
queryThreadUnlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
mWindow.setStartPosition(startPos);
|
mWindow.setStartPosition(startPos);
|
||||||
int count = getQuery().fillWindow(mWindow, mInitialRead, 0);
|
int count = getQuery().fillWindow(mWindow);
|
||||||
// return -1 means there is still more data to be retrieved from the resultset
|
if (startPos == 0) { // fillWindow returns count(*) only for startPos = 0
|
||||||
if (count == NO_COUNT){
|
|
||||||
mCount = startPos + mInitialRead;
|
|
||||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
|
||||||
Log.d(TAG, "received -1 from native_fill_window. read " + mCount + " rows so far");
|
|
||||||
}
|
|
||||||
Thread t = new Thread(new QueryThread(mCursorState), "query thread");
|
|
||||||
t.start();
|
|
||||||
} else if (startPos == 0) { // native_fill_window returns count(*) only for startPos = 0
|
|
||||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
Log.d(TAG, "received count(*) from native_fill_window: " + count);
|
Log.d(TAG, "received count(*) from native_fill_window: " + count);
|
||||||
}
|
}
|
||||||
mCount = count;
|
mCount = count;
|
||||||
} else if (mCount <= 0) {
|
} else if (mCount <= 0) {
|
||||||
throw new IllegalStateException("count should never be non-zero negative number");
|
throw new IllegalStateException("Row count should never be zero or negative "
|
||||||
|
+ "when the start position is non-zero");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,11 +216,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
|
|||||||
|
|
||||||
private void deactivateCommon() {
|
private void deactivateCommon() {
|
||||||
if (false) Log.v(TAG, "<<< Releasing cursor " + this);
|
if (false) Log.v(TAG, "<<< Releasing cursor " + this);
|
||||||
mCursorState = 0;
|
closeWindow();
|
||||||
if (mWindow != null) {
|
|
||||||
mWindow.close();
|
|
||||||
mWindow = null;
|
|
||||||
}
|
|
||||||
if (false) Log.v("DatabaseWindow", "closing window in release()");
|
if (false) Log.v("DatabaseWindow", "closing window in release()");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,16 +285,12 @@ public class SQLiteCursor extends AbstractWindowedCursor {
|
|||||||
// This one will recreate the temp table, and get its count
|
// This one will recreate the temp table, and get its count
|
||||||
mDriver.cursorRequeried(this);
|
mDriver.cursorRequeried(this);
|
||||||
mCount = NO_COUNT;
|
mCount = NO_COUNT;
|
||||||
mCursorState++;
|
|
||||||
queryThreadLock();
|
|
||||||
try {
|
try {
|
||||||
mQuery.requery();
|
mQuery.requery();
|
||||||
} catch (IllegalStateException e) {
|
} catch (IllegalStateException e) {
|
||||||
// for backwards compatibility, just return false
|
// for backwards compatibility, just return false
|
||||||
Log.w(TAG, "requery() failed " + e.getMessage(), e);
|
Log.w(TAG, "requery() failed " + e.getMessage(), e);
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
|
||||||
queryThreadUnlock();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,17 +315,8 @@ public class SQLiteCursor extends AbstractWindowedCursor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setWindow(CursorWindow window) {
|
public void setWindow(CursorWindow window) {
|
||||||
if (mWindow != null) {
|
super.setWindow(window);
|
||||||
mCursorState++;
|
mCount = NO_COUNT;
|
||||||
queryThreadLock();
|
|
||||||
try {
|
|
||||||
mWindow.close();
|
|
||||||
} finally {
|
|
||||||
queryThreadUnlock();
|
|
||||||
}
|
|
||||||
mCount = NO_COUNT;
|
|
||||||
}
|
|
||||||
mWindow = window;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -521,11 +354,4 @@ public class SQLiteCursor extends AbstractWindowedCursor {
|
|||||||
super.finalize();
|
super.finalize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* this is only for testing purposes.
|
|
||||||
*/
|
|
||||||
/* package */ int getMCount() {
|
|
||||||
return mCount;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1597,32 +1597,6 @@ public class SQLiteDatabase extends SQLiteClosable {
|
|||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs the provided SQL and returns a cursor over the result set.
|
|
||||||
* The cursor will read an initial set of rows and the return to the caller.
|
|
||||||
* It will continue to read in batches and send data changed notifications
|
|
||||||
* when the later batches are ready.
|
|
||||||
* @param sql the SQL query. The SQL string must not be ; terminated
|
|
||||||
* @param selectionArgs You may include ?s in where clause in the query,
|
|
||||||
* which will be replaced by the values from selectionArgs. The
|
|
||||||
* values will be bound as Strings.
|
|
||||||
* @param initialRead set the initial count of items to read from the cursor
|
|
||||||
* @param maxRead set the count of items to read on each iteration after the first
|
|
||||||
* @return A {@link Cursor} object, which is positioned before the first entry. Note that
|
|
||||||
* {@link Cursor}s are not synchronized, see the documentation for more details.
|
|
||||||
*
|
|
||||||
* This work is incomplete and not fully tested or reviewed, so currently
|
|
||||||
* hidden.
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
public Cursor rawQuery(String sql, String[] selectionArgs,
|
|
||||||
int initialRead, int maxRead) {
|
|
||||||
SQLiteCursor c = (SQLiteCursor)rawQueryWithFactory(
|
|
||||||
null, sql, selectionArgs, null);
|
|
||||||
c.setLoadStyle(initialRead, maxRead);
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method for inserting a row into the database.
|
* Convenience method for inserting a row into the database.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ import android.util.Log;
|
|||||||
public class SQLiteQuery extends SQLiteProgram {
|
public class SQLiteQuery extends SQLiteProgram {
|
||||||
private static final String TAG = "SQLiteQuery";
|
private static final String TAG = "SQLiteQuery";
|
||||||
|
|
||||||
|
private static native int nativeFillWindow(int databasePtr, int statementPtr, int windowPtr,
|
||||||
|
int startPos, int offsetParam);
|
||||||
|
private static native int nativeColumnCount(int statementPtr);
|
||||||
|
private static native String nativeColumnName(int statementPtr, int columnIndex);
|
||||||
|
|
||||||
/** The index of the unbound OFFSET parameter */
|
/** The index of the unbound OFFSET parameter */
|
||||||
private int mOffsetIndex = 0;
|
private int mOffsetIndex = 0;
|
||||||
|
|
||||||
@@ -68,19 +73,15 @@ public class SQLiteQuery extends SQLiteProgram {
|
|||||||
* @param window The window to fill into
|
* @param window The window to fill into
|
||||||
* @return number of total rows in the query
|
* @return number of total rows in the query
|
||||||
*/
|
*/
|
||||||
/* package */ int fillWindow(CursorWindow window,
|
/* package */ int fillWindow(CursorWindow window) {
|
||||||
int maxRead, int lastPos) {
|
|
||||||
mDatabase.lock(mSql);
|
mDatabase.lock(mSql);
|
||||||
long timeStart = SystemClock.uptimeMillis();
|
long timeStart = SystemClock.uptimeMillis();
|
||||||
try {
|
try {
|
||||||
acquireReference();
|
acquireReference();
|
||||||
try {
|
try {
|
||||||
window.acquireReference();
|
window.acquireReference();
|
||||||
// if the start pos is not equal to 0, then most likely window is
|
int numRows = nativeFillWindow(nHandle, nStatement, window.mWindowPtr,
|
||||||
// too small for the data set, loading by another thread
|
window.getStartPosition(), mOffsetIndex);
|
||||||
// is not safe in this situation. the native code will ignore maxRead
|
|
||||||
int numRows = native_fill_window(window.mWindowPtr, window.getStartPosition(),
|
|
||||||
mOffsetIndex, maxRead, lastPos);
|
|
||||||
mDatabase.logTimeStat(mSql, timeStart);
|
mDatabase.logTimeStat(mSql, timeStart);
|
||||||
return numRows;
|
return numRows;
|
||||||
} catch (IllegalStateException e){
|
} catch (IllegalStateException e){
|
||||||
@@ -111,7 +112,7 @@ public class SQLiteQuery extends SQLiteProgram {
|
|||||||
/* package */ int columnCountLocked() {
|
/* package */ int columnCountLocked() {
|
||||||
acquireReference();
|
acquireReference();
|
||||||
try {
|
try {
|
||||||
return native_column_count();
|
return nativeColumnCount(nStatement);
|
||||||
} finally {
|
} finally {
|
||||||
releaseReference();
|
releaseReference();
|
||||||
}
|
}
|
||||||
@@ -127,7 +128,7 @@ public class SQLiteQuery extends SQLiteProgram {
|
|||||||
/* package */ String columnNameLocked(int columnIndex) {
|
/* package */ String columnNameLocked(int columnIndex) {
|
||||||
acquireReference();
|
acquireReference();
|
||||||
try {
|
try {
|
||||||
return native_column_name(columnIndex);
|
return nativeColumnName(nStatement, columnIndex);
|
||||||
} finally {
|
} finally {
|
||||||
releaseReference();
|
releaseReference();
|
||||||
}
|
}
|
||||||
@@ -153,11 +154,4 @@ public class SQLiteQuery extends SQLiteProgram {
|
|||||||
}
|
}
|
||||||
compileAndbindAllArgs();
|
compileAndbindAllArgs();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final native int native_fill_window(int windowPtr,
|
|
||||||
int startPos, int offsetParam, int maxRead, int lastPos);
|
|
||||||
|
|
||||||
private final native int native_column_count();
|
|
||||||
|
|
||||||
private final native String native_column_name(int columnIndex);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,8 +205,14 @@ static jstring nativeGetString(JNIEnv* env, jclass clazz, jint windowPtr,
|
|||||||
if (type == FIELD_TYPE_STRING) {
|
if (type == FIELD_TYPE_STRING) {
|
||||||
uint32_t size = fieldSlot->data.buffer.size;
|
uint32_t size = fieldSlot->data.buffer.size;
|
||||||
#if WINDOW_STORAGE_UTF8
|
#if WINDOW_STORAGE_UTF8
|
||||||
return size > 1 ? env->NewStringUTF(window->getFieldSlotValueString(fieldSlot))
|
if (size <= 1) {
|
||||||
: gEmptyString;
|
return gEmptyString;
|
||||||
|
}
|
||||||
|
// Convert to UTF-16 here instead of calling NewStringUTF. NewStringUTF
|
||||||
|
// doesn't like UTF-8 strings with high codepoints. It actually expects
|
||||||
|
// Modified UTF-8 with encoded surrogate pairs.
|
||||||
|
String16 utf16(window->getFieldSlotValueString(fieldSlot), size - 1);
|
||||||
|
return env->NewString(reinterpret_cast<const jchar*>(utf16.string()), utf16.size());
|
||||||
#else
|
#else
|
||||||
size_t chars = size / sizeof(char16_t);
|
size_t chars = size / sizeof(char16_t);
|
||||||
return chars ? env->NewString(reinterpret_cast<jchar*>(
|
return chars ? env->NewString(reinterpret_cast<jchar*>(
|
||||||
@@ -267,7 +273,7 @@ static void fillCharArrayBufferUTF(JNIEnv* env, jobject bufferObj,
|
|||||||
if (dataObj) {
|
if (dataObj) {
|
||||||
if (size) {
|
if (size) {
|
||||||
jchar* data = static_cast<jchar*>(env->GetPrimitiveArrayCritical(dataObj, NULL));
|
jchar* data = static_cast<jchar*>(env->GetPrimitiveArrayCritical(dataObj, NULL));
|
||||||
utf8_to_utf16(reinterpret_cast<const uint8_t*>(str), len,
|
utf8_to_utf16_no_null_terminator(reinterpret_cast<const uint8_t*>(str), len,
|
||||||
reinterpret_cast<char16_t*>(data));
|
reinterpret_cast<char16_t*>(data));
|
||||||
env->ReleasePrimitiveArrayCritical(dataObj, data, 0);
|
env->ReleasePrimitiveArrayCritical(dataObj, data, 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,99 +35,20 @@
|
|||||||
|
|
||||||
namespace android {
|
namespace android {
|
||||||
|
|
||||||
static jfieldID gHandleField;
|
static jint nativeFillWindow(JNIEnv* env, jclass clazz, jint databasePtr,
|
||||||
static jfieldID gStatementField;
|
jint statementPtr, jint windowPtr, jint startPos, jint offsetParam) {
|
||||||
|
sqlite3* database = reinterpret_cast<sqlite3*>(databasePtr);
|
||||||
|
sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
|
||||||
#define GET_STATEMENT(env, object) \
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
||||||
(sqlite3_stmt *)env->GetIntField(object, gStatementField)
|
|
||||||
#define GET_HANDLE(env, object) \
|
|
||||||
(sqlite3 *)env->GetIntField(object, gHandleField)
|
|
||||||
|
|
||||||
static int skip_rows(sqlite3_stmt *statement, int maxRows) {
|
|
||||||
int retryCount = 0;
|
|
||||||
for (int i = 0; i < maxRows; i++) {
|
|
||||||
int err = sqlite3_step(statement);
|
|
||||||
if (err == SQLITE_ROW){
|
|
||||||
// do nothing
|
|
||||||
} else if (err == SQLITE_DONE) {
|
|
||||||
return i;
|
|
||||||
} else if (err == SQLITE_LOCKED || err == SQLITE_BUSY) {
|
|
||||||
// The table is locked, retry
|
|
||||||
LOG_WINDOW("Database locked, retrying");
|
|
||||||
if (retryCount > 50) {
|
|
||||||
LOGE("Bailing on database busy rety");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Sleep to give the thread holding the lock a chance to finish
|
|
||||||
usleep(1000);
|
|
||||||
retryCount++;
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LOG_WINDOW("skip_rows row %d", maxRows);
|
|
||||||
return maxRows;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int finish_program_and_get_row_count(sqlite3_stmt *statement) {
|
|
||||||
int numRows = 0;
|
|
||||||
int retryCount = 0;
|
|
||||||
while (true) {
|
|
||||||
int err = sqlite3_step(statement);
|
|
||||||
if (err == SQLITE_ROW){
|
|
||||||
numRows++;
|
|
||||||
} else if (err == SQLITE_LOCKED || err == SQLITE_BUSY) {
|
|
||||||
// The table is locked, retry
|
|
||||||
LOG_WINDOW("Database locked, retrying");
|
|
||||||
if (retryCount > 50) {
|
|
||||||
LOGE("Bailing on database busy rety");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Sleep to give the thread holding the lock a chance to finish
|
|
||||||
usleep(1000);
|
|
||||||
retryCount++;
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
// no need to throw exception
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sqlite3_reset(statement);
|
|
||||||
LOG_WINDOW("finish_program_and_get_row_count row %d", numRows);
|
|
||||||
return numRows;
|
|
||||||
}
|
|
||||||
|
|
||||||
static jint native_fill_window(JNIEnv* env, jobject object, jint windowPtr,
|
|
||||||
jint startPos, jint offsetParam, jint maxRead, jint lastPos)
|
|
||||||
{
|
|
||||||
int err;
|
|
||||||
sqlite3_stmt * statement = GET_STATEMENT(env, object);
|
|
||||||
int numRows = lastPos;
|
|
||||||
maxRead += lastPos;
|
|
||||||
int numColumns;
|
|
||||||
int retryCount;
|
|
||||||
int boundParams;
|
|
||||||
CursorWindow * window;
|
|
||||||
bool gotAllRows = true;
|
|
||||||
bool gotException = false;
|
|
||||||
|
|
||||||
if (statement == NULL) {
|
|
||||||
LOGE("Invalid statement in fillWindow()");
|
|
||||||
jniThrowException(env, "java/lang/IllegalStateException",
|
|
||||||
"Attempting to access a deactivated, closed, or empty cursor");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only do the binding if there is a valid offsetParam. If no binding needs to be done
|
// Only do the binding if there is a valid offsetParam. If no binding needs to be done
|
||||||
// offsetParam will be set to 0, an invliad value.
|
// offsetParam will be set to 0, an invalid value.
|
||||||
if(offsetParam > 0) {
|
if (offsetParam > 0) {
|
||||||
// Bind the offset parameter, telling the program which row to start with
|
// Bind the offset parameter, telling the program which row to start with
|
||||||
err = sqlite3_bind_int(statement, offsetParam, startPos);
|
int err = sqlite3_bind_int(statement, offsetParam, startPos);
|
||||||
if (err != SQLITE_OK) {
|
if (err != SQLITE_OK) {
|
||||||
LOGE("Unable to bind offset position, offsetParam = %d", offsetParam);
|
LOGE("Unable to bind offset position, offsetParam = %d", offsetParam);
|
||||||
throw_sqlite3_exception(env, GET_HANDLE(env, object));
|
throw_sqlite3_exception(env, database);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
LOG_WINDOW("Bound to startPos %d", startPos);
|
LOG_WINDOW("Bound to startPos %d", startPos);
|
||||||
@@ -135,136 +56,123 @@ static jint native_fill_window(JNIEnv* env, jobject object, jint windowPtr,
|
|||||||
LOG_WINDOW("Not binding to startPos %d", startPos);
|
LOG_WINDOW("Not binding to startPos %d", startPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the native window
|
// We assume numRows is initially 0.
|
||||||
window = reinterpret_cast<CursorWindow*>(windowPtr);
|
LOG_WINDOW("Window: numRows = %d, size = %d, freeSpace = %d",
|
||||||
if (!window) {
|
window->getNumRows(), window->size(), window->freeSpace());
|
||||||
LOGE("Invalid CursorWindow");
|
|
||||||
jniThrowException(env, "java/lang/IllegalArgumentException",
|
|
||||||
"Bad CursorWindow");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
LOG_WINDOW("Window: numRows = %d, size = %d, freeSpace = %d", window->getNumRows(), window->size(), window->freeSpace());
|
|
||||||
|
|
||||||
numColumns = sqlite3_column_count(statement);
|
int numColumns = sqlite3_column_count(statement);
|
||||||
if (!window->setNumColumns(numColumns)) {
|
if (!window->setNumColumns(numColumns)) {
|
||||||
LOGE("Failed to change column count from %d to %d", window->getNumColumns(), numColumns);
|
LOGE("Failed to change column count from %d to %d", window->getNumColumns(), numColumns);
|
||||||
jniThrowException(env, "java/lang/IllegalStateException", "numColumns mismatch");
|
jniThrowException(env, "java/lang/IllegalStateException", "numColumns mismatch");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
retryCount = 0;
|
int retryCount = 0;
|
||||||
if (startPos > 0) {
|
int totalRows = 0;
|
||||||
int num = skip_rows(statement, startPos);
|
int addedRows = 0;
|
||||||
if (num < 0) {
|
bool windowFull = false;
|
||||||
throw_sqlite3_exception(env, GET_HANDLE(env, object));
|
bool gotException = false;
|
||||||
return 0;
|
const bool countAllRows = (startPos == 0); // when startPos is 0, we count all rows
|
||||||
} else if (num < startPos) {
|
while (!gotException && (!windowFull || countAllRows)) {
|
||||||
LOGE("startPos %d > actual rows %d", startPos, num);
|
int err = sqlite3_step(statement);
|
||||||
return num;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while(startPos != 0 || numRows < maxRead) {
|
|
||||||
err = sqlite3_step(statement);
|
|
||||||
if (err == SQLITE_ROW) {
|
if (err == SQLITE_ROW) {
|
||||||
LOG_WINDOW("\nStepped statement %p to row %d", statement, startPos + numRows);
|
LOG_WINDOW("Stepped statement %p to row %d", statement, totalRows);
|
||||||
retryCount = 0;
|
retryCount = 0;
|
||||||
|
totalRows += 1;
|
||||||
|
|
||||||
// Allocate a new field directory for the row. This pointer is not reused
|
// Skip the row if the window is full or we haven't reached the start position yet.
|
||||||
// since it mey be possible for it to be relocated on a call to alloc() when
|
if (startPos >= totalRows || windowFull) {
|
||||||
// the field data is being allocated.
|
continue;
|
||||||
{
|
|
||||||
field_slot_t * fieldDir = window->allocRow();
|
|
||||||
if (!fieldDir) {
|
|
||||||
LOG_WINDOW("Failed allocating fieldDir at startPos %d row %d", startPos, numRows);
|
|
||||||
gotAllRows = false;
|
|
||||||
goto return_count;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pack the row into the window
|
// Allocate a new field directory for the row. This pointer is not reused
|
||||||
int i;
|
// since it may be possible for it to be relocated on a call to alloc() when
|
||||||
for (i = 0; i < numColumns; i++) {
|
// the field data is being allocated.
|
||||||
|
field_slot_t* fieldDir = window->allocRow();
|
||||||
|
if (!fieldDir) {
|
||||||
|
LOG_WINDOW("Failed allocating fieldDir at startPos %d row %d",
|
||||||
|
startPos, addedRows);
|
||||||
|
windowFull = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack the row into the window.
|
||||||
|
for (int i = 0; i < numColumns; i++) {
|
||||||
int type = sqlite3_column_type(statement, i);
|
int type = sqlite3_column_type(statement, i);
|
||||||
if (type == SQLITE_TEXT) {
|
if (type == SQLITE_TEXT) {
|
||||||
// TEXT data
|
// TEXT data
|
||||||
#if WINDOW_STORAGE_UTF8
|
#if WINDOW_STORAGE_UTF8
|
||||||
uint8_t const * text = (uint8_t const *)sqlite3_column_text(statement, i);
|
const uint8_t* text = reinterpret_cast<const uint8_t*>(
|
||||||
|
sqlite3_column_text(statement, i));
|
||||||
// SQLite does not include the NULL terminator in size, but does
|
// SQLite does not include the NULL terminator in size, but does
|
||||||
// ensure all strings are NULL terminated, so increase size by
|
// ensure all strings are NULL terminated, so increase size by
|
||||||
// one to make sure we store the terminator.
|
// one to make sure we store the terminator.
|
||||||
size_t size = sqlite3_column_bytes(statement, i) + 1;
|
size_t size = sqlite3_column_bytes(statement, i) + 1;
|
||||||
#else
|
#else
|
||||||
uint8_t const * text = (uint8_t const *)sqlite3_column_text16(statement, i);
|
const uint8_t* text = reinterpret_cast<const uint8_t*>(
|
||||||
|
sqlite3_column_text16(statement, i));
|
||||||
size_t size = sqlite3_column_bytes16(statement, i);
|
size_t size = sqlite3_column_bytes16(statement, i);
|
||||||
#endif
|
#endif
|
||||||
int offset = window->alloc(size);
|
int offset = window->alloc(size);
|
||||||
if (!offset) {
|
if (!offset) {
|
||||||
window->freeLastRow();
|
|
||||||
LOG_WINDOW("Failed allocating %u bytes for text/blob at %d,%d", size,
|
LOG_WINDOW("Failed allocating %u bytes for text/blob at %d,%d", size,
|
||||||
startPos + numRows, i);
|
startPos + addedRows, i);
|
||||||
gotAllRows = false;
|
windowFull = true;
|
||||||
goto return_count;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
window->copyIn(offset, text, size);
|
window->copyIn(offset, text, size);
|
||||||
|
|
||||||
// This must be updated after the call to alloc(), since that
|
field_slot_t* fieldSlot = window->getFieldSlot(addedRows, i);
|
||||||
// may move the field around in the window
|
|
||||||
field_slot_t * fieldSlot = window->getFieldSlot(numRows, i);
|
|
||||||
fieldSlot->type = FIELD_TYPE_STRING;
|
fieldSlot->type = FIELD_TYPE_STRING;
|
||||||
fieldSlot->data.buffer.offset = offset;
|
fieldSlot->data.buffer.offset = offset;
|
||||||
fieldSlot->data.buffer.size = size;
|
fieldSlot->data.buffer.size = size;
|
||||||
|
LOG_WINDOW("%d,%d is TEXT with %u bytes", startPos + addedRows, i, size);
|
||||||
LOG_WINDOW("%d,%d is TEXT with %u bytes", startPos + numRows, i, size);
|
|
||||||
} else if (type == SQLITE_INTEGER) {
|
} else if (type == SQLITE_INTEGER) {
|
||||||
// INTEGER data
|
// INTEGER data
|
||||||
int64_t value = sqlite3_column_int64(statement, i);
|
int64_t value = sqlite3_column_int64(statement, i);
|
||||||
if (!window->putLong(numRows, i, value)) {
|
if (!window->putLong(addedRows, i, value)) {
|
||||||
window->freeLastRow();
|
|
||||||
LOG_WINDOW("Failed allocating space for a long in column %d", i);
|
LOG_WINDOW("Failed allocating space for a long in column %d", i);
|
||||||
gotAllRows = false;
|
windowFull = true;
|
||||||
goto return_count;
|
break;
|
||||||
}
|
}
|
||||||
LOG_WINDOW("%d,%d is INTEGER 0x%016llx", startPos + numRows, i, value);
|
LOG_WINDOW("%d,%d is INTEGER 0x%016llx", startPos + addedRows, i, value);
|
||||||
} else if (type == SQLITE_FLOAT) {
|
} else if (type == SQLITE_FLOAT) {
|
||||||
// FLOAT data
|
// FLOAT data
|
||||||
double value = sqlite3_column_double(statement, i);
|
double value = sqlite3_column_double(statement, i);
|
||||||
if (!window->putDouble(numRows, i, value)) {
|
if (!window->putDouble(addedRows, i, value)) {
|
||||||
window->freeLastRow();
|
|
||||||
LOG_WINDOW("Failed allocating space for a double in column %d", i);
|
LOG_WINDOW("Failed allocating space for a double in column %d", i);
|
||||||
gotAllRows = false;
|
windowFull = true;
|
||||||
goto return_count;
|
break;
|
||||||
}
|
}
|
||||||
LOG_WINDOW("%d,%d is FLOAT %lf", startPos + numRows, i, value);
|
LOG_WINDOW("%d,%d is FLOAT %lf", startPos + addedRows, i, value);
|
||||||
} else if (type == SQLITE_BLOB) {
|
} else if (type == SQLITE_BLOB) {
|
||||||
// BLOB data
|
// BLOB data
|
||||||
uint8_t const * blob = (uint8_t const *)sqlite3_column_blob(statement, i);
|
uint8_t const * blob = (uint8_t const *)sqlite3_column_blob(statement, i);
|
||||||
size_t size = sqlite3_column_bytes16(statement, i);
|
size_t size = sqlite3_column_bytes16(statement, i);
|
||||||
int offset = window->alloc(size);
|
int offset = window->alloc(size);
|
||||||
if (!offset) {
|
if (!offset) {
|
||||||
window->freeLastRow();
|
|
||||||
LOG_WINDOW("Failed allocating %u bytes for blob at %d,%d", size,
|
LOG_WINDOW("Failed allocating %u bytes for blob at %d,%d", size,
|
||||||
startPos + numRows, i);
|
startPos + addedRows, i);
|
||||||
gotAllRows = false;
|
windowFull = true;
|
||||||
goto return_count;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
window->copyIn(offset, blob, size);
|
window->copyIn(offset, blob, size);
|
||||||
|
|
||||||
// This must be updated after the call to alloc(), since that
|
field_slot_t* fieldSlot = window->getFieldSlot(addedRows, i);
|
||||||
// may move the field around in the window
|
|
||||||
field_slot_t * fieldSlot = window->getFieldSlot(numRows, i);
|
|
||||||
fieldSlot->type = FIELD_TYPE_BLOB;
|
fieldSlot->type = FIELD_TYPE_BLOB;
|
||||||
fieldSlot->data.buffer.offset = offset;
|
fieldSlot->data.buffer.offset = offset;
|
||||||
fieldSlot->data.buffer.size = size;
|
fieldSlot->data.buffer.size = size;
|
||||||
|
LOG_WINDOW("%d,%d is Blob with %u bytes @ %d",
|
||||||
LOG_WINDOW("%d,%d is Blob with %u bytes @ %d", startPos + numRows, i, size, offset);
|
startPos + addedRows, i, size, offset);
|
||||||
} else if (type == SQLITE_NULL) {
|
} else if (type == SQLITE_NULL) {
|
||||||
// NULL field
|
// NULL field
|
||||||
window->putNull(numRows, i);
|
if (!window->putNull(addedRows, i)) {
|
||||||
|
LOG_WINDOW("Failed allocating space for a null in column %d", i);
|
||||||
|
windowFull = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
LOG_WINDOW("%d,%d is NULL", startPos + numRows, i);
|
LOG_WINDOW("%d,%d is NULL", startPos + addedRows, i);
|
||||||
} else {
|
} else {
|
||||||
// Unknown data
|
// Unknown data
|
||||||
LOGE("Unknown column type when filling database window");
|
LOGE("Unknown column type when filling database window");
|
||||||
@@ -274,14 +182,12 @@ static jint native_fill_window(JNIEnv* env, jobject object, jint windowPtr,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i < numColumns) {
|
// Update the final row tally.
|
||||||
// Not all the fields fit in the window
|
if (windowFull || gotException) {
|
||||||
// Unknown data error happened
|
window->freeLastRow();
|
||||||
break;
|
} else {
|
||||||
|
addedRows += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the row as complete in the window
|
|
||||||
numRows++;
|
|
||||||
} else if (err == SQLITE_DONE) {
|
} else if (err == SQLITE_DONE) {
|
||||||
// All rows processed, bail
|
// All rows processed, bail
|
||||||
LOG_WINDOW("Processed all rows");
|
LOG_WINDOW("Processed all rows");
|
||||||
@@ -290,63 +196,41 @@ static jint native_fill_window(JNIEnv* env, jobject object, jint windowPtr,
|
|||||||
// The table is locked, retry
|
// The table is locked, retry
|
||||||
LOG_WINDOW("Database locked, retrying");
|
LOG_WINDOW("Database locked, retrying");
|
||||||
if (retryCount > 50) {
|
if (retryCount > 50) {
|
||||||
LOGE("Bailing on database busy rety");
|
LOGE("Bailing on database busy retry");
|
||||||
throw_sqlite3_exception(env, GET_HANDLE(env, object), "retrycount exceeded");
|
throw_sqlite3_exception(env, database, "retrycount exceeded");
|
||||||
gotException = true;
|
gotException = true;
|
||||||
break;
|
} else {
|
||||||
|
// Sleep to give the thread holding the lock a chance to finish
|
||||||
|
usleep(1000);
|
||||||
|
retryCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sleep to give the thread holding the lock a chance to finish
|
|
||||||
usleep(1000);
|
|
||||||
|
|
||||||
retryCount++;
|
|
||||||
continue;
|
|
||||||
} else {
|
} else {
|
||||||
throw_sqlite3_exception(env, GET_HANDLE(env, object));
|
throw_sqlite3_exception(env, database);
|
||||||
gotException = true;
|
gotException = true;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_WINDOW("Resetting statement %p after fetching %d rows in %d bytes\n\n\n\n", statement,
|
LOG_WINDOW("Resetting statement %p after fetching %d rows and adding %d rows"
|
||||||
numRows, window->size() - window->freeSpace());
|
"to the window in %d bytes",
|
||||||
LOG_WINDOW("Filled window with %d rows in %d bytes", numRows,
|
statement, totalRows, addedRows, window->size() - window->freeSpace());
|
||||||
window->size() - window->freeSpace());
|
sqlite3_reset(statement);
|
||||||
if (err == SQLITE_ROW) {
|
|
||||||
// there is more data to be returned. let the caller know by returning -1
|
// Report the total number of rows on request.
|
||||||
return -1;
|
if (startPos > totalRows) {
|
||||||
}
|
LOGE("startPos %d > actual rows %d", startPos, totalRows);
|
||||||
return_count:
|
|
||||||
if (startPos) {
|
|
||||||
sqlite3_reset(statement);
|
|
||||||
LOG_WINDOW("Not doing count(*) because startPos %d is non-zero", startPos);
|
|
||||||
return 0;
|
|
||||||
} else if (gotAllRows) {
|
|
||||||
sqlite3_reset(statement);
|
|
||||||
LOG_WINDOW("Not doing count(*) because we already know the count(*)");
|
|
||||||
return numRows;
|
|
||||||
} else if (gotException) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
// since startPos == 0, we need to get the count(*) of the result set
|
|
||||||
return numRows + 1 + finish_program_and_get_row_count(statement);
|
|
||||||
}
|
}
|
||||||
|
return countAllRows ? totalRows : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static jint native_column_count(JNIEnv* env, jobject object)
|
static jint nativeColumnCount(JNIEnv* env, jclass clazz, jint statementPtr) {
|
||||||
{
|
sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
|
||||||
sqlite3_stmt * statement = GET_STATEMENT(env, object);
|
|
||||||
|
|
||||||
return sqlite3_column_count(statement);
|
return sqlite3_column_count(statement);
|
||||||
}
|
}
|
||||||
|
|
||||||
static jstring native_column_name(JNIEnv* env, jobject object, jint columnIndex)
|
static jstring nativeColumnName(JNIEnv* env, jclass clazz, jint statementPtr,
|
||||||
{
|
jint columnIndex) {
|
||||||
sqlite3_stmt * statement = GET_STATEMENT(env, object);
|
sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
|
||||||
char const * name;
|
const char* name = sqlite3_column_name(statement, columnIndex);
|
||||||
|
|
||||||
name = sqlite3_column_name(statement, columnIndex);
|
|
||||||
|
|
||||||
return env->NewStringUTF(name);
|
return env->NewStringUTF(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,30 +238,16 @@ static jstring native_column_name(JNIEnv* env, jobject object, jint columnIndex)
|
|||||||
static JNINativeMethod sMethods[] =
|
static JNINativeMethod sMethods[] =
|
||||||
{
|
{
|
||||||
/* name, signature, funcPtr */
|
/* name, signature, funcPtr */
|
||||||
{"native_fill_window", "(IIIII)I",
|
{ "nativeFillWindow", "(IIIII)I",
|
||||||
(void *)native_fill_window},
|
(void*)nativeFillWindow },
|
||||||
{"native_column_count", "()I", (void*)native_column_count},
|
{ "nativeColumnCount", "(I)I",
|
||||||
{"native_column_name", "(I)Ljava/lang/String;", (void *)native_column_name},
|
(void*)nativeColumnCount},
|
||||||
|
{ "nativeColumnName", "(II)Ljava/lang/String;",
|
||||||
|
(void*)nativeColumnName},
|
||||||
};
|
};
|
||||||
|
|
||||||
int register_android_database_SQLiteQuery(JNIEnv * env)
|
int register_android_database_SQLiteQuery(JNIEnv * env)
|
||||||
{
|
{
|
||||||
jclass clazz;
|
|
||||||
|
|
||||||
clazz = env->FindClass("android/database/sqlite/SQLiteQuery");
|
|
||||||
if (clazz == NULL) {
|
|
||||||
LOGE("Can't find android/database/sqlite/SQLiteQuery");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
gHandleField = env->GetFieldID(clazz, "nHandle", "I");
|
|
||||||
gStatementField = env->GetFieldID(clazz, "nStatement", "I");
|
|
||||||
|
|
||||||
if (gHandleField == NULL || gStatementField == NULL) {
|
|
||||||
LOGE("Error locating fields");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return AndroidRuntime::registerNativeMethods(env,
|
return AndroidRuntime::registerNativeMethods(env,
|
||||||
"android/database/sqlite/SQLiteQuery", sMethods, NELEM(sMethods));
|
"android/database/sqlite/SQLiteQuery", sMethods, NELEM(sMethods));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,109 +291,6 @@ public class DatabaseCursorTest extends AndroidTestCase implements PerformanceTe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//@Large
|
|
||||||
@Suppress
|
|
||||||
public void testLoadingThreadDelayRegisterData() throws Exception {
|
|
||||||
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);");
|
|
||||||
|
|
||||||
final int count = 505;
|
|
||||||
String sql = "INSERT INTO test (data) VALUES (?);";
|
|
||||||
SQLiteStatement s = mDatabase.compileStatement(sql);
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
s.bindLong(1, i);
|
|
||||||
s.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
int maxRead = 500;
|
|
||||||
int initialRead = 5;
|
|
||||||
SQLiteCursor c = (SQLiteCursor)mDatabase.rawQuery("select * from test;",
|
|
||||||
null, initialRead, maxRead);
|
|
||||||
|
|
||||||
TestObserver observer = new TestObserver(count, c);
|
|
||||||
c.getCount();
|
|
||||||
c.registerDataSetObserver(observer);
|
|
||||||
if (!observer.quit) {
|
|
||||||
Looper.loop();
|
|
||||||
}
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
//@LargeTest
|
|
||||||
@BrokenTest("Consistently times out")
|
|
||||||
@Suppress
|
|
||||||
public void testLoadingThread() throws Exception {
|
|
||||||
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);");
|
|
||||||
|
|
||||||
final int count = 50000;
|
|
||||||
String sql = "INSERT INTO test (data) VALUES (?);";
|
|
||||||
SQLiteStatement s = mDatabase.compileStatement(sql);
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
s.bindLong(1, i);
|
|
||||||
s.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
int maxRead = 1000;
|
|
||||||
int initialRead = 5;
|
|
||||||
SQLiteCursor c = (SQLiteCursor)mDatabase.rawQuery("select * from test;",
|
|
||||||
null, initialRead, maxRead);
|
|
||||||
|
|
||||||
TestObserver observer = new TestObserver(count, c);
|
|
||||||
c.registerDataSetObserver(observer);
|
|
||||||
c.getCount();
|
|
||||||
|
|
||||||
Looper.loop();
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
//@LargeTest
|
|
||||||
@BrokenTest("Consistently times out")
|
|
||||||
@Suppress
|
|
||||||
public void testLoadingThreadClose() throws Exception {
|
|
||||||
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);");
|
|
||||||
|
|
||||||
final int count = 1000;
|
|
||||||
String sql = "INSERT INTO test (data) VALUES (?);";
|
|
||||||
SQLiteStatement s = mDatabase.compileStatement(sql);
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
s.bindLong(1, i);
|
|
||||||
s.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
int maxRead = 11;
|
|
||||||
int initialRead = 5;
|
|
||||||
SQLiteCursor c = (SQLiteCursor)mDatabase.rawQuery("select * from test;",
|
|
||||||
null, initialRead, maxRead);
|
|
||||||
|
|
||||||
TestObserver observer = new TestObserver(count, c);
|
|
||||||
c.registerDataSetObserver(observer);
|
|
||||||
c.getCount();
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@LargeTest
|
|
||||||
public void testLoadingThreadDeactivate() throws Exception {
|
|
||||||
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);");
|
|
||||||
|
|
||||||
final int count = 1000;
|
|
||||||
String sql = "INSERT INTO test (data) VALUES (?);";
|
|
||||||
SQLiteStatement s = mDatabase.compileStatement(sql);
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
s.bindLong(1, i);
|
|
||||||
s.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
int maxRead = 11;
|
|
||||||
int initialRead = 5;
|
|
||||||
SQLiteCursor c = (SQLiteCursor)mDatabase.rawQuery("select * from test;",
|
|
||||||
null, initialRead, maxRead);
|
|
||||||
|
|
||||||
TestObserver observer = new TestObserver(count, c);
|
|
||||||
c.registerDataSetObserver(observer);
|
|
||||||
c.getCount();
|
|
||||||
c.deactivate();
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
public void testManyRowsLong() throws Exception {
|
public void testManyRowsLong() throws Exception {
|
||||||
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);");
|
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);");
|
||||||
|
|||||||
@@ -149,6 +149,13 @@ void utf8_to_utf32(const char* src, size_t src_len, char32_t* dst);
|
|||||||
*/
|
*/
|
||||||
ssize_t utf8_to_utf16_length(const uint8_t* src, size_t srcLen);
|
ssize_t utf8_to_utf16_length(const uint8_t* src, size_t srcLen);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert UTF-8 to UTF-16 including surrogate pairs.
|
||||||
|
* Returns a pointer to the end of the string (where a null terminator might go
|
||||||
|
* if you wanted to add one).
|
||||||
|
*/
|
||||||
|
char16_t* utf8_to_utf16_no_null_terminator(const uint8_t* src, size_t srcLen, char16_t* dst);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert UTF-8 to UTF-16 including surrogate pairs. The destination buffer
|
* Convert UTF-8 to UTF-16 including surrogate pairs. The destination buffer
|
||||||
* must be large enough to hold the result as measured by utf8_to_utf16_length
|
* must be large enough to hold the result as measured by utf8_to_utf16_length
|
||||||
|
|||||||
@@ -542,11 +542,7 @@ ssize_t utf8_to_utf16_length(const uint8_t* u8str, size_t u8len)
|
|||||||
return u16measuredLen;
|
return u16measuredLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
char16_t* utf8_to_utf16_no_null_terminator(const uint8_t* u8str, size_t u8len, char16_t* u16str)
|
||||||
* Convert a UTF-8 string to UTF-16. The destination UTF-16 buffer must have
|
|
||||||
* space for NULL at the end.
|
|
||||||
*/
|
|
||||||
void utf8_to_utf16(const uint8_t* u8str, size_t u8len, char16_t* u16str)
|
|
||||||
{
|
{
|
||||||
const uint8_t* const u8end = u8str + u8len;
|
const uint8_t* const u8end = u8str + u8len;
|
||||||
const uint8_t* u8cur = u8str;
|
const uint8_t* u8cur = u8str;
|
||||||
@@ -569,7 +565,12 @@ void utf8_to_utf16(const uint8_t* u8str, size_t u8len, char16_t* u16str)
|
|||||||
|
|
||||||
u8cur += u8len;
|
u8cur += u8len;
|
||||||
}
|
}
|
||||||
*u16cur = 0;
|
return u16cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
void utf8_to_utf16(const uint8_t* u8str, size_t u8len, char16_t* u16str) {
|
||||||
|
char16_t* end = utf8_to_utf16_no_null_terminator(u8str, u8len, u16str);
|
||||||
|
*end = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user