android support! thanks to TachiWeb devs.
This commit is contained in:
@@ -0,0 +1,414 @@
|
||||
/*
|
||||
* 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;
|
||||
import android.content.ContentResolver;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import kotlin.NotImplementedError;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
/**
|
||||
* This is an abstract cursor class that handles a lot of the common code
|
||||
* that all cursors need to deal with and is provided for convenience reasons.
|
||||
*/
|
||||
public abstract class AbstractCursor implements CrossProcessCursor {
|
||||
private static final String TAG = "Cursor";
|
||||
/**
|
||||
* @removed This field should not be used.
|
||||
*/
|
||||
protected HashMap<Long, Map<String, Object>> mUpdatedRows;
|
||||
/**
|
||||
* @removed This field should not be used.
|
||||
*/
|
||||
protected int mRowIdColumnIndex;
|
||||
/**
|
||||
* @removed This field should not be used.
|
||||
*/
|
||||
protected Long mCurrentRowID;
|
||||
/**
|
||||
* @deprecated Use {@link #getPosition()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
protected int mPos;
|
||||
/**
|
||||
* @deprecated Use {@link #isClosed()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
protected boolean mClosed;
|
||||
/**
|
||||
* @deprecated Do not use.
|
||||
*/
|
||||
@Deprecated
|
||||
protected ContentResolver mContentResolver;
|
||||
private Uri mNotifyUri;
|
||||
private final Object mSelfObserverLock = new Object();
|
||||
private ContentObserver mSelfObserver;
|
||||
private boolean mSelfObserverRegistered;
|
||||
private final DataSetObservable mDataSetObservable = new DataSetObservable();
|
||||
private final ContentObservable mContentObservable = new ContentObservable();
|
||||
private Bundle mExtras = Bundle.EMPTY;
|
||||
/* -------------------------------------------------------- */
|
||||
/* These need to be implemented by subclasses */
|
||||
@Override
|
||||
abstract public int getCount();
|
||||
@Override
|
||||
abstract public String[] getColumnNames();
|
||||
@Override
|
||||
abstract public String getString(int column);
|
||||
@Override
|
||||
abstract public short getShort(int column);
|
||||
@Override
|
||||
abstract public int getInt(int column);
|
||||
@Override
|
||||
abstract public long getLong(int column);
|
||||
@Override
|
||||
abstract public float getFloat(int column);
|
||||
@Override
|
||||
abstract public double getDouble(int column);
|
||||
@Override
|
||||
abstract public boolean isNull(int column);
|
||||
@Override
|
||||
public int getType(int column) {
|
||||
// Reflects the assumption that all commonly used field types (meaning everything
|
||||
// but blobs) are convertible to strings so it should be safe to call
|
||||
// getString to retrieve them.
|
||||
return FIELD_TYPE_STRING;
|
||||
}
|
||||
// TODO implement getBlob in all cursor types
|
||||
@Override
|
||||
public byte[] getBlob(int column) {
|
||||
throw new UnsupportedOperationException("getBlob is not supported");
|
||||
}
|
||||
/* -------------------------------------------------------- */
|
||||
/* Methods that may optionally be implemented by subclasses */
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@Override
|
||||
public CursorWindow getWindow() {
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return getColumnNames().length;
|
||||
}
|
||||
@Override
|
||||
public void deactivate() {
|
||||
onDeactivateOrClose();
|
||||
}
|
||||
/** @hide */
|
||||
protected void onDeactivateOrClose() {
|
||||
if (mSelfObserver != null) {
|
||||
mContentResolver.unregisterContentObserver(mSelfObserver);
|
||||
mSelfObserverRegistered = false;
|
||||
}
|
||||
mDataSetObservable.notifyInvalidated();
|
||||
}
|
||||
@Override
|
||||
public boolean requery() {
|
||||
if (mSelfObserver != null && mSelfObserverRegistered == false) {
|
||||
mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
|
||||
mSelfObserverRegistered = true;
|
||||
}
|
||||
mDataSetObservable.notifyChanged();
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return mClosed;
|
||||
}
|
||||
@Override
|
||||
public void close() {
|
||||
mClosed = true;
|
||||
mContentObservable.unregisterAll();
|
||||
onDeactivateOrClose();
|
||||
}
|
||||
/**
|
||||
* This function is called every time the cursor is successfully scrolled
|
||||
* to a new position, giving the subclass a chance to update any state it
|
||||
* may have. If it returns false the move function will also do so and the
|
||||
* cursor will scroll to the beforeFirst position.
|
||||
*
|
||||
* @param oldPosition the position that we're moving from
|
||||
* @param newPosition the position that we're moving to
|
||||
* @return true if the move is successful, false otherwise
|
||||
*/
|
||||
@Override
|
||||
public boolean onMove(int oldPosition, int newPosition) {
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
|
||||
// Default implementation, uses getString
|
||||
String result = getString(columnIndex);
|
||||
if (result != null) {
|
||||
char[] data = buffer.data;
|
||||
if (data == null || data.length < result.length()) {
|
||||
buffer.data = result.toCharArray();
|
||||
} else {
|
||||
result.getChars(0, result.length(), data, 0);
|
||||
}
|
||||
buffer.sizeCopied = result.length();
|
||||
} else {
|
||||
buffer.sizeCopied = 0;
|
||||
}
|
||||
}
|
||||
/* -------------------------------------------------------- */
|
||||
/* Implementation */
|
||||
public AbstractCursor() {
|
||||
mPos = -1;
|
||||
}
|
||||
@Override
|
||||
public final int getPosition() {
|
||||
return mPos;
|
||||
}
|
||||
@Override
|
||||
public final boolean moveToPosition(int position) {
|
||||
// Make sure position isn't past the end of the cursor
|
||||
final int count = getCount();
|
||||
if (position >= count) {
|
||||
mPos = count;
|
||||
return false;
|
||||
}
|
||||
// Make sure position isn't before the beginning of the cursor
|
||||
if (position < 0) {
|
||||
mPos = -1;
|
||||
return false;
|
||||
}
|
||||
// Check for no-op moves, and skip the rest of the work for them
|
||||
if (position == mPos) {
|
||||
return true;
|
||||
}
|
||||
boolean result = onMove(mPos, position);
|
||||
if (result == false) {
|
||||
mPos = -1;
|
||||
} else {
|
||||
mPos = position;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@Override
|
||||
public void fillWindow(int position, CursorWindow window) {
|
||||
DatabaseUtils.cursorFillWindow(this, position, window);
|
||||
}
|
||||
@Override
|
||||
public final boolean move(int offset) {
|
||||
return moveToPosition(mPos + offset);
|
||||
}
|
||||
@Override
|
||||
public final boolean moveToFirst() {
|
||||
return moveToPosition(0);
|
||||
}
|
||||
@Override
|
||||
public final boolean moveToLast() {
|
||||
return moveToPosition(getCount() - 1);
|
||||
}
|
||||
@Override
|
||||
public final boolean moveToNext() {
|
||||
return moveToPosition(mPos + 1);
|
||||
}
|
||||
@Override
|
||||
public final boolean moveToPrevious() {
|
||||
return moveToPosition(mPos - 1);
|
||||
}
|
||||
@Override
|
||||
public final boolean isFirst() {
|
||||
return mPos == 0 && getCount() != 0;
|
||||
}
|
||||
@Override
|
||||
public final boolean isLast() {
|
||||
int cnt = getCount();
|
||||
return mPos == (cnt - 1) && cnt != 0;
|
||||
}
|
||||
@Override
|
||||
public final boolean isBeforeFirst() {
|
||||
if (getCount() == 0) {
|
||||
return true;
|
||||
}
|
||||
return mPos == -1;
|
||||
}
|
||||
@Override
|
||||
public final boolean isAfterLast() {
|
||||
if (getCount() == 0) {
|
||||
return true;
|
||||
}
|
||||
return mPos == getCount();
|
||||
}
|
||||
@Override
|
||||
public int getColumnIndex(String columnName) {
|
||||
// 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);
|
||||
}
|
||||
String columnNames[] = getColumnNames();
|
||||
int length = columnNames.length;
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (columnNames[i].equalsIgnoreCase(columnName)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
if (false) {
|
||||
if (getCount() > 0) {
|
||||
Log.w("AbstractCursor", "Unknown column " + columnName);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@Override
|
||||
public int getColumnIndexOrThrow(String columnName) {
|
||||
final int index = getColumnIndex(columnName);
|
||||
if (index < 0) {
|
||||
throw new IllegalArgumentException("column '" + columnName + "' does not exist");
|
||||
}
|
||||
return index;
|
||||
}
|
||||
@Override
|
||||
public String getColumnName(int columnIndex) {
|
||||
return getColumnNames()[columnIndex];
|
||||
}
|
||||
@Override
|
||||
public void registerContentObserver(ContentObserver observer) {
|
||||
mContentObservable.registerObserver(observer);
|
||||
}
|
||||
@Override
|
||||
public void unregisterContentObserver(ContentObserver observer) {
|
||||
// cursor will unregister all observers when it close
|
||||
if (!mClosed) {
|
||||
mContentObservable.unregisterObserver(observer);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void registerDataSetObserver(DataSetObserver observer) {
|
||||
mDataSetObservable.registerObserver(observer);
|
||||
}
|
||||
@Override
|
||||
public void unregisterDataSetObserver(DataSetObserver observer) {
|
||||
mDataSetObservable.unregisterObserver(observer);
|
||||
}
|
||||
/**
|
||||
* Subclasses must call this method when they finish committing updates to notify all
|
||||
* observers.
|
||||
*
|
||||
* @param selfChange
|
||||
*/
|
||||
protected void onChange(boolean selfChange) {
|
||||
synchronized (mSelfObserverLock) {
|
||||
mContentObservable.dispatchChange(selfChange, null);
|
||||
if (mNotifyUri != null && selfChange) {
|
||||
mContentResolver.notifyChange(mNotifyUri, mSelfObserver);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Specifies a content URI to watch for changes.
|
||||
*
|
||||
* @param cr The content resolver from the caller's context.
|
||||
* @param notifyUri The URI to watch for changes. This can be a
|
||||
* specific row URI, or a base URI for a whole class of content.
|
||||
*/
|
||||
@Override
|
||||
public void setNotificationUri(ContentResolver cr, Uri notifyUri) {
|
||||
throw new NotImplementedError("Not implemented!");
|
||||
}
|
||||
@Override
|
||||
public Uri getNotificationUri() {
|
||||
synchronized (mSelfObserverLock) {
|
||||
return mNotifyUri;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean getWantsAllOnMoveCalls() {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public void setExtras(Bundle extras) {
|
||||
mExtras = (extras == null) ? Bundle.EMPTY : extras;
|
||||
}
|
||||
@Override
|
||||
public Bundle getExtras() {
|
||||
return mExtras;
|
||||
}
|
||||
@Override
|
||||
public Bundle respond(Bundle extras) {
|
||||
return Bundle.EMPTY;
|
||||
}
|
||||
/**
|
||||
* @deprecated Always returns false since Cursors do not support updating rows
|
||||
*/
|
||||
@Deprecated
|
||||
protected boolean isFieldUpdated(int columnIndex) {
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* @deprecated Always returns null since Cursors do not support updating rows
|
||||
*/
|
||||
@Deprecated
|
||||
protected Object getUpdatedField(int columnIndex) {
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* This function throws CursorIndexOutOfBoundsException if
|
||||
* the cursor position is out of bounds. Subclass implementations of
|
||||
* the get functions should call this before attempting
|
||||
* to retrieve data.
|
||||
*
|
||||
* @throws CursorIndexOutOfBoundsException
|
||||
*/
|
||||
protected void checkPosition() {
|
||||
if (-1 == mPos || getCount() == mPos) {
|
||||
throw new CursorIndexOutOfBoundsException(mPos, getCount());
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected void finalize() {
|
||||
if (mSelfObserver != null && mSelfObserverRegistered == true) {
|
||||
mContentResolver.unregisterContentObserver(mSelfObserver);
|
||||
}
|
||||
try {
|
||||
if (!mClosed) close();
|
||||
} catch(Exception e) { }
|
||||
}
|
||||
/**
|
||||
* Cursors use this class to track changes others make to their URI.
|
||||
*/
|
||||
protected static class SelfContentObserver extends ContentObserver {
|
||||
WeakReference<AbstractCursor> mCursor;
|
||||
public SelfContentObserver(AbstractCursor cursor) {
|
||||
super(null);
|
||||
mCursor = new WeakReference<AbstractCursor>(cursor);
|
||||
}
|
||||
@Override
|
||||
public boolean deliverSelfNotifications() {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
AbstractCursor cursor = mCursor.get();
|
||||
if (cursor != null) {
|
||||
cursor.onChange(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import xyz.nulldev.androidcompat.db.ScrollableResultSet;
|
||||
|
||||
/**
|
||||
* A base class for Cursors that store their data in {@link CursorWindow}s.
|
||||
* <p>
|
||||
* The cursor owns the cursor window it uses. When the cursor is closed,
|
||||
* its window is also closed. Likewise, when the window used by the cursor is
|
||||
* changed, its old window is closed. This policy of strict ownership ensures
|
||||
* that cursor windows are not leaked.
|
||||
* </p><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 {
|
||||
/**
|
||||
* The cursor window owned by this cursor.
|
||||
*/
|
||||
protected CursorWindow mWindow;
|
||||
@Override
|
||||
public byte[] getBlob(int columnIndex) {
|
||||
checkPosition();
|
||||
return mWindow.getBlob(mPos, columnIndex);
|
||||
}
|
||||
@Override
|
||||
public String getString(int columnIndex) {
|
||||
checkPosition();
|
||||
return mWindow.getString(mPos, columnIndex);
|
||||
}
|
||||
@Override
|
||||
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
|
||||
checkPosition();
|
||||
mWindow.copyStringToBuffer(mPos, columnIndex, buffer);
|
||||
}
|
||||
@Override
|
||||
public short getShort(int columnIndex) {
|
||||
checkPosition();
|
||||
return mWindow.getShort(mPos, columnIndex);
|
||||
}
|
||||
@Override
|
||||
public int getInt(int columnIndex) {
|
||||
checkPosition();
|
||||
return mWindow.getInt(mPos, columnIndex);
|
||||
}
|
||||
@Override
|
||||
public long getLong(int columnIndex) {
|
||||
checkPosition();
|
||||
return mWindow.getLong(mPos, columnIndex);
|
||||
}
|
||||
@Override
|
||||
public float getFloat(int columnIndex) {
|
||||
checkPosition();
|
||||
return mWindow.getFloat(mPos, columnIndex);
|
||||
}
|
||||
@Override
|
||||
public double getDouble(int columnIndex) {
|
||||
checkPosition();
|
||||
return mWindow.getDouble(mPos, columnIndex);
|
||||
}
|
||||
@Override
|
||||
public boolean isNull(int columnIndex) {
|
||||
checkPosition();
|
||||
return mWindow.getType(mPos, columnIndex) == Cursor.FIELD_TYPE_NULL;
|
||||
}
|
||||
/**
|
||||
* @deprecated Use {@link #getType}
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isBlob(int columnIndex) {
|
||||
return getType(columnIndex) == Cursor.FIELD_TYPE_BLOB;
|
||||
}
|
||||
/**
|
||||
* @deprecated Use {@link #getType}
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isString(int columnIndex) {
|
||||
return getType(columnIndex) == Cursor.FIELD_TYPE_STRING;
|
||||
}
|
||||
/**
|
||||
* @deprecated Use {@link #getType}
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isLong(int columnIndex) {
|
||||
return getType(columnIndex) == Cursor.FIELD_TYPE_INTEGER;
|
||||
}
|
||||
/**
|
||||
* @deprecated Use {@link #getType}
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isFloat(int columnIndex) {
|
||||
return getType(columnIndex) == Cursor.FIELD_TYPE_FLOAT;
|
||||
}
|
||||
@Override
|
||||
public int getType(int columnIndex) {
|
||||
checkPosition();
|
||||
return mWindow.getType(mPos, columnIndex);
|
||||
}
|
||||
@Override
|
||||
protected void checkPosition() {
|
||||
super.checkPosition();
|
||||
|
||||
if (mWindow == null) {
|
||||
throw new StaleDataException("Attempting to access a closed CursorWindow." +
|
||||
"Most probable cause: cursor is deactivated prior to calling this method.");
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public CursorWindow getWindow() {
|
||||
return mWindow;
|
||||
}
|
||||
/**
|
||||
* Sets a new cursor window for the cursor to use.
|
||||
* <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) {
|
||||
if (window != mWindow) {
|
||||
closeWindow();
|
||||
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() {
|
||||
return mWindow != null;
|
||||
}
|
||||
/**
|
||||
* Closes the cursor window and sets {@link #mWindow} to null.
|
||||
* @hide
|
||||
*/
|
||||
protected void closeWindow() {
|
||||
if (mWindow != null) {
|
||||
mWindow.close();
|
||||
mWindow = null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* If there is a window, clear it.
|
||||
* Otherwise, creates a new window.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
protected void clearOrCreateWindow(ScrollableResultSet rs) {
|
||||
if (mWindow == null) {
|
||||
mWindow = new CursorWindow(rs);
|
||||
} else {
|
||||
mWindow.clear();
|
||||
}
|
||||
}
|
||||
/** @hide */
|
||||
@Override
|
||||
protected void onDeactivateOrClose() {
|
||||
super.onDeactivateOrClose();
|
||||
closeWindow();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
/**
|
||||
* This is used for {@link Cursor#copyStringToBuffer}
|
||||
*/
|
||||
public final class CharArrayBuffer {
|
||||
public CharArrayBuffer(int size) {
|
||||
data = new char[size];
|
||||
}
|
||||
|
||||
public CharArrayBuffer(char[] buf) {
|
||||
data = buf;
|
||||
}
|
||||
|
||||
public char[] data; // In and out parameter
|
||||
public int sizeCopied; // Out parameter
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* A specialization of {@link Observable} for {@link ContentObserver}
|
||||
* that provides methods for sending notifications to a list of
|
||||
* {@link ContentObserver} objects.
|
||||
*/
|
||||
public class ContentObservable extends Observable<ContentObserver> {
|
||||
// Even though the generic method defined in Observable would be perfectly
|
||||
// fine on its own, we can't delete this overridden method because it would
|
||||
// potentially break binary compatibility with existing applications.
|
||||
@Override
|
||||
public void registerObserver(ContentObserver observer) {
|
||||
super.registerObserver(observer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes {@link ContentObserver#dispatchChange(boolean)} on each observer.
|
||||
* <p>
|
||||
* If <code>selfChange</code> is true, only delivers the notification
|
||||
* to the observer if it has indicated that it wants to receive self-change
|
||||
* notifications by implementing {@link ContentObserver#deliverSelfNotifications}
|
||||
* to return true.
|
||||
* </p>
|
||||
*
|
||||
* @param selfChange True if this is a self-change notification.
|
||||
*
|
||||
* @deprecated Use {@link #dispatchChange(boolean, Uri)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void dispatchChange(boolean selfChange) {
|
||||
dispatchChange(selfChange, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes {@link ContentObserver#dispatchChange(boolean, Uri)} on each observer.
|
||||
* Includes the changed content Uri when available.
|
||||
* <p>
|
||||
* If <code>selfChange</code> is true, only delivers the notification
|
||||
* to the observer if it has indicated that it wants to receive self-change
|
||||
* notifications by implementing {@link ContentObserver#deliverSelfNotifications}
|
||||
* to return true.
|
||||
* </p>
|
||||
*
|
||||
* @param selfChange True if this is a self-change notification.
|
||||
* @param uri The Uri of the changed content, or null if unknown.
|
||||
*/
|
||||
public void dispatchChange(boolean selfChange, Uri uri) {
|
||||
synchronized(mObservers) {
|
||||
for (ContentObserver observer : mObservers) {
|
||||
if (!selfChange || observer.deliverSelfNotifications()) {
|
||||
observer.dispatchChange(selfChange, uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes {@link ContentObserver#onChange} on each observer.
|
||||
*
|
||||
* @param selfChange True if this is a self-change notification.
|
||||
*
|
||||
* @deprecated Use {@link #dispatchChange} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void notifyChange(boolean selfChange) {
|
||||
synchronized(mObservers) {
|
||||
for (ContentObserver observer : mObservers) {
|
||||
observer.onChange(selfChange, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,493 @@
|
||||
|
||||
/*
|
||||
* 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;
|
||||
import android.database.sqlite.SQLiteClosable;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.os.Parcel;
|
||||
import kotlin.NotImplementedError;
|
||||
import xyz.nulldev.androidcompat.db.ScrollableResultSet;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* A buffer containing multiple cursor rows.
|
||||
* <p>
|
||||
* A {@link CursorWindow} is read-write when initially created and used locally.
|
||||
* When sent to a remote process (by writing it to a {@link Parcel}), the remote process
|
||||
* receives a read-only view of the cursor window. Typically the cursor window
|
||||
* will be allocated by the producer, filled with data, and then sent to the
|
||||
* consumer for reading.
|
||||
* </p>
|
||||
*/
|
||||
public class CursorWindow extends SQLiteClosable {
|
||||
private ScrollableResultSet resultSet;
|
||||
public CursorWindow(ScrollableResultSet resultSet) {
|
||||
this.resultSet = resultSet;
|
||||
}
|
||||
/**
|
||||
* Clears out the existing contents of the window, making it safe to reuse
|
||||
* for new data.
|
||||
* <p>
|
||||
* The start position ({@link #getStartPosition()}), number of rows ({@link #getNumRows()}),
|
||||
* and number of columns in the cursor are all reset to zero.
|
||||
* </p>
|
||||
*/
|
||||
public void clear() {
|
||||
}
|
||||
/**
|
||||
* Gets the start position of this cursor window.
|
||||
* <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}.
|
||||
* </p>
|
||||
*
|
||||
* @return The zero-based start position.
|
||||
*/
|
||||
public int getStartPosition() {
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* Sets the start position of this cursor window.
|
||||
* <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}.
|
||||
* </p>
|
||||
*
|
||||
* @param pos The new zero-based start position.
|
||||
*/
|
||||
public void setStartPosition(int pos) {
|
||||
}
|
||||
/**
|
||||
* Gets the number of rows in this window.
|
||||
*
|
||||
* @return The number of rows in this cursor window.
|
||||
*/
|
||||
public int getNumRows() {
|
||||
return resultSet.getResultSetLength();
|
||||
}
|
||||
/**
|
||||
* Sets the number of columns in this window.
|
||||
* <p>
|
||||
* This method must be called before any rows are added to the window, otherwise
|
||||
* it will fail to set the number of columns if it differs from the current number
|
||||
* of columns.
|
||||
* </p>
|
||||
*
|
||||
* @param columnNum The new number of columns.
|
||||
* @return True if successful.
|
||||
*/
|
||||
public boolean setNumColumns(int columnNum) {
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Allocates a new row at the end of this cursor window.
|
||||
*
|
||||
* @return True if successful, false if the cursor window is out of memory.
|
||||
*/
|
||||
public boolean allocRow() {
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Frees the last row in this cursor window.
|
||||
*/
|
||||
public void freeLastRow(){
|
||||
}
|
||||
/**
|
||||
* Returns true if the field at the specified row and column index
|
||||
* has type {@link Cursor#FIELD_TYPE_NULL}.
|
||||
*
|
||||
* @param row The zero-based row index.
|
||||
* @param column The zero-based column index.
|
||||
* @return True if the field has type {@link Cursor#FIELD_TYPE_NULL}.
|
||||
* @deprecated Use {@link #getType(int, int)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isNull(int row, int column) {
|
||||
return getType(row, column) == Cursor.FIELD_TYPE_NULL;
|
||||
}
|
||||
/**
|
||||
* Returns true if the field at the specified row and column index
|
||||
* has type {@link Cursor#FIELD_TYPE_BLOB} or {@link Cursor#FIELD_TYPE_NULL}.
|
||||
*
|
||||
* @param row The zero-based row index.
|
||||
* @param column The zero-based column index.
|
||||
* @return True if the field has type {@link Cursor#FIELD_TYPE_BLOB} or
|
||||
* {@link Cursor#FIELD_TYPE_NULL}.
|
||||
* @deprecated Use {@link #getType(int, int)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isBlob(int row, int column) {
|
||||
int type = getType(row, column);
|
||||
return type == Cursor.FIELD_TYPE_BLOB || type == Cursor.FIELD_TYPE_NULL;
|
||||
}
|
||||
/**
|
||||
* Returns true if the field at the specified row and column index
|
||||
* has type {@link Cursor#FIELD_TYPE_INTEGER}.
|
||||
*
|
||||
* @param row The zero-based row index.
|
||||
* @param column The zero-based column index.
|
||||
* @return True if the field has type {@link Cursor#FIELD_TYPE_INTEGER}.
|
||||
* @deprecated Use {@link #getType(int, int)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isLong(int row, int column) {
|
||||
return getType(row, column) == Cursor.FIELD_TYPE_INTEGER;
|
||||
}
|
||||
/**
|
||||
* Returns true if the field at the specified row and column index
|
||||
* has type {@link Cursor#FIELD_TYPE_FLOAT}.
|
||||
*
|
||||
* @param row The zero-based row index.
|
||||
* @param column The zero-based column index.
|
||||
* @return True if the field has type {@link Cursor#FIELD_TYPE_FLOAT}.
|
||||
* @deprecated Use {@link #getType(int, int)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isFloat(int row, int column) {
|
||||
return getType(row, column) == Cursor.FIELD_TYPE_FLOAT;
|
||||
}
|
||||
/**
|
||||
* Returns true if the field at the specified row and column index
|
||||
* has type {@link Cursor#FIELD_TYPE_STRING} or {@link Cursor#FIELD_TYPE_NULL}.
|
||||
*
|
||||
* @param row The zero-based row index.
|
||||
* @param column The zero-based column index.
|
||||
* @return True if the field has type {@link Cursor#FIELD_TYPE_STRING}
|
||||
* or {@link Cursor#FIELD_TYPE_NULL}.
|
||||
* @deprecated Use {@link #getType(int, int)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isString(int row, int column) {
|
||||
int type = getType(row, column);
|
||||
return type == Cursor.FIELD_TYPE_STRING || type == Cursor.FIELD_TYPE_NULL;
|
||||
}
|
||||
/**
|
||||
* Returns the type of the field at the specified row and column index.
|
||||
* <p>
|
||||
* The returned field types are:
|
||||
* <ul>
|
||||
* <li>{@link Cursor#FIELD_TYPE_NULL}</li>
|
||||
* <li>{@link Cursor#FIELD_TYPE_INTEGER}</li>
|
||||
* <li>{@link Cursor#FIELD_TYPE_FLOAT}</li>
|
||||
* <li>{@link Cursor#FIELD_TYPE_STRING}</li>
|
||||
* <li>{@link Cursor#FIELD_TYPE_BLOB}</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @param row The zero-based row index.
|
||||
* @param column The zero-based column index.
|
||||
* @return The field type.
|
||||
*/
|
||||
public int getType(int row, int column) {
|
||||
acquireReference();
|
||||
try {
|
||||
jumpToRow(row);
|
||||
String clazz = resultSet.getMetaData().getColumnClassName(column + 1);
|
||||
resultSet.getObject(column + 1);
|
||||
if(resultSet.wasNull())
|
||||
return Cursor.FIELD_TYPE_NULL;
|
||||
if(clazz.equals(String.class.getName()))
|
||||
return Cursor.FIELD_TYPE_STRING;
|
||||
else if(clazz.equals(Integer.class.getName()) || clazz.equals(Long.class.getName()) || clazz.equals(Short.class.getName()) || clazz.equals(Byte.class.getName()) || clazz.equals(Boolean.class.getName()))
|
||||
return Cursor.FIELD_TYPE_INTEGER;
|
||||
else if(clazz.equals(Double.class.getName()) || clazz.equals(Float.class.getName()))
|
||||
return Cursor.FIELD_TYPE_FLOAT;
|
||||
else
|
||||
throw new SQLiteException("Unknown field type: " + clazz);
|
||||
} catch (SQLException e) {
|
||||
throw new SQLiteException("Failed to get type of field at: (" + row + ", " + column + ")!", e);
|
||||
} finally {
|
||||
releaseReference();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Gets the value of the field at the specified row and column index as a byte array.
|
||||
* <p>
|
||||
* The result is determined as follows:
|
||||
* <ul>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
|
||||
* is <code>null</code>.</li>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then the result
|
||||
* is the blob value.</li>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
|
||||
* is the array of bytes that make up the internal representation of the
|
||||
* string value.</li>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER} or
|
||||
* {@link Cursor#FIELD_TYPE_FLOAT}, then a {@link SQLiteException} is thrown.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @param row The zero-based row index.
|
||||
* @param column The zero-based column index.
|
||||
* @return The value of the field as a byte array.
|
||||
*/
|
||||
public byte[] getBlob(int row, int column) {
|
||||
throw new NotImplementedError("Not implemented!");
|
||||
}
|
||||
/**
|
||||
* Gets the value of the field at the specified row and column index as a string.
|
||||
* <p>
|
||||
* The result is determined as follows:
|
||||
* <ul>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
|
||||
* is <code>null</code>.</li>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
|
||||
* is the string value.</li>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
|
||||
* is a string representation of the integer in decimal, obtained by formatting the
|
||||
* value with the <code>printf</code> family of functions using
|
||||
* format specifier <code>%lld</code>.</li>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
|
||||
* is a string representation of the floating-point value in decimal, obtained by
|
||||
* formatting the value with the <code>printf</code> family of functions using
|
||||
* format specifier <code>%g</code>.</li>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
|
||||
* {@link SQLiteException} is thrown.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @param row The zero-based row index.
|
||||
* @param column The zero-based column index.
|
||||
* @return The value of the field as a string.
|
||||
*/
|
||||
public String getString(int row, int column) {
|
||||
acquireReference();
|
||||
try {
|
||||
jumpToRow(row);
|
||||
return resultSet.getString(column + 1);
|
||||
} finally {
|
||||
releaseReference();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Copies the text of the field at the specified row and column index into
|
||||
* a {@link CharArrayBuffer}.
|
||||
* <p>
|
||||
* The buffer is populated as follows:
|
||||
* <ul>
|
||||
* <li>If the buffer is too small for the value to be copied, then it is
|
||||
* automatically resized.</li>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the buffer
|
||||
* is set to an empty string.</li>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the buffer
|
||||
* is set to the contents of the string.</li>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the buffer
|
||||
* is set to a string representation of the integer in decimal, obtained by formatting the
|
||||
* value with the <code>printf</code> family of functions using
|
||||
* format specifier <code>%lld</code>.</li>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the buffer is
|
||||
* set to a string representation of the floating-point value in decimal, obtained by
|
||||
* formatting the value with the <code>printf</code> family of functions using
|
||||
* format specifier <code>%g</code>.</li>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
|
||||
* {@link SQLiteException} is thrown.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @param row The zero-based row index.
|
||||
* @param column The zero-based column index.
|
||||
* @param buffer The {@link CharArrayBuffer} to hold the string. It is automatically
|
||||
* resized if the requested string is larger than the buffer's current capacity.
|
||||
*/
|
||||
public void copyStringToBuffer(int row, int column, CharArrayBuffer buffer) {
|
||||
if (buffer == null) {
|
||||
throw new IllegalArgumentException("CharArrayBuffer should not be null");
|
||||
}
|
||||
acquireReference();
|
||||
try {
|
||||
jumpToRow(row);
|
||||
buffer.data = resultSet.getString(column + 1).toCharArray();
|
||||
} finally {
|
||||
releaseReference();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Gets the value of the field at the specified row and column index as a <code>long</code>.
|
||||
* <p>
|
||||
* The result is determined as follows:
|
||||
* <ul>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
|
||||
* is <code>0L</code>.</li>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
|
||||
* is the value obtained by parsing the string value with <code>strtoll</code>.
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
|
||||
* is the <code>long</code> value.</li>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
|
||||
* is the floating-point value converted to a <code>long</code>.</li>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
|
||||
* {@link SQLiteException} is thrown.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @param row The zero-based row index.
|
||||
* @param column The zero-based column index.
|
||||
* @return The value of the field as a <code>long</code>.
|
||||
*/
|
||||
public long getLong(int row, int column) {
|
||||
acquireReference();
|
||||
try {
|
||||
jumpToRow(row);
|
||||
return resultSet.getLong(column + 1);
|
||||
} finally {
|
||||
releaseReference();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Gets the value of the field at the specified row and column index as a
|
||||
* <code>double</code>.
|
||||
* <p>
|
||||
* The result is determined as follows:
|
||||
* <ul>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
|
||||
* is <code>0.0</code>.</li>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
|
||||
* is the value obtained by parsing the string value with <code>strtod</code>.
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
|
||||
* is the integer value converted to a <code>double</code>.</li>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
|
||||
* is the <code>double</code> value.</li>
|
||||
* <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
|
||||
* {@link SQLiteException} is thrown.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @param row The zero-based row index.
|
||||
* @param column The zero-based column index.
|
||||
* @return The value of the field as a <code>double</code>.
|
||||
*/
|
||||
public double getDouble(int row, int column) {
|
||||
acquireReference();
|
||||
try {
|
||||
jumpToRow(row);
|
||||
return resultSet.getDouble(column + 1);
|
||||
} finally {
|
||||
releaseReference();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Gets the value of the field at the specified row and column index as a
|
||||
* <code>short</code>.
|
||||
* <p>
|
||||
* The result is determined by invoking {@link #getLong} and converting the
|
||||
* result to <code>short</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param row The zero-based row index.
|
||||
* @param column The zero-based column index.
|
||||
* @return The value of the field as a <code>short</code>.
|
||||
*/
|
||||
public short getShort(int row, int column) {
|
||||
return (short) getLong(row, column);
|
||||
}
|
||||
/**
|
||||
* Gets the value of the field at the specified row and column index as an
|
||||
* <code>int</code>.
|
||||
* <p>
|
||||
* The result is determined by invoking {@link #getLong} and converting the
|
||||
* result to <code>int</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param row The zero-based row index.
|
||||
* @param column The zero-based column index.
|
||||
* @return The value of the field as an <code>int</code>.
|
||||
*/
|
||||
public int getInt(int row, int column) {
|
||||
return (int) getLong(row, column);
|
||||
}
|
||||
/**
|
||||
* Gets the value of the field at the specified row and column index as a
|
||||
* <code>float</code>.
|
||||
* <p>
|
||||
* The result is determined by invoking {@link #getDouble} and converting the
|
||||
* result to <code>float</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param row The zero-based row index.
|
||||
* @param column The zero-based column index.
|
||||
* @return The value of the field as an <code>float</code>.
|
||||
*/
|
||||
public float getFloat(int row, int column) {
|
||||
return (float) getDouble(row, column);
|
||||
}
|
||||
/**
|
||||
* Copies a byte array into the field at the specified row and column index.
|
||||
*
|
||||
* @param value The value to store.
|
||||
* @param row The zero-based row index.
|
||||
* @param column The zero-based column index.
|
||||
* @return True if successful.
|
||||
*/
|
||||
public boolean putBlob(byte[] value, int row, int column) {
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Copies a string into the field at the specified row and column index.
|
||||
*
|
||||
* @param value The value to store.
|
||||
* @param row The zero-based row index.
|
||||
* @param column The zero-based column index.
|
||||
* @return True if successful.
|
||||
*/
|
||||
public boolean putString(String value, int row, int column) {
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Puts a long integer into the field at the specified row and column index.
|
||||
*
|
||||
* @param value The value to store.
|
||||
* @param row The zero-based row index.
|
||||
* @param column The zero-based column index.
|
||||
* @return True if successful.
|
||||
*/
|
||||
public boolean putLong(long value, int row, int column) {
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Puts a double-precision floating point value into the field at the
|
||||
* specified row and column index.
|
||||
*
|
||||
* @param value The value to store.
|
||||
* @param row The zero-based row index.
|
||||
* @param column The zero-based column index.
|
||||
* @return True if successful.
|
||||
*/
|
||||
public boolean putDouble(double value, int row, int column) {
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Puts a null value into the field at the specified row and column index.
|
||||
*
|
||||
* @param row The zero-based row index.
|
||||
* @param column The zero-based column index.
|
||||
* @return True if successful.
|
||||
*/
|
||||
public boolean putNull(int row, int column) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAllReferencesReleased() {
|
||||
}
|
||||
|
||||
private void jumpToRow(int row) {
|
||||
// TODO Optimize
|
||||
resultSet.first();
|
||||
for(int i = 0; i < row; i++) {
|
||||
resultSet.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* A specialization of {@link Observable} for {@link DataSetObserver}
|
||||
* that provides methods for sending notifications to a list of
|
||||
* {@link DataSetObserver} objects.
|
||||
*/
|
||||
public class DataSetObservable extends Observable<DataSetObserver> {
|
||||
/**
|
||||
* Invokes {@link DataSetObserver#onChanged} on each observer.
|
||||
* Called when the contents of the data set have changed. The recipient
|
||||
* will obtain the new contents the next time it queries the data set.
|
||||
*/
|
||||
public void notifyChanged() {
|
||||
synchronized(mObservers) {
|
||||
// since onChanged() is implemented by the app, it could do anything, including
|
||||
// removing itself from {@link mObservers} - and that could cause problems if
|
||||
// an iterator is used on the ArrayList {@link mObservers}.
|
||||
// to avoid such problems, just march thru the list in the reverse order.
|
||||
for (int i = mObservers.size() - 1; i >= 0; i--) {
|
||||
mObservers.get(i).onChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes {@link DataSetObserver#onInvalidated} on each observer.
|
||||
* Called when the data set is no longer valid and cannot be queried again,
|
||||
* such as when the data set has been closed.
|
||||
*/
|
||||
public void notifyInvalidated() {
|
||||
synchronized (mObservers) {
|
||||
for (int i = mObservers.size() - 1; i >= 0; i--) {
|
||||
mObservers.get(i).onInvalidated();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package android.database;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
/**
|
||||
* Default class used to define the action to take when database corruption is reported
|
||||
* by sqlite.
|
||||
* <p>
|
||||
* An application can specify an implementation of {@link DatabaseErrorHandler} on the
|
||||
* following:
|
||||
* <ul>
|
||||
* <li>{@link SQLiteDatabase#openOrCreateDatabase(String,
|
||||
* android.database.sqlite.SQLiteDatabase.CursorFactory, DatabaseErrorHandler)}</li>
|
||||
* <li>{@link SQLiteDatabase#openDatabase(String,
|
||||
* android.database.sqlite.SQLiteDatabase.CursorFactory, int, DatabaseErrorHandler)}</li>
|
||||
* </ul>
|
||||
* The specified {@link DatabaseErrorHandler} is used to handle database corruption errors, if they
|
||||
* occur.
|
||||
* <p>
|
||||
* If null is specified for the DatabaseErrorHandler param in the above calls, this class is used
|
||||
* as the default {@link DatabaseErrorHandler}.
|
||||
*/
|
||||
public final class DefaultDatabaseErrorHandler implements DatabaseErrorHandler {
|
||||
private static final String TAG = "DefaultDatabaseErrorHandler";
|
||||
/**
|
||||
* defines the default method to be invoked when database corruption is detected.
|
||||
* @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption
|
||||
* is detected.
|
||||
*/
|
||||
public void onCorruption(SQLiteDatabase dbObj) {
|
||||
Log.e(TAG, "Corruption reported by sqlite on database: " + dbObj.getPath());
|
||||
// is the corruption detected even before database could be 'opened'?
|
||||
if (!dbObj.isOpen()) {
|
||||
// database files are not even openable. delete this database file.
|
||||
// NOTE if the database has attached databases, then any of them could be corrupt.
|
||||
// and not deleting all of them could cause corrupted database file to remain and
|
||||
// make the application crash on database open operation. To avoid this problem,
|
||||
// the application should provide its own {@link DatabaseErrorHandler} impl class
|
||||
// to delete ALL files of the database (including the attached databases).
|
||||
deleteDatabaseFile(dbObj.getPath());
|
||||
return;
|
||||
}
|
||||
List<Pair<String, String>> attachedDbs = null;
|
||||
try {
|
||||
// Close the database, which will cause subsequent operations to fail.
|
||||
// before that, get the attached database list first.
|
||||
try {
|
||||
attachedDbs = dbObj.getAttachedDbs();
|
||||
} catch (SQLiteException e) {
|
||||
/* ignore */
|
||||
}
|
||||
try {
|
||||
dbObj.close();
|
||||
} catch (SQLiteException e) {
|
||||
/* ignore */
|
||||
}
|
||||
} finally {
|
||||
// Delete all files of this corrupt database and/or attached databases
|
||||
if (attachedDbs != null) {
|
||||
for (Pair<String, String> p : attachedDbs) {
|
||||
deleteDatabaseFile(p.second);
|
||||
}
|
||||
} else {
|
||||
// attachedDbs = null is possible when the database is so corrupt that even
|
||||
// "PRAGMA database_list;" also fails. delete the main database file
|
||||
deleteDatabaseFile(dbObj.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
private void deleteDatabaseFile(String fileName) {
|
||||
if (fileName.equalsIgnoreCase(":memory:") || fileName.trim().length() == 0) {
|
||||
return;
|
||||
}
|
||||
Log.e(TAG, "deleting the database file: " + fileName);
|
||||
try {
|
||||
SQLiteDatabase.deleteDatabase(new File(fileName));
|
||||
} catch (Exception e) {
|
||||
/* print warning and ignore exception */
|
||||
Log.w(TAG, "delete failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Provides methods for registering or unregistering arbitrary observers in an {@link ArrayList}.
|
||||
*
|
||||
* This abstract class is intended to be subclassed and specialized to maintain
|
||||
* a registry of observers of specific types and dispatch notifications to them.
|
||||
*
|
||||
* @param T The observer type.
|
||||
*/
|
||||
public abstract class Observable<T> {
|
||||
/**
|
||||
* The list of observers. An observer can be in the list at most
|
||||
* once and will never be null.
|
||||
*/
|
||||
protected final ArrayList<T> mObservers = new ArrayList<T>();
|
||||
|
||||
/**
|
||||
* Adds an observer to the list. The observer cannot be null and it must not already
|
||||
* be registered.
|
||||
* @param observer the observer to register
|
||||
* @throws IllegalArgumentException the observer is null
|
||||
* @throws IllegalStateException the observer is already registered
|
||||
*/
|
||||
public void registerObserver(T observer) {
|
||||
if (observer == null) {
|
||||
throw new IllegalArgumentException("The observer is null.");
|
||||
}
|
||||
synchronized(mObservers) {
|
||||
if (mObservers.contains(observer)) {
|
||||
throw new IllegalStateException("Observer " + observer + " is already registered.");
|
||||
}
|
||||
mObservers.add(observer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a previously registered observer. The observer must not be null and it
|
||||
* must already have been registered.
|
||||
* @param observer the observer to unregister
|
||||
* @throws IllegalArgumentException the observer is null
|
||||
* @throws IllegalStateException the observer is not yet registered
|
||||
*/
|
||||
public void unregisterObserver(T observer) {
|
||||
if (observer == null) {
|
||||
throw new IllegalArgumentException("The observer is null.");
|
||||
}
|
||||
synchronized(mObservers) {
|
||||
int index = mObservers.indexOf(observer);
|
||||
if (index == -1) {
|
||||
throw new IllegalStateException("Observer " + observer + " was not registered.");
|
||||
}
|
||||
mObservers.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all registered observers.
|
||||
*/
|
||||
public void unregisterAll() {
|
||||
synchronized(mObservers) {
|
||||
mObservers.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
/**
|
||||
* An exception that indicates there was an error with SQL parsing or execution.
|
||||
*/
|
||||
public class SQLException extends RuntimeException {
|
||||
public SQLException() {
|
||||
}
|
||||
public SQLException(String error) {
|
||||
super(error);
|
||||
}
|
||||
public SQLException(String error, Throwable cause) {
|
||||
super(error, cause);
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.database.sqlite;
|
||||
|
||||
/**
|
||||
* This exception class is used when sqlite can't access the database file
|
||||
* due to lack of permissions on the file.
|
||||
*/
|
||||
public class SQLiteAccessPermException extends SQLiteException {
|
||||
public SQLiteAccessPermException() {}
|
||||
|
||||
public SQLiteAccessPermException(String error) {
|
||||
super(error);
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.database.sqlite;
|
||||
|
||||
/**
|
||||
* Thrown if the the bind or column parameter index is out of range
|
||||
*/
|
||||
public class SQLiteBindOrColumnIndexOutOfRangeException extends SQLiteException {
|
||||
public SQLiteBindOrColumnIndexOutOfRangeException() {}
|
||||
|
||||
public SQLiteBindOrColumnIndexOutOfRangeException(String error) {
|
||||
super(error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.database.sqlite;
|
||||
|
||||
public class SQLiteBlobTooBigException extends SQLiteException {
|
||||
public SQLiteBlobTooBigException() {}
|
||||
|
||||
public SQLiteBlobTooBigException(String error) {
|
||||
super(error);
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.database.sqlite;
|
||||
|
||||
public class SQLiteCantOpenDatabaseException extends SQLiteException {
|
||||
public SQLiteCantOpenDatabaseException() {}
|
||||
|
||||
public SQLiteCantOpenDatabaseException(String error) {
|
||||
super(error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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.io.Closeable;
|
||||
|
||||
/**
|
||||
* An object created from a SQLiteDatabase that can be closed.
|
||||
*
|
||||
* This class implements a primitive reference counting scheme for database objects.
|
||||
*/
|
||||
public abstract class SQLiteClosable implements Closeable {
|
||||
private int mReferenceCount = 1;
|
||||
|
||||
/**
|
||||
* Called when the last reference to the object was released by
|
||||
* a call to {@link #releaseReference()} or {@link #close()}.
|
||||
*/
|
||||
protected abstract void onAllReferencesReleased();
|
||||
|
||||
/**
|
||||
* Called when the last reference to the object was released by
|
||||
* a call to {@link #releaseReferenceFromContainer()}.
|
||||
*
|
||||
* @deprecated Do not use.
|
||||
*/
|
||||
@Deprecated
|
||||
protected void onAllReferencesReleasedFromContainer() {
|
||||
onAllReferencesReleased();
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires a reference to the object.
|
||||
*
|
||||
* @throws IllegalStateException if the last reference to the object has already
|
||||
* been released.
|
||||
*/
|
||||
public void acquireReference() {
|
||||
synchronized(this) {
|
||||
if (mReferenceCount <= 0) {
|
||||
throw new IllegalStateException(
|
||||
"attempt to re-open an already-closed object: " + this);
|
||||
}
|
||||
mReferenceCount++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases a reference to the object, closing the object if the last reference
|
||||
* was released.
|
||||
*
|
||||
* @see #onAllReferencesReleased()
|
||||
*/
|
||||
public void releaseReference() {
|
||||
boolean refCountIsZero = false;
|
||||
synchronized(this) {
|
||||
refCountIsZero = --mReferenceCount == 0;
|
||||
}
|
||||
if (refCountIsZero) {
|
||||
onAllReferencesReleased();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases a reference to the object that was owned by the container of the object,
|
||||
* closing the object if the last reference was released.
|
||||
*
|
||||
* @see #onAllReferencesReleasedFromContainer()
|
||||
* @deprecated Do not use.
|
||||
*/
|
||||
@Deprecated
|
||||
public void releaseReferenceFromContainer() {
|
||||
boolean refCountIsZero = false;
|
||||
synchronized(this) {
|
||||
refCountIsZero = --mReferenceCount == 0;
|
||||
}
|
||||
if (refCountIsZero) {
|
||||
onAllReferencesReleasedFromContainer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases a reference to the object, closing the object if the last reference
|
||||
* was released.
|
||||
*
|
||||
* Calling this method is equivalent to calling {@link #releaseReference}.
|
||||
*
|
||||
* @see #releaseReference()
|
||||
* @see #onAllReferencesReleased()
|
||||
*/
|
||||
public void close() {
|
||||
releaseReference();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* 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.util.Log;
|
||||
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 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 = "SQLiteCursor";
|
||||
static final int NO_COUNT = -1;
|
||||
|
||||
/** The name of the table to edit */
|
||||
private final String mEditTable;
|
||||
|
||||
/** The names of the columns in the rows */
|
||||
private final String[] mColumns;
|
||||
|
||||
/** The query object for the cursor */
|
||||
private final SQLiteQuery mQuery;
|
||||
|
||||
/** The compiled query this cursor came from */
|
||||
private final SQLiteCursorDriver mDriver;
|
||||
|
||||
/** The number of rows in the cursor */
|
||||
private int mCount = NO_COUNT;
|
||||
|
||||
/** The number of rows that can fit in the cursor window, 0 if unknown */
|
||||
private int mCursorWindowCapacity;
|
||||
|
||||
/** 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 final Throwable mStackTrace;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param db a reference to a Database object that is already constructed
|
||||
* and opened. This param is not used any longer
|
||||
* @param editTable the name of the table used for this query
|
||||
* @param query the rest of the query terms
|
||||
* cursor is finalized
|
||||
* @deprecated use {@link #SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public SQLiteCursor(SQLiteDatabase db, SQLiteCursorDriver driver,
|
||||
String editTable, SQLiteQuery query) {
|
||||
this(driver, editTable, 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.
|
||||
*
|
||||
* @param editTable the name of the table used for this query
|
||||
* @param query the {@link SQLiteQuery} object associated with this cursor object.
|
||||
*/
|
||||
public SQLiteCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) {
|
||||
if (query == null) {
|
||||
throw new IllegalArgumentException("query object cannot be null");
|
||||
}
|
||||
mStackTrace = null;
|
||||
mDriver = driver;
|
||||
mEditTable = editTable;
|
||||
mColumnNameMap = null;
|
||||
mQuery = query;
|
||||
|
||||
try {
|
||||
query.B_setBindArgs();
|
||||
query.getPreparedStatement().execute();
|
||||
|
||||
ResultSetMetaData metaData = query.getResultSet().getMetaData();
|
||||
mColumns = new String[metaData.getColumnCount()];
|
||||
for(int i = 1; i <= metaData.getColumnCount(); i++) {
|
||||
mColumns[i - 1] = metaData.getColumnLabel(i);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new SQLiteException("Failed to get column names!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database that this cursor is associated with.
|
||||
* @return the SQLiteDatabase that this cursor is associated with.
|
||||
*/
|
||||
public SQLiteDatabase getDatabase() {
|
||||
return mQuery.getDatabase();
|
||||
}
|
||||
|
||||
@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() {
|
||||
return mQuery.getResultSet().getResultSetLength();
|
||||
}
|
||||
|
||||
private void fillWindow(int requiredPos) {
|
||||
clearOrCreateWindow(mQuery.getResultSet());
|
||||
|
||||
try {
|
||||
} catch (RuntimeException ex) {
|
||||
// Close the cursor window if the query failed and therefore will
|
||||
// not produce any results. This helps to avoid accidentally leaking
|
||||
// the cursor window if the client does not correctly handle exceptions
|
||||
// and fails to close the cursor.
|
||||
closeWindow();
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getColumnNames() {
|
||||
return mColumns;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
mDriver.cursorDeactivated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
super.close();
|
||||
synchronized (this) {
|
||||
mQuery.close();
|
||||
mDriver.cursorClosed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requery() {
|
||||
if (isClosed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
if (!mQuery.getDatabase().isOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mWindow != null) {
|
||||
mWindow.clear();
|
||||
}
|
||||
mPos = -1;
|
||||
mCount = NO_COUNT;
|
||||
|
||||
mDriver.cursorRequeried(this);
|
||||
}
|
||||
|
||||
try {
|
||||
return super.requery();
|
||||
} catch (IllegalStateException e) {
|
||||
// for backwards compatibility, just return false
|
||||
Log.w(TAG, "requery() failed " + e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWindow(CursorWindow window) {
|
||||
super.setWindow(window);
|
||||
mCount = NO_COUNT;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
//TODO Finish
|
||||
// if (mStackTrace != null) {
|
||||
// String sql = mQuery.getSql();
|
||||
// int len = sql.length();
|
||||
// StrictMode.onSqliteObjectLeaked(
|
||||
// "Finalizing a Cursor that has not been deactivated or closed. " +
|
||||
// "database = " + mQuery.getDatabase().getLabel() +
|
||||
// ", table = " + mEditTable +
|
||||
// ", query = " + sql.substring(0, (len > 1000) ? 1000 : len),
|
||||
// mStackTrace);
|
||||
// }
|
||||
close();
|
||||
}
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 requeried.
|
||||
*/
|
||||
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
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.database.sqlite;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Describes how to configure a database.
|
||||
* <p>
|
||||
* The purpose of this object is to keep track of all of the little
|
||||
* configuration settings that are applied to a database after it
|
||||
* is opened so that they can be applied to all connections in the
|
||||
* connection pool uniformly.
|
||||
* </p><p>
|
||||
* Each connection maintains its own copy of this object so it can
|
||||
* keep track of which settings have already been applied.
|
||||
* </p>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class SQLiteDatabaseConfiguration {
|
||||
// The pattern we use to strip email addresses from database paths
|
||||
// when constructing a label to use in log messages.
|
||||
private static final Pattern EMAIL_IN_DB_PATTERN =
|
||||
Pattern.compile("[\\w\\.\\-]+@[\\w\\.\\-]+");
|
||||
|
||||
/**
|
||||
* Special path used by in-memory databases.
|
||||
*/
|
||||
public static final String MEMORY_DB_PATH = ":memory:";
|
||||
|
||||
/**
|
||||
* The database path.
|
||||
*/
|
||||
public final String path;
|
||||
|
||||
/**
|
||||
* The label to use to describe the database when it appears in logs.
|
||||
* This is derived from the path but is stripped to remove PII.
|
||||
*/
|
||||
public final String label;
|
||||
|
||||
/**
|
||||
* The flags used to open the database.
|
||||
*/
|
||||
public int openFlags;
|
||||
|
||||
/**
|
||||
* The maximum size of the prepared statement cache for each database connection.
|
||||
* Must be non-negative.
|
||||
*
|
||||
* Default is 25.
|
||||
*/
|
||||
public int maxSqlCacheSize;
|
||||
|
||||
/**
|
||||
* The database locale.
|
||||
*
|
||||
* Default is the value returned by {@link Locale#getDefault()}.
|
||||
*/
|
||||
public Locale locale;
|
||||
|
||||
/**
|
||||
* True if foreign key constraints are enabled.
|
||||
*
|
||||
* Default is false.
|
||||
*/
|
||||
public boolean foreignKeyConstraintsEnabled;
|
||||
|
||||
/**
|
||||
* Creates a database configuration with the required parameters for opening a
|
||||
* database and default values for all other parameters.
|
||||
*
|
||||
* @param path The database path.
|
||||
* @param openFlags Open flags for the database, such as {@link SQLiteDatabase#OPEN_READWRITE}.
|
||||
*/
|
||||
public SQLiteDatabaseConfiguration(String path, int openFlags) {
|
||||
if (path == null) {
|
||||
throw new IllegalArgumentException("path must not be null.");
|
||||
}
|
||||
|
||||
this.path = path;
|
||||
label = stripPathForLogs(path);
|
||||
this.openFlags = openFlags;
|
||||
|
||||
// Set default values for optional parameters.
|
||||
maxSqlCacheSize = 25;
|
||||
locale = Locale.getDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a database configuration as a copy of another configuration.
|
||||
*
|
||||
* @param other The other configuration.
|
||||
*/
|
||||
public SQLiteDatabaseConfiguration(SQLiteDatabaseConfiguration other) {
|
||||
if (other == null) {
|
||||
throw new IllegalArgumentException("other must not be null.");
|
||||
}
|
||||
|
||||
this.path = other.path;
|
||||
this.label = other.label;
|
||||
updateParametersFrom(other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the non-immutable parameters of this configuration object
|
||||
* from the other configuration object.
|
||||
*
|
||||
* @param other The object from which to copy the parameters.
|
||||
*/
|
||||
public void updateParametersFrom(SQLiteDatabaseConfiguration other) {
|
||||
if (other == null) {
|
||||
throw new IllegalArgumentException("other must not be null.");
|
||||
}
|
||||
if (!path.equals(other.path)) {
|
||||
throw new IllegalArgumentException("other configuration must refer to "
|
||||
+ "the same database.");
|
||||
}
|
||||
|
||||
openFlags = other.openFlags;
|
||||
maxSqlCacheSize = other.maxSqlCacheSize;
|
||||
locale = other.locale;
|
||||
foreignKeyConstraintsEnabled = other.foreignKeyConstraintsEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the database is in-memory.
|
||||
* @return True if the database is in-memory.
|
||||
*/
|
||||
public boolean isInMemoryDb() {
|
||||
return path.equalsIgnoreCase(MEMORY_DB_PATH);
|
||||
}
|
||||
|
||||
private static String stripPathForLogs(String path) {
|
||||
if (path.indexOf('@') == -1) {
|
||||
return path;
|
||||
}
|
||||
return EMAIL_IN_DB_PATTERN.matcher(path).replaceAll("XX@YY");
|
||||
}
|
||||
}
|
||||
+28
@@ -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);
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.database.sqlite;
|
||||
|
||||
/**
|
||||
* Thrown if the database engine was unable to acquire the
|
||||
* database locks it needs to do its job. If the statement is a [COMMIT]
|
||||
* or occurs outside of an explicit transaction, then you can retry the
|
||||
* statement. If the statement is not a [COMMIT] and occurs within a
|
||||
* explicit transaction then you should rollback the transaction before
|
||||
* continuing.
|
||||
*/
|
||||
public class SQLiteDatabaseLockedException extends SQLiteException {
|
||||
public SQLiteDatabaseLockedException() {}
|
||||
|
||||
public SQLiteDatabaseLockedException(String error) {
|
||||
super(error);
|
||||
}
|
||||
}
|
||||
+25
@@ -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 SQLiteDatatypeMismatchException extends SQLiteException {
|
||||
public SQLiteDatatypeMismatchException() {}
|
||||
|
||||
public SQLiteDatatypeMismatchException(String error) {
|
||||
super(error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* 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.os.Build;
|
||||
import android.os.SystemProperties;
|
||||
import android.util.Log;
|
||||
import android.util.Printer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Provides debugging info about all SQLite databases running in the current process.
|
||||
*
|
||||
* {@hide}
|
||||
*/
|
||||
public final class SQLiteDebug {
|
||||
private static native void nativeGetPagerStats(PagerStats stats);
|
||||
|
||||
/**
|
||||
* Controls the printing of informational SQL log messages.
|
||||
*
|
||||
* Enable using "adb shell setprop log.tag.SQLiteLog VERBOSE".
|
||||
*/
|
||||
public static final boolean DEBUG_SQL_LOG =
|
||||
Log.isLoggable("SQLiteLog", Log.VERBOSE);
|
||||
|
||||
/**
|
||||
* Controls the printing of SQL statements as they are executed.
|
||||
*
|
||||
* Enable using "adb shell setprop log.tag.SQLiteStatements VERBOSE".
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* Enable using "adb shell setprop log.tag.SQLiteTime VERBOSE".
|
||||
*/
|
||||
public static final boolean DEBUG_SQL_TIME =
|
||||
Log.isLoggable("SQLiteTime", Log.VERBOSE);
|
||||
|
||||
/**
|
||||
* True to enable database performance testing instrumentation.
|
||||
* @hide
|
||||
*/
|
||||
public static final boolean DEBUG_LOG_SLOW_QUERIES = Build.IS_DEBUGGABLE;
|
||||
|
||||
private SQLiteDebug() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a query should be logged.
|
||||
*
|
||||
* Reads the "db.log.slow_query_threshold" system property, which can be changed
|
||||
* by the user at any time. If the value is zero, then all queries will
|
||||
* be considered slow. If the value does not exist or is negative, then no queries will
|
||||
* be considered slow.
|
||||
*
|
||||
* This value can be changed dynamically while the system is running.
|
||||
* For example, "adb shell setprop db.log.slow_query_threshold 200" will
|
||||
* log all queries that take 200ms or longer to run.
|
||||
* @hide
|
||||
*/
|
||||
public static final boolean shouldLogSlowQuery(long elapsedTimeMillis) {
|
||||
int slowQueryMillis = SystemProperties.getInt("db.log.slow_query_threshold", -1);
|
||||
return slowQueryMillis >= 0 && elapsedTimeMillis >= slowQueryMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains statistics about the active pagers in the current process.
|
||||
*
|
||||
* @see #nativeGetPagerStats(PagerStats)
|
||||
*/
|
||||
public static class PagerStats {
|
||||
/** 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 pageCacheOverflow;
|
||||
|
||||
/** 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;
|
||||
|
||||
/** statement cache stats: hits/misses/cachesize */
|
||||
public String cache;
|
||||
|
||||
public DbStats(String dbName, long pageCount, long pageSize, int lookaside,
|
||||
int hits, int misses, int cachesize) {
|
||||
this.dbName = dbName;
|
||||
this.pageSize = pageSize / 1024;
|
||||
dbSize = (pageCount * pageSize) / 1024;
|
||||
this.lookaside = lookaside;
|
||||
this.cache = hits + "/" + misses + "/" + cachesize;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return all pager and database stats for the current process.
|
||||
* @return {@link PagerStats}
|
||||
*/
|
||||
public static PagerStats getDatabaseInfo() {
|
||||
PagerStats stats = new PagerStats();
|
||||
nativeGetPagerStats(stats);
|
||||
stats.dbStats = SQLiteDatabase.getDbStats();
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps detailed information about all databases used by the process.
|
||||
* @param printer The printer for dumping database state.
|
||||
* @param args Command-line arguments supplied to dumpsys dbinfo
|
||||
*/
|
||||
public static void dump(Printer printer, String[] args) {
|
||||
boolean verbose = false;
|
||||
for (String arg : args) {
|
||||
if (arg.equals("-v")) {
|
||||
verbose = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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;
|
||||
import android.os.CancellationSignal;
|
||||
|
||||
/**
|
||||
* A cursor driver that uses the given query directly.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
|
||||
private final SQLiteDatabase mDatabase;
|
||||
private final String mEditTable;
|
||||
private final String mSql;
|
||||
private final CancellationSignal mCancellationSignal;
|
||||
private SQLiteQuery mQuery;
|
||||
|
||||
public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable,
|
||||
CancellationSignal cancellationSignal) {
|
||||
mDatabase = db;
|
||||
mEditTable = editTable;
|
||||
mSql = sql;
|
||||
mCancellationSignal = cancellationSignal;
|
||||
}
|
||||
|
||||
public Cursor query(CursorFactory factory, String[] selectionArgs) {
|
||||
final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancellationSignal);
|
||||
final Cursor cursor;
|
||||
try {
|
||||
query.bindAllArgsAsStrings(selectionArgs);
|
||||
|
||||
if (factory == null) {
|
||||
cursor = new SQLiteCursor(this, mEditTable, query);
|
||||
} else {
|
||||
cursor = factory.newCursor(mDatabase, this, mEditTable, query);
|
||||
}
|
||||
} catch (RuntimeException ex) {
|
||||
query.close();
|
||||
throw ex;
|
||||
}
|
||||
|
||||
mQuery = query;
|
||||
return cursor;
|
||||
}
|
||||
|
||||
public void cursorClosed() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public void setBindArguments(String[] bindArgs) {
|
||||
mQuery.bindAllArgsAsStrings(bindArgs);
|
||||
}
|
||||
|
||||
public void cursorDeactivated() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public void cursorRequeried(Cursor cursor) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SQLiteDirectCursorDriver: " + mSql;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
public SQLiteException(String error, Throwable cause) {
|
||||
super(error, cause);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.database.sqlite;
|
||||
|
||||
import android.os.StatFs;
|
||||
import android.os.SystemProperties;
|
||||
|
||||
/**
|
||||
* Provides access to SQLite functions that affect all database connection,
|
||||
* such as memory management.
|
||||
*
|
||||
* The native code associated with SQLiteGlobal is also sets global configuration options
|
||||
* using sqlite3_config() then calls sqlite3_initialize() to ensure that the SQLite
|
||||
* library is properly initialized exactly once before any other framework or application
|
||||
* code has a chance to run.
|
||||
*
|
||||
* Verbose SQLite logging is enabled if the "log.tag.SQLiteLog" property is set to "V".
|
||||
* (per {@link SQLiteDebug#DEBUG_SQL_LOG}).
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class SQLiteGlobal {
|
||||
private static final Object sLock = new Object();
|
||||
private static int sDefaultPageSize;
|
||||
|
||||
private static native int nativeReleaseMemory();
|
||||
|
||||
private SQLiteGlobal() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to release memory by pruning the SQLite page cache and other
|
||||
* internal data structures.
|
||||
*
|
||||
* @return The number of bytes that were freed.
|
||||
*/
|
||||
public static int releaseMemory() {
|
||||
return nativeReleaseMemory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default page size to use when creating a database.
|
||||
*/
|
||||
public static int getDefaultPageSize() {
|
||||
synchronized (sLock) {
|
||||
if (sDefaultPageSize == 0) {
|
||||
sDefaultPageSize = new StatFs("/data").getBlockSize();
|
||||
}
|
||||
return SystemProperties.getInt("debug.sqlite.pagesize", sDefaultPageSize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default journal mode when WAL is not in use.
|
||||
*/
|
||||
public static String getDefaultJournalMode() {
|
||||
return SystemProperties.get("debug.sqlite.journalmode","TRUNCATE");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the journal size limit in bytes.
|
||||
*/
|
||||
public static int getJournalSizeLimit() {
|
||||
return SystemProperties.getInt("debug.sqlite.journalsizelimit",
|
||||
524288);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default database synchronization mode when WAL is not in use.
|
||||
*/
|
||||
public static String getDefaultSyncMode() {
|
||||
return SystemProperties.get("debug.sqlite.syncmode","FULL");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the database synchronization mode when in WAL mode.
|
||||
*/
|
||||
public static String getWALSyncMode() {
|
||||
return SystemProperties.get("debug.sqlite.wal.syncmode", "FULL");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the WAL auto-checkpoint integer in database pages.
|
||||
*/
|
||||
public static int getWALAutoCheckpoint() {
|
||||
int value = SystemProperties.getInt("debug.sqlite.wal.autocheckpoint", 100);
|
||||
return Math.max(1, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the connection pool size when in WAL mode.
|
||||
*/
|
||||
public static int getWALConnectionPoolSize() {
|
||||
int value = SystemProperties.getInt("debug.sqlite.wal.poolsize", 4);
|
||||
return Math.max(2, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* This error can occur if the application creates a SQLiteStatement object and allows multiple
|
||||
* threads in the application use it at the same time.
|
||||
* Sqlite returns this error if bind and execute methods on this object occur at the same time
|
||||
* from multiple threads, like so:
|
||||
* thread # 1: in execute() method of the SQLiteStatement object
|
||||
* while thread # 2: is in bind..() on the same object.
|
||||
*</p>
|
||||
* FIX this by NEVER sharing the same SQLiteStatement object between threads.
|
||||
* Create a local instance of the SQLiteStatement whenever it is needed, use it and close it ASAP.
|
||||
* NEVER make it globally available.
|
||||
*/
|
||||
public class SQLiteMisuseException extends SQLiteException {
|
||||
public SQLiteMisuseException() {}
|
||||
|
||||
public SQLiteMisuseException(String error) {
|
||||
super(error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
* 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.DatabaseErrorHandler;
|
||||
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.</p>
|
||||
*/
|
||||
public abstract class SQLiteOpenHelper {
|
||||
private static final String TAG = SQLiteOpenHelper.class.getSimpleName();
|
||||
|
||||
// When true, getReadableDatabase returns a read-only database if it is just being opened.
|
||||
// The database handle is reopened in read/write mode when getWritableDatabase is called.
|
||||
// We leave this behavior disabled in production because it is inefficient and breaks
|
||||
// many applications. For debugging purposes it can be useful to turn on strict
|
||||
// read-only semantics to catch applications that call getReadableDatabase when they really
|
||||
// wanted getWritableDatabase.
|
||||
private static final boolean DEBUG_STRICT_READONLY = false;
|
||||
|
||||
private final Context mContext;
|
||||
private final String mName;
|
||||
private final CursorFactory mFactory;
|
||||
private final int mNewVersion;
|
||||
|
||||
private SQLiteDatabase mDatabase;
|
||||
private boolean mIsInitializing;
|
||||
private boolean mEnableWriteAheadLogging;
|
||||
private final DatabaseErrorHandler mErrorHandler;
|
||||
|
||||
/**
|
||||
* 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; if the database is
|
||||
* newer, {@link #onDowngrade} will be used to downgrade the database
|
||||
*/
|
||||
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
|
||||
this(context, name, factory, version, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a helper object to create, open, and/or manage a database.
|
||||
* The database is not actually created or opened until one of
|
||||
* {@link #getWritableDatabase} or {@link #getReadableDatabase} is called.
|
||||
*
|
||||
* <p>Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be
|
||||
* used to handle corruption when sqlite reports database corruption.</p>
|
||||
*
|
||||
* @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; if the database is
|
||||
* newer, {@link #onDowngrade} will be used to downgrade the database
|
||||
* @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database
|
||||
* corruption, or null to use the default error handler.
|
||||
*/
|
||||
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
|
||||
DatabaseErrorHandler errorHandler) {
|
||||
if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
|
||||
|
||||
mContext = context;
|
||||
mName = name;
|
||||
mFactory = factory;
|
||||
mNewVersion = version;
|
||||
mErrorHandler = errorHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the SQLite database being opened, as given to
|
||||
* the constructor.
|
||||
*/
|
||||
public String getDatabaseName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables the use of write-ahead logging for the database.
|
||||
*
|
||||
* Write-ahead logging cannot be used with read-only databases so the value of
|
||||
* this flag is ignored if the database is opened read-only.
|
||||
*
|
||||
* @param enabled True if write-ahead logging should be enabled, false if it
|
||||
* should be disabled.
|
||||
*
|
||||
* @see SQLiteDatabase#enableWriteAheadLogging()
|
||||
*/
|
||||
public void setWriteAheadLoggingEnabled(boolean enabled) {
|
||||
synchronized (this) {
|
||||
if (mEnableWriteAheadLogging != enabled) {
|
||||
if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
|
||||
if (enabled) {
|
||||
mDatabase.enableWriteAheadLogging();
|
||||
} else {
|
||||
mDatabase.disableWriteAheadLogging();
|
||||
}
|
||||
}
|
||||
mEnableWriteAheadLogging = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 SQLiteDatabase getWritableDatabase() {
|
||||
synchronized (this) {
|
||||
return getDatabaseLocked(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 SQLiteDatabase getReadableDatabase() {
|
||||
synchronized (this) {
|
||||
return getDatabaseLocked(false);
|
||||
}
|
||||
}
|
||||
|
||||
private SQLiteDatabase getDatabaseLocked(boolean writable) {
|
||||
if (mDatabase != null) {
|
||||
if (!mDatabase.isOpen()) {
|
||||
// Darn! The user closed the database by calling mDatabase.close().
|
||||
mDatabase = null;
|
||||
} else if (!writable || !mDatabase.isReadOnly()) {
|
||||
// The database is already open for business.
|
||||
return mDatabase;
|
||||
}
|
||||
}
|
||||
|
||||
if (mIsInitializing) {
|
||||
throw new IllegalStateException("getDatabase called recursively");
|
||||
}
|
||||
|
||||
SQLiteDatabase db = mDatabase;
|
||||
try {
|
||||
mIsInitializing = true;
|
||||
|
||||
if (db != null) {
|
||||
if (writable && db.isReadOnly()) {
|
||||
db.reopenReadWrite();
|
||||
}
|
||||
} else if (mName == null) {
|
||||
db = SQLiteDatabase.create(null);
|
||||
} else {
|
||||
try {
|
||||
if (DEBUG_STRICT_READONLY && !writable) {
|
||||
final String path = mContext.getDatabasePath(mName).getPath();
|
||||
db = SQLiteDatabase.openDatabase(path, mFactory,
|
||||
SQLiteDatabase.OPEN_READONLY, mErrorHandler);
|
||||
} else {
|
||||
db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ?
|
||||
Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0,
|
||||
mFactory, mErrorHandler);
|
||||
}
|
||||
} catch (SQLiteException ex) {
|
||||
if (writable) {
|
||||
throw ex;
|
||||
}
|
||||
Log.e(TAG, "Couldn't open " + mName
|
||||
+ " for writing (will try read-only):", ex);
|
||||
final String path = mContext.getDatabasePath(mName).getPath();
|
||||
db = SQLiteDatabase.openDatabase(path, mFactory,
|
||||
SQLiteDatabase.OPEN_READONLY, mErrorHandler);
|
||||
}
|
||||
}
|
||||
|
||||
onConfigure(db);
|
||||
|
||||
final int version = db.getVersion();
|
||||
if (version != mNewVersion) {
|
||||
if (db.isReadOnly()) {
|
||||
throw new SQLiteException("Can't upgrade read-only database from version " +
|
||||
db.getVersion() + " to " + mNewVersion + ": " + mName);
|
||||
}
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
if (version == 0) {
|
||||
onCreate(db);
|
||||
} else {
|
||||
if (version > mNewVersion) {
|
||||
onDowngrade(db, version, mNewVersion);
|
||||
} else {
|
||||
onUpgrade(db, version, mNewVersion);
|
||||
}
|
||||
}
|
||||
db.setVersion(mNewVersion);
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
onOpen(db);
|
||||
|
||||
if (db.isReadOnly()) {
|
||||
Log.w(TAG, "Opened " + mName + " in read-only mode");
|
||||
}
|
||||
|
||||
mDatabase = db;
|
||||
return db;
|
||||
} 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 connection is being configured, to enable features
|
||||
* such as write-ahead logging or foreign key support.
|
||||
* <p>
|
||||
* This method is called before {@link #onCreate}, {@link #onUpgrade},
|
||||
* {@link #onDowngrade}, or {@link #onOpen} are called. It should not modify
|
||||
* the database except to configure the database connection as required.
|
||||
* </p><p>
|
||||
* This method should only call methods that configure the parameters of the
|
||||
* database connection, such as {@link SQLiteDatabase#enableWriteAheadLogging}
|
||||
* {@link SQLiteDatabase#setForeignKeyConstraintsEnabled},
|
||||
* {@link SQLiteDatabase#setLocale}, {@link SQLiteDatabase#setMaximumSize},
|
||||
* or executing PRAGMA statements.
|
||||
* </p>
|
||||
*
|
||||
* @param db The database.
|
||||
*/
|
||||
public void onConfigure(SQLiteDatabase db) {}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* </p><p>
|
||||
* This method executes within a transaction. If an exception is thrown, all changes
|
||||
* will automatically be rolled back.
|
||||
* </p>
|
||||
*
|
||||
* @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 needs to be downgraded. This is strictly similar to
|
||||
* {@link #onUpgrade} method, but is called whenever current version is newer than requested one.
|
||||
* However, this method is not abstract, so it is not mandatory for a customer to
|
||||
* implement it. If not overridden, default implementation will reject downgrade and
|
||||
* throws SQLiteException
|
||||
*
|
||||
* <p>
|
||||
* This method executes within a transaction. If an exception is thrown, all changes
|
||||
* will automatically be rolled back.
|
||||
* </p>
|
||||
*
|
||||
* @param db The database.
|
||||
* @param oldVersion The old database version.
|
||||
* @param newVersion The new database version.
|
||||
*/
|
||||
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
throw new SQLiteException("Can't downgrade database from version " +
|
||||
oldVersion + " to " + newVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the database has been opened. The implementation
|
||||
* should check {@link SQLiteDatabase#isReadOnly} before updating the
|
||||
* database.
|
||||
* <p>
|
||||
* This method is called after the database connection has been configured
|
||||
* and after the database schema has been created, upgraded or downgraded as necessary.
|
||||
* If the database connection must be configured in some way before the schema
|
||||
* is created, upgraded, or downgraded, do it in {@link #onConfigure} instead.
|
||||
* </p>
|
||||
*
|
||||
* @param db The database.
|
||||
*/
|
||||
public void onOpen(SQLiteDatabase db) {}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.database.sqlite;
|
||||
|
||||
public class SQLiteOutOfMemoryException extends SQLiteException {
|
||||
public SQLiteOutOfMemoryException() {}
|
||||
|
||||
public SQLiteOutOfMemoryException(String error) {
|
||||
super(error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* 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.DatabaseUtils;
|
||||
import android.os.CancellationSignal;
|
||||
import xyz.nulldev.androidcompat.db.ScrollableResultSet;
|
||||
|
||||
import java.sql.ParameterMetaData;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* A base class for compiled SQLite programs.
|
||||
* <p>
|
||||
* This class is not thread-safe.
|
||||
* </p>
|
||||
*/
|
||||
public abstract class SQLiteProgram extends SQLiteClosable {
|
||||
private final SQLiteDatabase mDatabase;
|
||||
private final String mSql;
|
||||
private final int mNumParameters;
|
||||
private final Object[] mBindArgs;
|
||||
private PreparedStatement preparedStatement = null;
|
||||
|
||||
SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs,
|
||||
CancellationSignal cancellationSignalForPrepare) {
|
||||
mDatabase = db;
|
||||
mSql = sql.trim();
|
||||
|
||||
ParameterMetaData metaData;
|
||||
try {
|
||||
preparedStatement = mDatabase.getConnection().prepareStatement(mSql, Statement.RETURN_GENERATED_KEYS);
|
||||
metaData = preparedStatement.getParameterMetaData();
|
||||
mNumParameters = metaData.getParameterCount();
|
||||
} catch (SQLException e) {
|
||||
throw new SQLiteException("Could not compile SQL statement: " + mSql, e);
|
||||
}
|
||||
|
||||
int n = DatabaseUtils.getSqlStatementType(mSql);
|
||||
switch (n) {
|
||||
case DatabaseUtils.STATEMENT_BEGIN:
|
||||
case DatabaseUtils.STATEMENT_COMMIT:
|
||||
case DatabaseUtils.STATEMENT_ABORT:
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (bindArgs != null && bindArgs.length > mNumParameters) {
|
||||
throw new IllegalArgumentException("Too many bind arguments. "
|
||||
+ bindArgs.length + " arguments were provided but the statement needs "
|
||||
+ mNumParameters + " arguments.");
|
||||
}
|
||||
|
||||
if (mNumParameters != 0) {
|
||||
mBindArgs = new Object[mNumParameters];
|
||||
if (bindArgs != null) {
|
||||
System.arraycopy(bindArgs, 0, mBindArgs, 0, bindArgs.length);
|
||||
}
|
||||
} else {
|
||||
mBindArgs = null;
|
||||
}
|
||||
}
|
||||
|
||||
PreparedStatement getPreparedStatement() {
|
||||
return preparedStatement;
|
||||
}
|
||||
|
||||
final SQLiteDatabase getDatabase() {
|
||||
return mDatabase;
|
||||
}
|
||||
|
||||
final String getSql() {
|
||||
return mSql;
|
||||
}
|
||||
|
||||
final Object[] getBindArgs() {
|
||||
return mBindArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unimplemented.
|
||||
* @deprecated This method is deprecated and must not be used.
|
||||
*/
|
||||
@Deprecated
|
||||
public final int getUniqueId() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
bind(index, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind a long value to this statement. The value remains bound until
|
||||
* {@link #clearBindings} is called.
|
||||
*addToBindArgs
|
||||
* @param index The 1-based index to the parameter to bind
|
||||
* @param value The value to bind
|
||||
*/
|
||||
public void bindLong(int index, long value) {
|
||||
bind(index, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
bind(index, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, must not be null
|
||||
*/
|
||||
public void bindString(int index, String value) {
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("the bind value at index " + index + " is null");
|
||||
}
|
||||
bind(index, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, must not be null
|
||||
*/
|
||||
public void bindBlob(int index, byte[] value) {
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("the bind value at index " + index + " is null");
|
||||
}
|
||||
bind(index, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all existing bindings. Unset bindings are treated as NULL.
|
||||
*/
|
||||
public void clearBindings() {
|
||||
if (mBindArgs != null) {
|
||||
Arrays.fill(mBindArgs, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of String bindArgs, this method binds all of them in one single call.
|
||||
*
|
||||
* @param bindArgs the String array of bind args, none of which must be null.
|
||||
*/
|
||||
public void bindAllArgsAsStrings(String[] bindArgs) {
|
||||
if (bindArgs != null) {
|
||||
for (int i = bindArgs.length; i != 0; i--) {
|
||||
bindString(i, bindArgs[i - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAllReferencesReleased() {
|
||||
clearBindings();
|
||||
|
||||
// Close prepared statement
|
||||
try {
|
||||
if (preparedStatement.isClosed())
|
||||
preparedStatement.close();
|
||||
} catch(SQLException ignored) {}
|
||||
}
|
||||
|
||||
private void bind(int index, Object value) {
|
||||
if (index < 1 || index > mNumParameters) {
|
||||
throw new IllegalArgumentException("Cannot bind argument at index "
|
||||
+ index + " because the index is out of range. "
|
||||
+ "The statement has " + mNumParameters + " parameters.");
|
||||
}
|
||||
mBindArgs[index - 1] = value;
|
||||
}
|
||||
|
||||
protected void B_setBindArgs() {
|
||||
Object[] bindArgs = getBindArgs();
|
||||
if(bindArgs == null) bindArgs = new Object[0];
|
||||
for(int i = 0; i < bindArgs.length; i++) {
|
||||
Object obj = bindArgs[i];
|
||||
try {
|
||||
getPreparedStatement().setObject(i + 1, obj);
|
||||
} catch (SQLException e) {
|
||||
throw new SQLiteException("Failed to bind argument: (" + i + ", " + obj + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ScrollableResultSet resultSet = null;
|
||||
ScrollableResultSet getResultSet() {
|
||||
if(resultSet == null) {
|
||||
try {
|
||||
resultSet = new ScrollableResultSet(getPreparedStatement().getResultSet());
|
||||
} catch (SQLException e) {
|
||||
throw new SQLiteException("Failed to get result set!", e);
|
||||
}
|
||||
}
|
||||
|
||||
return resultSet;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.CancellationSignal;
|
||||
import android.os.OperationCanceledException;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Represents a query that reads the resulting rows into a {@link SQLiteQuery}.
|
||||
* This class is used by {@link SQLiteCursor} and isn't useful itself.
|
||||
* <p>
|
||||
* This class is not thread-safe.
|
||||
* </p>
|
||||
*/
|
||||
public final class SQLiteQuery extends SQLiteProgram {
|
||||
private static final String TAG = "SQLiteQuery";
|
||||
|
||||
private final CancellationSignal mCancellationSignal;
|
||||
|
||||
SQLiteQuery(SQLiteDatabase db, String query, CancellationSignal cancellationSignal) {
|
||||
super(db, query, null, cancellationSignal);
|
||||
|
||||
mCancellationSignal = cancellationSignal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads rows into a buffer.
|
||||
*
|
||||
* @param window The window to fill into
|
||||
* @param startPos The start position for filling the window.
|
||||
* @param requiredPos The position of a row that MUST be in the window.
|
||||
* If it won't fit, then the query should discard part of what it filled.
|
||||
* @param countAllRows True to count all rows that the query would
|
||||
* return regardless of whether they fit in the window.
|
||||
* @return Number of rows that were enumerated. Might not be all rows
|
||||
* unless countAllRows is true.
|
||||
*
|
||||
* @throws SQLiteException if an error occurs.
|
||||
* @throws OperationCanceledException if the operation was canceled.
|
||||
*/
|
||||
int fillWindow(CursorWindow window, int startPos, int requiredPos, boolean countAllRows) {
|
||||
acquireReference();
|
||||
try {
|
||||
window.acquireReference();
|
||||
try {
|
||||
throw new SQLiteException("Not implemented!");
|
||||
} catch (SQLiteException ex) {
|
||||
Log.e(TAG, "exception: " + ex.getMessage() + "; query: " + getSql());
|
||||
throw ex;
|
||||
} finally {
|
||||
window.releaseReference();
|
||||
}
|
||||
} finally {
|
||||
releaseReference();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SQLiteQuery: " + getSql();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,648 @@
|
||||
/*
|
||||
* 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.os.CancellationSignal;
|
||||
import android.os.OperationCanceledException;
|
||||
import android.provider.BaseColumns;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
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 mStrict;
|
||||
|
||||
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 (<append chunk 1><append chunk2>) AND (<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 (<append chunk 1><append chunk2>) AND (<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 factory to use.
|
||||
*/
|
||||
public void setCursorFactory(SQLiteDatabase.CursorFactory factory) {
|
||||
mFactory = factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* When set, the selection is verified against malicious arguments.
|
||||
* When using this class to create a statement using
|
||||
* {@link #buildQueryString(boolean, String, String[], String, String, String, String, String)},
|
||||
* non-numeric limits will raise an exception. If a projection map is specified, fields
|
||||
* not in that map will be ignored.
|
||||
* If this class is used to execute the statement directly using
|
||||
* {@link #query(SQLiteDatabase, String[], String, String[], String, String, String)}
|
||||
* or
|
||||
* {@link #query(SQLiteDatabase, String[], String, String[], String, String, String, String)},
|
||||
* additionally also parenthesis escaping selection are caught.
|
||||
*
|
||||
* To summarize: To get maximum protection against malicious third party apps (for example
|
||||
* content provider consumers), make sure to do the following:
|
||||
* <ul>
|
||||
* <li>Set this value to true</li>
|
||||
* <li>Use a projection map</li>
|
||||
* <li>Use one of the query overloads instead of getting the statement as a sql string</li>
|
||||
* </ul>
|
||||
* By default, this value is false.
|
||||
*/
|
||||
public void setStrict(boolean flag) {
|
||||
mStrict = 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 */, null /* cancellationSignal */);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return query(db, projectionIn, selection, selectionArgs,
|
||||
groupBy, having, sortOrder, limit, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
|
||||
* If the operation is canceled, then {@link OperationCanceledException} will be thrown
|
||||
* when the query is executed.
|
||||
* @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, CancellationSignal cancellationSignal) {
|
||||
if (mTables == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (mStrict && selection != null && selection.length() > 0) {
|
||||
// Validate the user-supplied selection to detect syntactic anomalies
|
||||
// in the selection string that could indicate a SQL injection attempt.
|
||||
// The idea is to ensure that the selection clause is a valid SQL expression
|
||||
// by compiling it twice: once wrapped in parentheses and once as
|
||||
// originally specified. An attacker cannot create an expression that
|
||||
// would escape the SQL expression while maintaining balanced parentheses
|
||||
// in both the wrapped and original forms.
|
||||
String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy,
|
||||
having, sortOrder, limit);
|
||||
db.validateSql(sqlForValidation, cancellationSignal); // will throw if query is invalid
|
||||
}
|
||||
|
||||
String sql = buildQuery(
|
||||
projectionIn, selection, 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),
|
||||
cancellationSignal); // will throw if query is invalid
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method's signature is misleading since no SQL parameter
|
||||
* substitution is carried out. The selection arguments parameter does not get
|
||||
* used at all. To avoid confusion, call
|
||||
* {@link #buildQuery(String[], String, String, String, String, String)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public String buildQuery(
|
||||
String[] projectionIn, String selection, String[] selectionArgs,
|
||||
String groupBy, String having, String sortOrder, String limit) {
|
||||
return buildQuery(projectionIn, selection, 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 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 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, groupBy, having,
|
||||
null /* sortOrder */,
|
||||
null /* limit */);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method's signature is misleading since no SQL parameter
|
||||
* substitution is carried out. The selection arguments parameter does not get
|
||||
* used at all. To avoid confusion, call
|
||||
* {@link #buildUnionSubQuery}
|
||||
* instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public String buildUnionSubQuery(
|
||||
String typeDiscriminatorColumn,
|
||||
String[] unionColumns,
|
||||
Set<String> columnsPresentInTable,
|
||||
int computedColumnsOffset,
|
||||
String typeDiscriminatorValue,
|
||||
String selection,
|
||||
String[] selectionArgs,
|
||||
String groupBy,
|
||||
String having) {
|
||||
return buildUnionSubQuery(
|
||||
typeDiscriminatorColumn, unionColumns, columnsPresentInTable,
|
||||
computedColumnsOffset, typeDiscriminatorValue, selection,
|
||||
groupBy, having);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (!mStrict &&
|
||||
( 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;
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.database.sqlite;
|
||||
|
||||
public class SQLiteReadOnlyDatabaseException extends SQLiteException {
|
||||
public SQLiteReadOnlyDatabaseException() {}
|
||||
|
||||
public SQLiteReadOnlyDatabaseException(String error) {
|
||||
super(error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* 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.ParcelFileDescriptor;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Represents a statement that can be executed against a database. The statement
|
||||
* cannot return multiple rows or columns, but single value (1 x 1) result sets
|
||||
* are supported.
|
||||
* <p>
|
||||
* This class is not thread-safe.
|
||||
* </p>
|
||||
*/
|
||||
public final class SQLiteStatement extends SQLiteProgram {
|
||||
SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs) {
|
||||
super(db, sql, bindArgs, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute this SQL statement, if it is not a SELECT / INSERT / DELETE / UPDATE, for example
|
||||
* CREATE / DROP table, view, trigger, index etc.
|
||||
*
|
||||
* @throws android.database.SQLException If the SQL string is invalid for
|
||||
* some reason
|
||||
*/
|
||||
public void execute() {
|
||||
acquireReference();
|
||||
try {
|
||||
B_setBindArgs();
|
||||
getPreparedStatement().execute();
|
||||
} catch (SQLException e) {
|
||||
throw new SQLiteException("Failed to execute SQL statement!", e);
|
||||
} finally {
|
||||
releaseReference();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute this SQL statement, if the the number of rows affected by execution of this SQL
|
||||
* statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements.
|
||||
*
|
||||
* @return the number of rows affected by this SQL statement execution.
|
||||
* @throws android.database.SQLException If the SQL string is invalid for
|
||||
* some reason
|
||||
*/
|
||||
public int executeUpdateDelete() {
|
||||
acquireReference();
|
||||
try {
|
||||
B_setBindArgs();
|
||||
getPreparedStatement().execute();
|
||||
return getPreparedStatement().getUpdateCount();
|
||||
} catch (SQLException e) {
|
||||
throw new SQLiteException("Failed to execute SQL statement!", e);
|
||||
} finally {
|
||||
releaseReference();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
acquireReference();
|
||||
try {
|
||||
B_setBindArgs();
|
||||
getPreparedStatement().executeUpdate();
|
||||
ResultSet generated = getPreparedStatement().getGeneratedKeys();
|
||||
if(generated.next())
|
||||
return generated.getLong(1);
|
||||
else
|
||||
return -1;
|
||||
} catch (SQLException e) {
|
||||
throw new SQLiteException("Failed to execute SQL statement!", e);
|
||||
} finally {
|
||||
releaseReference();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
acquireReference();
|
||||
try {
|
||||
B_setBindArgs();
|
||||
getPreparedStatement().execute();
|
||||
if(getResultSet().next())
|
||||
return getResultSet().getLong(1);
|
||||
else
|
||||
throw new SQLiteDoneException();
|
||||
} catch (SQLException e) {
|
||||
throw new SQLiteException("Failed to execute SQL statement!", e);
|
||||
} finally {
|
||||
releaseReference();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
acquireReference();
|
||||
try {
|
||||
B_setBindArgs();
|
||||
getPreparedStatement().execute();
|
||||
if(getResultSet().next())
|
||||
return getResultSet().getString(1);
|
||||
else
|
||||
throw new SQLiteDoneException();
|
||||
} catch (SQLException e) {
|
||||
throw new SQLiteException("Failed to execute SQL statement!", e);
|
||||
} finally {
|
||||
releaseReference();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a statement that returns a 1 by 1 table with a blob value.
|
||||
*
|
||||
* @return A read-only file descriptor for a copy of the blob value, or {@code null}
|
||||
* if the value is null or could not be read for some reason.
|
||||
*
|
||||
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
|
||||
*/
|
||||
public ParcelFileDescriptor simpleQueryForBlobFileDescriptor() {
|
||||
throw new UnsupportedOperationException("Not implemented!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SQLiteProgram: " + getSql();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.database.sqlite;
|
||||
|
||||
/**
|
||||
* Describes a SQLite statement.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class SQLiteStatementInfo {
|
||||
/**
|
||||
* The number of parameters that the statement has.
|
||||
*/
|
||||
public int numParameters;
|
||||
|
||||
/**
|
||||
* The names of all columns in the result set of the statement.
|
||||
*/
|
||||
public String[] columnNames;
|
||||
|
||||
/**
|
||||
* True if the statement is read-only.
|
||||
*/
|
||||
public boolean readOnly;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.database.sqlite;
|
||||
|
||||
public class SQLiteTableLockedException extends SQLiteException {
|
||||
public SQLiteTableLockedException() {}
|
||||
|
||||
public SQLiteTableLockedException(String error) {
|
||||
super(error);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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.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)) {
|
||||
//From: com.android.internal.R.string.low_memory,
|
||||
Toast.makeText(context, "Device storage is full. Delete some files to free space.",
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<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 manage 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. If you are working
|
||||
with data sent to you by a provider, you do not use these SQLite classes, but
|
||||
instead use the generic {@link android.database} classes.
|
||||
|
||||
<p>The Android SDK and Android emulators both include the
|
||||
<a href="{@docRoot}studio/command-line/sqlite3.html">sqlite3</a> command-line
|
||||
database tool. On your development machine, run the tool from the
|
||||
<code>platform-tools/</code> folder of your SDK. On the emulator, run the tool
|
||||
with adb shell, for example, <code>adb -e shell sqlite3</code>.
|
||||
|
||||
<p>The version of SQLite depends on the version of Android. See the following table:
|
||||
<table style="width:auto;">
|
||||
<tr><th>Android API</th><th>SQLite Version</th></tr>
|
||||
<tr><td>API 24</td><td>3.9</td></tr>
|
||||
<tr><td>API 21</td><td>3.8</td></tr>
|
||||
<tr><td>API 11</td><td>3.7</td></tr>
|
||||
<tr><td>API 8</td><td>3.6</td></tr>
|
||||
<tr><td>API 3</td><td>3.5</td></tr>
|
||||
<tr><td>API 1</td><td>3.4</td></tr>
|
||||
</table>
|
||||
|
||||
<p>Some device manufacturers include different versions of SQLite on their devices.
|
||||
There are two ways to programmatically determine the version number.
|
||||
|
||||
<ul>
|
||||
<li>If available, use the sqlite3 tool, for example:
|
||||
<code>adb -e shell sqlite3 --version</code>.</li>
|
||||
<li>Create and query an in-memory database as shown in the following code sample:
|
||||
<pre>
|
||||
String query = "select sqlite_version() AS sqlite_version";
|
||||
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(":memory:", null);
|
||||
Cursor cursor = db.rawQuery(query, null);
|
||||
String sqliteVersion = "";
|
||||
if (cursor.moveToNext()) {
|
||||
sqliteVersion = cursor.getString(0);
|
||||
}</pre>
|
||||
</li>
|
||||
</ul>
|
||||
</BODY>
|
||||
</HTML>
|
||||
Reference in New Issue
Block a user