author | Gian-Carlo Pascutto <gpascutto@mozilla.com> |
Mon, 27 Feb 2012 12:28:22 +0100 | |
changeset 87832 | 3e6935243310b57940c140238306dab8fefafa51 |
parent 87831 | ff36792efde97c8409f92fa4ad5644a9caa6a9ef |
child 87833 | 0bf80d3cebf5d8cdc251d2c4432651350eddc3c7 |
push id | 22160 |
push user | mbrubeck@mozilla.com |
push date | Tue, 28 Feb 2012 17:21:33 +0000 |
treeherder | mozilla-central@dde4e0089a18 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | blassey |
bugs | 727264 |
milestone | 13.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -91,16 +91,17 @@ FENNEC_JAVA_FILES = \ GeckoProfile.java \ GeckoStateListDrawable.java \ GeckoThread.java \ GlobalHistory.java \ LinkPreference.java \ ProfileMigrator.java \ PromptService.java \ sqlite/ByteBufferInputStream.java \ + sqlite/MatrixBlobCursor.java \ sqlite/SQLiteBridge.java \ sqlite/SQLiteBridgeException.java \ SetupScreen.java \ SurfaceLockInfo.java \ Tab.java \ Tabs.java \ TabsTray.java \ gfx/BitmapUtils.java \
--- a/mobile/android/base/ProfileMigrator.java +++ b/mobile/android/base/ProfileMigrator.java @@ -40,37 +40,37 @@ package org.mozilla.gecko; import org.mozilla.gecko.db.BrowserContract; import org.mozilla.gecko.db.BrowserContract.Bookmarks; import org.mozilla.gecko.db.BrowserContract.History; import org.mozilla.gecko.db.BrowserContract.ImageColumns; import org.mozilla.gecko.db.BrowserContract.Images; import org.mozilla.gecko.db.BrowserContract.URLColumns; import org.mozilla.gecko.db.BrowserContract.SyncColumns; import org.mozilla.gecko.db.BrowserDB; -import org.mozilla.gecko.sqlite.ByteBufferInputStream; import org.mozilla.gecko.sqlite.SQLiteBridge; import org.mozilla.gecko.sqlite.SQLiteBridgeException; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.database.Cursor; import android.database.SQLException; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.provider.Browser; import android.util.Log; import android.net.Uri; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.File; -import java.nio.ByteBuffer; +import java.io.InputStream; import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -215,27 +215,30 @@ public class ProfileMigrator { } // We want to know the id of special root folders in the places DB, // and replace them by the corresponding root id in the Android DB. protected void calculateReroot(SQLiteBridge db) { mRerootMap = new HashMap<Long, Long>(); try { - ArrayList<Object[]> queryResult = db.query(kRootQuery); - final int rootCol = db.getColumnIndex(kRootName); - final int folderCol = db.getColumnIndex(kRootFolderId); + Cursor cursor = db.rawQuery(kRootQuery, null); + final int rootCol = cursor.getColumnIndex(kRootName); + final int folderCol = cursor.getColumnIndex(kRootFolderId); - for (Object[] resultRow: queryResult) { - String name = (String)resultRow[rootCol]; - long placesFolderId = Integer.parseInt((String)resultRow[folderCol]); + cursor.moveToFirst(); + while (!cursor.isAfterLast()) { + String name = cursor.getString(rootCol); + long placesFolderId = cursor.getLong(folderCol); mRerootMap.put(placesFolderId, getFolderId(name)); Log.v(LOGTAG, "Name: " + name + ", pid=" + placesFolderId + ", nid=" + mRerootMap.get(placesFolderId)); + cursor.moveToNext(); } + cursor.close(); } catch (SQLiteBridgeException e) { Log.e(LOGTAG, "Failed to get bookmark roots: ", e); return; } } // Get a list of the last times an URL was accessed protected Map<String, Long> gatherBrowserDBHistory() { @@ -332,25 +335,25 @@ public class ProfileMigrator { mCr.insert(getHistoryUri(), values); } } finally { if (cursor != null) cursor.close(); } } - protected BitmapDrawable decodeImageData(ByteBuffer data) { - ByteBufferInputStream byteStream = new ByteBufferInputStream(data); + protected BitmapDrawable decodeImageData(byte[] data) { + InputStream byteStream = new ByteArrayInputStream(data); BitmapDrawable image = (BitmapDrawable)Drawable.createFromStream(byteStream, "src"); return image; } protected void addFavicon(String url, String faviconUrl, String faviconGuid, - String mime, ByteBuffer data) { + String mime, byte[] data) { // Some GIFs can cause us to lock up completely // without exceptions or anything. Not cool. if (mime == null || mime.compareTo("image/gif") == 0) { return; } BitmapDrawable image = null; // Decode non-PNG images. if (mime.compareTo("image/png") != 0) { @@ -368,19 +371,17 @@ public class ProfileMigrator { if (image != null) { Bitmap bitmap = image.getBitmap(); ByteArrayOutputStream stream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); values.put(Images.FAVICON, stream.toByteArray()); } else { // PNG images can be passed directly. Well, aside // from having to convert them into a byte[]. - byte[] byteArray = new byte[data.remaining()]; - data.get(byteArray); - values.put(Images.FAVICON, byteArray); + values.put(Images.FAVICON, data); } values.put(Images.URL, url); values.put(Images.FAVICON_URL, faviconUrl); // Restore deleted record if possible values.put(Images.IS_DELETED, 0); values.put(Images.GUID, faviconGuid); @@ -407,46 +408,48 @@ public class ProfileMigrator { /* current time */ Long.toString(System.currentTimeMillis()), /* History entries to return. No point in retrieving more than we can store. */ Integer.toString(BrowserDB.getMaxHistoryCount()) }; - ArrayList<Object[]> queryResult = - db.query(kHistoryQuery, queryParams); - final int urlCol = db.getColumnIndex(kHistoryUrl); - final int titleCol = db.getColumnIndex(kHistoryTitle); - final int dateCol = db.getColumnIndex(kHistoryDate); - final int visitsCol = db.getColumnIndex(kHistoryVisits); - final int faviconMimeCol = db.getColumnIndex(kFaviconMime); - final int faviconDataCol = db.getColumnIndex(kFaviconData); - final int faviconUrlCol = db.getColumnIndex(kFaviconUrl); - final int faviconGuidCol = db.getColumnIndex(kFaviconGuid); + Cursor cursor = db.rawQuery(kHistoryQuery, queryParams); + final int urlCol = cursor.getColumnIndex(kHistoryUrl); + final int titleCol = cursor.getColumnIndex(kHistoryTitle); + final int dateCol = cursor.getColumnIndex(kHistoryDate); + final int visitsCol = cursor.getColumnIndex(kHistoryVisits); + final int faviconMimeCol = cursor.getColumnIndex(kFaviconMime); + final int faviconDataCol = cursor.getColumnIndex(kFaviconData); + final int faviconUrlCol = cursor.getColumnIndex(kFaviconUrl); + final int faviconGuidCol = cursor.getColumnIndex(kFaviconGuid); - for (Object[] resultRow: queryResult) { - String url = (String)resultRow[urlCol]; - String title = (String)resultRow[titleCol]; - long date = Long.parseLong((String)(resultRow[dateCol])) / (long)1000; - int visits = Integer.parseInt((String)(resultRow[visitsCol])); - ByteBuffer faviconDataBuff = (ByteBuffer)resultRow[faviconDataCol]; - String faviconMime = (String)resultRow[faviconMimeCol]; - String faviconUrl = (String)resultRow[faviconUrlCol]; - String faviconGuid = (String)resultRow[faviconGuidCol]; + cursor.moveToFirst(); + while (!cursor.isAfterLast()) { + String url = cursor.getString(urlCol); + String title = cursor.getString(titleCol); + long date = cursor.getLong(dateCol) / (long)1000; + int visits = cursor.getInt(visitsCol); + byte[] faviconDataBuff = cursor.getBlob(faviconDataCol); + String faviconMime = cursor.getString(faviconMimeCol); + String faviconUrl = cursor.getString(faviconUrlCol); + String faviconGuid = cursor.getString(faviconGuidCol); try { placesHistory.add(url); addFavicon(url, faviconUrl, faviconGuid, faviconMime, faviconDataBuff); addHistory(browserDBHistory, url, title, date, visits); } catch (Exception e) { Log.e(LOGTAG, "Error adding history entry: ", e); } + cursor.moveToNext(); } + cursor.close(); } catch (SQLiteBridgeException e) { Log.e(LOGTAG, "Failed to get history: ", e); return; } // GlobalHistory access communicates with Gecko // and must run on its thread GeckoAppShell.getHandler().post(new Runnable() { public void run() { @@ -494,30 +497,30 @@ public class ProfileMigrator { } if (updated == 0) { mCr.insert(getBookmarksUri(), values); } } protected void migrateBookmarks(SQLiteBridge db) { try { - ArrayList<Object[]> queryResult = db.query(kBookmarkQuery); - final int urlCol = db.getColumnIndex(kBookmarkUrl); - final int titleCol = db.getColumnIndex(kBookmarkTitle); - final int guidCol = db.getColumnIndex(kBookmarkGuid); - final int idCol = db.getColumnIndex(kBookmarkId); - final int typeCol = db.getColumnIndex(kBookmarkType); - final int parentCol = db.getColumnIndex(kBookmarkParent); - final int addedCol = db.getColumnIndex(kBookmarkAdded); - final int modifiedCol = db.getColumnIndex(kBookmarkModified); - final int positionCol = db.getColumnIndex(kBookmarkPosition); - final int faviconMimeCol = db.getColumnIndex(kFaviconMime); - final int faviconDataCol = db.getColumnIndex(kFaviconData); - final int faviconUrlCol = db.getColumnIndex(kFaviconUrl); - final int faviconGuidCol = db.getColumnIndex(kFaviconGuid); + Cursor cursor = db.rawQuery(kBookmarkQuery, null); + final int urlCol = cursor.getColumnIndex(kBookmarkUrl); + final int titleCol = cursor.getColumnIndex(kBookmarkTitle); + final int guidCol = cursor.getColumnIndex(kBookmarkGuid); + final int idCol = cursor.getColumnIndex(kBookmarkId); + final int typeCol = cursor.getColumnIndex(kBookmarkType); + final int parentCol = cursor.getColumnIndex(kBookmarkParent); + final int addedCol = cursor.getColumnIndex(kBookmarkAdded); + final int modifiedCol = cursor.getColumnIndex(kBookmarkModified); + final int positionCol = cursor.getColumnIndex(kBookmarkPosition); + final int faviconMimeCol = cursor.getColumnIndex(kFaviconMime); + final int faviconDataCol = cursor.getColumnIndex(kFaviconData); + final int faviconUrlCol = cursor.getColumnIndex(kFaviconUrl); + final int faviconGuidCol = cursor.getColumnIndex(kFaviconGuid); // The keys are places IDs. Set<Long> openFolders = new HashSet<Long>(); Set<Long> knownFolders = new HashSet<Long>(mRerootMap.keySet()); // We iterate over all bookmarks, and add all bookmarks that // have their parent folders present. If there are bookmarks // that we can't add, we remember what these are and try again @@ -530,43 +533,48 @@ public class ProfileMigrator { do { // Reset the set of missing folders that block us from // adding entries. openFolders.clear(); int added = 0; int skipped = 0; - for (Object[] resultRow: queryResult) { - long id = Long.parseLong((String)resultRow[idCol]); + cursor.moveToFirst(); + while (!cursor.isAfterLast()) { + long id = cursor.getLong(idCol); // Already processed? if so just skip - if (processedBookmarks.contains(id)) + if (processedBookmarks.contains(id)) { + cursor.moveToNext(); continue; + } - int type = Integer.parseInt((String)resultRow[typeCol]); - long parent = Long.parseLong((String)resultRow[parentCol]); + int type = cursor.getInt(typeCol); + long parent = cursor.getLong(parentCol); // Places has an explicit root folder, id=1 parent=0. // Skip that. - if (id == 1 && parent == 0 && type == kPlacesTypeFolder) + if (id == 1 && parent == 0 && type == kPlacesTypeFolder) { + cursor.moveToNext(); continue; + } - String url = (String)resultRow[urlCol]; - String title = (String)resultRow[titleCol]; - String guid = (String)resultRow[guidCol]; + String url = cursor.getString(urlCol); + String title = cursor.getString(titleCol); + String guid = cursor.getString(guidCol); long dateadded = - Long.parseLong((String)resultRow[addedCol]) / (long)1000; + cursor.getLong(addedCol) / (long)1000; long datemodified = - Long.parseLong((String)resultRow[modifiedCol]) / (long)1000; - long position = Long.parseLong((String)resultRow[positionCol]); - ByteBuffer faviconDataBuff = (ByteBuffer)resultRow[faviconDataCol]; - String faviconMime = (String)resultRow[faviconMimeCol]; - String faviconUrl = (String)resultRow[faviconUrlCol]; - String faviconGuid = (String)resultRow[faviconGuidCol]; + cursor.getLong(modifiedCol) / (long)1000; + long position = cursor.getLong(positionCol); + byte[] faviconDataBuff = cursor.getBlob(faviconDataCol); + String faviconMime = cursor.getString(faviconMimeCol); + String faviconUrl = cursor.getString(faviconUrlCol); + String faviconGuid = cursor.getString(faviconGuidCol); // Is the parent for this bookmark already added? // If so, we can add the bookmark itself. if (knownFolders.contains(parent)) { try { boolean isFolder = (type == kPlacesTypeFolder); addBookmark(url, title, guid, parent, dateadded, datemodified, @@ -585,16 +593,17 @@ public class ProfileMigrator { Log.e(LOGTAG, "Error adding bookmark: ", e); } added++; } else { // We have to postpone until parent is processed; openFolders.add(parent); skipped++; } + cursor.moveToNext(); } // Now check if any of the new folders we added was a folder // that we were blocked on, by intersecting openFolders and // knownFolders. If this is empty, we're done because the next // iteration can't make progress. boolean changed = openFolders.retainAll(knownFolders); @@ -603,16 +612,18 @@ public class ProfileMigrator { // those folders are orphans. Report this situation here. if (openFolders.isEmpty() && changed) { Log.w(LOGTAG, "Orphaned bookmarks found, not imported"); } iterations++; Log.i(LOGTAG, "Iteration = " + iterations + ", added " + added + " bookmark(s), skipped " + skipped + " bookmark(s)"); } while (!openFolders.isEmpty()); + + cursor.close(); } catch (SQLiteBridgeException e) { Log.e(LOGTAG, "Failed to get bookmarks: ", e); return; } } protected void migratePlaces(File aFile) { String dbPath = aFile.getPath() + "/places.sqlite";
new file mode 100644 --- /dev/null +++ b/mobile/android/base/sqlite/MatrixBlobCursor.java @@ -0,0 +1,310 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- +/* + * 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 org.mozilla.gecko.sqlite; + +import org.mozilla.gecko.sqlite.SQLiteBridgeException; + +import android.database.AbstractCursor; +import android.database.CursorIndexOutOfBoundsException; +import android.database.DatabaseUtils; +import java.lang.UnsupportedOperationException; +import java.nio.ByteBuffer; +import java.util.ArrayList; + +/* + * Android's AbstractCursor throws on getBlob() + * and MatrixCursor forgot to override it. This was fixed + * at some point but old devices are still SOL. + * Oh, and everything in MatrixCursor is private instead of + * protected, so we need to entirely duplicate it here, + * instad of just being able to add the missing method. + */ +/** + * A mutable cursor implementation backed by an array of {@code Object}s. Use + * {@link #newRow()} to add rows. Automatically expands internal capacity + * as needed. + */ +public class MatrixBlobCursor extends AbstractCursor { + + private final String[] columnNames; + private Object[] data; + private int rowCount = 0; + private final int columnCount; + + /** + * Constructs a new cursor with the given initial capacity. + * + * @param columnNames names of the columns, the ordering of which + * determines column ordering elsewhere in this cursor + * @param initialCapacity in rows + */ + public MatrixBlobCursor(String[] columnNames, int initialCapacity) { + this.columnNames = columnNames; + this.columnCount = columnNames.length; + + if (initialCapacity < 1) { + initialCapacity = 1; + } + + this.data = new Object[columnCount * initialCapacity]; + } + + /** + * Constructs a new cursor. + * + * @param columnNames names of the columns, the ordering of which + * determines column ordering elsewhere in this cursor + */ + public MatrixBlobCursor(String[] columnNames) { + this(columnNames, 16); + } + + /** + * Gets value at the given column for the current row. + */ + protected Object get(int column) { + if (column < 0 || column >= columnCount) { + throw new CursorIndexOutOfBoundsException("Requested column: " + + column + ", # of columns: " + columnCount); + } + if (mPos < 0) { + throw new CursorIndexOutOfBoundsException("Before first row."); + } + if (mPos >= rowCount) { + throw new CursorIndexOutOfBoundsException("After last row."); + } + return data[mPos * columnCount + column]; + } + + /** + * Adds a new row to the end and returns a builder for that row. Not safe + * for concurrent use. + * + * @return builder which can be used to set the column values for the new + * row + */ + public RowBuilder newRow() { + rowCount++; + int endIndex = rowCount * columnCount; + ensureCapacity(endIndex); + int start = endIndex - columnCount; + return new RowBuilder(start, endIndex); + } + + /** + * Adds a new row to the end with the given column values. Not safe + * for concurrent use. + * + * @throws IllegalArgumentException if {@code columnValues.length != + * columnNames.length} + * @param columnValues in the same order as the the column names specified + * at cursor construction time + */ + public void addRow(Object[] columnValues) { + if (columnValues.length != columnCount) { + throw new IllegalArgumentException("columnNames.length = " + + columnCount + ", columnValues.length = " + + columnValues.length); + } + + int start = rowCount++ * columnCount; + ensureCapacity(start + columnCount); + System.arraycopy(columnValues, 0, data, start, columnCount); + } + + /** + * Adds a new row to the end with the given column values. Not safe + * for concurrent use. + * + * @throws IllegalArgumentException if {@code columnValues.size() != + * columnNames.length} + * @param columnValues in the same order as the the column names specified + * at cursor construction time + */ + public void addRow(Iterable<?> columnValues) { + int start = rowCount * columnCount; + int end = start + columnCount; + ensureCapacity(end); + + if (columnValues instanceof ArrayList<?>) { + addRow((ArrayList<?>) columnValues, start); + return; + } + + int current = start; + Object[] localData = data; + for (Object columnValue : columnValues) { + if (current == end) { + // TODO: null out row? + throw new IllegalArgumentException( + "columnValues.size() > columnNames.length"); + } + localData[current++] = columnValue; + } + + if (current != end) { + // TODO: null out row? + throw new IllegalArgumentException( + "columnValues.size() < columnNames.length"); + } + + // Increase row count here in case we encounter an exception. + rowCount++; + } + + /** Optimization for {@link ArrayList}. */ + private void addRow(ArrayList<?> columnValues, int start) { + int size = columnValues.size(); + if (size != columnCount) { + throw new IllegalArgumentException("columnNames.length = " + + columnCount + ", columnValues.size() = " + size); + } + + rowCount++; + Object[] localData = data; + for (int i = 0; i < size; i++) { + localData[start + i] = columnValues.get(i); + } + } + + /** Ensures that this cursor has enough capacity. */ + private void ensureCapacity(int size) { + if (size > data.length) { + Object[] oldData = this.data; + int newSize = data.length * 2; + if (newSize < size) { + newSize = size; + } + this.data = new Object[newSize]; + System.arraycopy(oldData, 0, this.data, 0, oldData.length); + } + } + + /** + * Builds a row, starting from the left-most column and adding one column + * value at a time. Follows the same ordering as the column names specified + * at cursor construction time. + */ + public class RowBuilder { + + private int index; + private final int endIndex; + + RowBuilder(int index, int endIndex) { + this.index = index; + this.endIndex = endIndex; + } + + /** + * Sets the next column value in this row. + * + * @throws CursorIndexOutOfBoundsException if you try to add too many + * values + * @return this builder to support chaining + */ + public RowBuilder add(Object columnValue) { + if (index == endIndex) { + throw new CursorIndexOutOfBoundsException( + "No more columns left."); + } + + data[index++] = columnValue; + return this; + } + } + + // AbstractCursor implementation. + + @Override + public int getCount() { + return rowCount; + } + + @Override + public String[] getColumnNames() { + return columnNames; + } + + @Override + public String getString(int column) { + Object value = get(column); + if (value == null) return null; + return value.toString(); + } + + @Override + public short getShort(int column) { + Object value = get(column); + if (value == null) return 0; + if (value instanceof Number) return ((Number) value).shortValue(); + return Short.parseShort(value.toString()); + } + + @Override + public int getInt(int column) { + Object value = get(column); + if (value == null) return 0; + if (value instanceof Number) return ((Number) value).intValue(); + return Integer.parseInt(value.toString()); + } + + @Override + public long getLong(int column) { + Object value = get(column); + if (value == null) return 0; + if (value instanceof Number) return ((Number) value).longValue(); + return Long.parseLong(value.toString()); + } + + @Override + public float getFloat(int column) { + Object value = get(column); + if (value == null) return 0.0f; + if (value instanceof Number) return ((Number) value).floatValue(); + return Float.parseFloat(value.toString()); + } + + @Override + public double getDouble(int column) { + Object value = get(column); + if (value == null) return 0.0d; + if (value instanceof Number) return ((Number) value).doubleValue(); + return Double.parseDouble(value.toString()); + } + + @Override + public byte[] getBlob(int column) { + Object value = get(column); + if (value == null) return null; + if (value instanceof byte[]) { + return (byte[]) value; + } + if (value instanceof ByteBuffer) { + ByteBuffer data = (ByteBuffer)value; + byte[] byteArray = new byte[data.remaining()]; + data.get(byteArray); + return byteArray; + } + throw new UnsupportedOperationException("BLOB Object not of known type"); + } + + @Override + public boolean isNull(int column) { + return get(column) == null; + } +}
--- a/mobile/android/base/sqlite/SQLiteBridge.java +++ b/mobile/android/base/sqlite/SQLiteBridge.java @@ -1,18 +1,18 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko.sqlite; import org.mozilla.gecko.sqlite.SQLiteBridgeException; +import org.mozilla.gecko.sqlite.MatrixBlobCursor; import android.content.ContentValues; import android.database.Cursor; -import android.database.MatrixCursor; import android.text.TextUtils; import android.util.Log; import java.lang.String; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -49,35 +49,35 @@ public class SQLiteBridge { // Takes the path to the database we want to access. public SQLiteBridge(String aDb) throws SQLiteBridgeException { mDb = aDb; } // Executes a simple line of sql. public void execSQL(String sql) throws SQLiteBridgeException { - query(sql, null); + internalQuery(sql, null); } // Executes a simple line of sql. Allow you to bind arguments public void execSQL(String sql, String[] bindArgs) throws SQLiteBridgeException { - query(sql, bindArgs); + internalQuery(sql, bindArgs); } // Executes a DELETE statement on the database public int delete(String table, String whereClause, String[] whereArgs) throws SQLiteBridgeException { StringBuilder sb = new StringBuilder("DELETE from "); sb.append(table); if (whereClause != null) { sb.append(" WHERE " + whereClause); } - query(sb.toString(), whereArgs); + internalQuery(sb.toString(), whereArgs); return mQueryResults[kResultRowsChanged].intValue(); } public Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, @@ -109,20 +109,26 @@ public class SQLiteBridge { if (orderBy != null) { sb.append(" ORDER BY " + orderBy); } if (limit != null) { sb.append(" " + limit); } + return rawQuery(sb.toString(), selectionArgs); + } + + public Cursor rawQuery(String sql, String[] selectionArgs) + throws SQLiteBridgeException { ArrayList<Object[]> results; - results = query(sb.toString(), selectionArgs); + results = internalQuery(sql, selectionArgs); - MatrixCursor cursor = new MatrixCursor(mColumns.toArray(new String[0])); + MatrixBlobCursor cursor = + new MatrixBlobCursor(mColumns.toArray(new String[0])); try { for (Object resultRow: results) { Object[] resultColumns = (Object[])resultRow; if (resultColumns.length == mColumns.size()) cursor.addRow(resultColumns); } } catch(IllegalArgumentException ex) { Log.e(LOGTAG, "Error getting rows", ex); @@ -155,17 +161,17 @@ public class SQLiteBridge { // XXX - Do we need to bind these values? sb.append(" VALUES ("); sb.append(TextUtils.join(", ", valueNames)); sb.append(") "); String[] binds = new String[valueBinds.size()]; valueBinds.toArray(binds); - query(sb.toString(), binds); + internalQuery(sb.toString(), binds); return mQueryResults[kResultInsertRowId]; } public int update(String table, ContentValues values, String whereClause, String[] whereArgs) throws SQLiteBridgeException { Set<Entry<String, Object>> valueSet = values.valueSet(); Iterator<Entry<String, Object>> valueIterator = valueSet.iterator(); ArrayList<String> valueNames = new ArrayList<String>(); @@ -190,59 +196,48 @@ public class SQLiteBridge { for (int i = 0; i < whereArgs.length; i++) { valueNames.add(whereArgs[i]); } } String[] binds = new String[valueNames.size()]; valueNames.toArray(binds); - query(sb.toString(), binds); + internalQuery(sb.toString(), binds); return mQueryResults[kResultRowsChanged].intValue(); } public int getVersion() throws SQLiteBridgeException { ArrayList<Object[]> results = null; - results = query("PRAGMA user_version"); + results = internalQuery("PRAGMA user_version", null); int ret = -1; if (results != null) { for (Object resultRow: results) { Object[] resultColumns = (Object[])resultRow; String version = (String)resultColumns[0]; ret = Integer.parseInt(version); } } return ret; } - // Do an SQL query without parameters - public ArrayList<Object[]> query(String aQuery) throws SQLiteBridgeException { - return query(aQuery, null); - } - // Do an SQL query, substituting the parameters in the query with the passed // parameters. The parameters are subsituded in order, so named parameters // are not supported. // The result is returned as an ArrayList<Object[]>, with each // row being an entry in the ArrayList, and each column being one Object // in the Object[] array. The columns are of type null, // direct ByteBuffer (BLOB), or String (everything else). - public ArrayList<Object[]> query(String aQuery, String[] aParams) + private ArrayList<Object[]> internalQuery(String aQuery, String[] aParams) throws SQLiteBridgeException { ArrayList<Object[]> result = new ArrayList<Object[]>(); mQueryResults = new Long[2]; mColumns = new ArrayList<String>(); sqliteCall(mDb, aQuery, aParams, mColumns, mQueryResults, result); return result; } - // Gets the index in the row Object[] for the given column name. - // Returns -1 if not found. - public int getColumnIndex(String aColumnName) { - return mColumns.lastIndexOf(aColumnName); - } - // nop, provided for API compatibility with SQLiteDatabase. public void close() { } -} \ No newline at end of file +}