FileDocCategorySizeDatePackage
SQLiteCursor.javaAPI DocAndroid 1.5 API19252Wed May 06 22:41:54 BST 2009android.database.sqlite

SQLiteCursor

public class SQLiteCursor extends android.database.AbstractWindowedCursor
A Cursor implementation that exposes results from a query on a {@link SQLiteDatabase}.

Fields Summary
static final String
TAG
static final int
NO_COUNT
private String
mEditTable
The name of the table to edit
private String[]
mColumns
The names of the columns in the rows
private SQLiteQuery
mQuery
The query object for the cursor
private SQLiteDatabase
mDatabase
The database the cursor was created from
private SQLiteCursorDriver
mDriver
The compiled query this cursor came from
private int
mCount
The number of rows in the cursor
private Map
mColumnNameMap
A mapping of column names to column indices, to speed up lookups
private StackTraceElement[]
mStackTraceElements
Used to find out where a cursor was allocated in case it never got released.
private int
mMaxRead
mMaxRead is the max items that each cursor window reads default to a very high value
private int
mInitialRead
private int
mCursorState
private ReentrantLock
mLock
private boolean
mPendingData
protected MainThreadNotificationHandler
mNotificationHandler
Constructors Summary
public SQLiteCursor(SQLiteDatabase db, SQLiteCursorDriver driver, String editTable, SQLiteQuery query)
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

        // The AbstractCursor constructor needs to do some setup.
        super();

        if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
            mStackTraceElements = new Exception().getStackTrace();
        }

        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();
        }
    
Methods Summary
public voidclose()

        super.close();
        deactivateCommon();
        mQuery.close();
        mDriver.cursorClosed();
    
public booleancommitUpdates(java.util.Map additionalValues)

hide
deprecated

        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;
    
public voiddeactivate()

        super.deactivate();
        deactivateCommon();
        mDriver.cursorDeactivated();
    
private voiddeactivateCommon()

        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()");
    
public booleandeleteRow()

hide
deprecated

        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;
        }
    
private voidfillWindow(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();
        } 
    
protected voidfinalize()
Release the native resources, if they haven't been released yet.

        try {
            if (mWindow != null) {
                close();
                String message = "Finalizing cursor " + this + " on " + mEditTable
                        + " that has not been deactivated or closed";
                if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
                    Log.d(TAG, message + "\nThis cursor was created in:");
                    for (StackTraceElement ste : mStackTraceElements) {
                        Log.d(TAG, "      " + ste);
                    }
                }
                SQLiteDebug.notifyActiveCursorFinalized();
                throw new IllegalStateException(message);
            } else {
                if (Config.LOGV) {
                    Log.v(TAG, "Finalizing cursor " + this + " on " + mEditTable);
                }
            }
        } finally {
            super.finalize();
        }
    
public intgetColumnIndex(java.lang.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;
        }
    
public java.lang.String[]getColumnNames()

        return mColumns;
    
public intgetCount()

        if (mCount == NO_COUNT) {
            fillWindow(0);
        }
        return mCount;
    
public SQLiteDatabasegetDatabase()

return
the SQLiteDatabase that this cursor is associated with.

        return mDatabase;
    
public booleanonMove(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;
    
private voidqueryThreadLock()

        if (mLock != null) {
            mLock.lock();            
        }
    
private voidqueryThreadUnlock()

        if (mLock != null) {
            mLock.unlock();            
        }
    
public voidregisterDataSetObserver(android.database.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();
            }
        }
        
    
public booleanrequery()

        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;
    
public voidsetLoadStyle(int initialRead, int maxRead)
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

    
                                                                                         
          
        mMaxRead = maxRead;
        mInitialRead = initialRead;
        mLock = new ReentrantLock(true);
    
public voidsetSelectionArguments(java.lang.String[] selectionArgs)
Changes the selection arguments. The new values take effect after a call to requery().

        mDriver.setBindArguments(selectionArgs);
    
public voidsetWindow(android.database.CursorWindow window)

        
        if (mWindow != null) {
            mCursorState++;
            queryThreadLock();
            try {
                mWindow.close();
            } finally {
                queryThreadUnlock();
            }
            mCount = NO_COUNT;
        }
        mWindow = window;
    
public booleansupportsUpdates()

hide
deprecated

        return super.supportsUpdates() && !TextUtils.isEmpty(mEditTable);