Index: build.gradle
==================================================================
--- build.gradle
+++ build.gradle
@@ -3,11 +3,11 @@
buildscript {
repositories {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.3.3'
+ classpath 'com.android.tools.build:gradle:3.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
Index: gradle/wrapper/gradle-wrapper.properties
==================================================================
--- gradle/wrapper/gradle-wrapper.properties
+++ gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Thu Apr 27 23:58:23 ICT 2017
+#Sun Nov 12 03:46:22 ICT 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
Index: sqlite3/build.gradle
==================================================================
--- sqlite3/build.gradle
+++ sqlite3/build.gradle
@@ -1,15 +1,12 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 25
- buildToolsVersion "25.0.2"
-
defaultConfig {
minSdkVersion 16
- targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
DELETED sqlite3/src/androidTest/java/org/sqlite/database/ApplicationTest.java
Index: sqlite3/src/androidTest/java/org/sqlite/database/ApplicationTest.java
==================================================================
--- sqlite3/src/androidTest/java/org/sqlite/database/ApplicationTest.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.sqlite.database;
-
-import android.app.Application;
-import android.test.ApplicationTestCase;
-
-/**
- * Testing Fundamentals
- */
-public class ApplicationTest extends ApplicationTestCase {
- public ApplicationTest() {
- super(Application.class);
- }
-}
ADDED sqlite3/src/androidTest/java/org/sqlite/database/database_cts/AbstractCursorTest.java
Index: sqlite3/src/androidTest/java/org/sqlite/database/database_cts/AbstractCursorTest.java
==================================================================
--- /dev/null
+++ sqlite3/src/androidTest/java/org/sqlite/database/database_cts/AbstractCursorTest.java
@@ -0,0 +1,641 @@
+/*
+ * 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 org.sqlite.database.database_cts;
+
+import android.content.Context;
+import android.database.AbstractCursor;
+import android.database.CharArrayBuffer;
+import android.database.ContentObserver;
+import android.database.CursorIndexOutOfBoundsException;
+import android.database.CursorWindow;
+import android.database.DataSetObserver;
+import org.sqlite.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.test.InstrumentationTestCase;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Random;
+
+/**
+ * Test {@link AbstractCursor}.
+ */
+public class AbstractCursorTest extends InstrumentationTestCase {
+ private static final int POSITION0 = 0;
+ private static final int POSITION1 = 1;
+ private static final int ROW_MAX = 10;
+ private static final int DATA_COUNT = 10;
+ private static final String[] COLUMN_NAMES1 = new String[] {
+ "_id", // 0
+ "number" // 1
+ };
+ private static final String[] COLUMN_NAMES = new String[] { "name", "number", "profit" };
+ private TestAbstractCursor mTestAbstractCursor;
+ private Object mLockObj = new Object();
+
+ private SQLiteDatabase mDatabase;
+ private File mDatabaseFile;
+ private AbstractCursor mDatabaseCursor;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ System.loadLibrary("sqliteX");
+ setupDatabase();
+ ArrayList list = createTestList(ROW_MAX, COLUMN_NAMES.length);
+ mTestAbstractCursor = new TestAbstractCursor(COLUMN_NAMES, list);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDatabaseCursor.close();
+ mTestAbstractCursor.close();
+ mDatabase.close();
+ if (mDatabaseFile.exists()) {
+ mDatabaseFile.delete();
+ }
+ super.tearDown();
+ }
+
+ public void testConstructor() {
+ TestAbstractCursor abstractCursor = new TestAbstractCursor();
+ assertEquals(-1, abstractCursor.getPosition());
+ }
+
+ public void testGetBlob() {
+ try {
+ mTestAbstractCursor.getBlob(0);
+ fail("getBlob should throws a UnsupportedOperationException here");
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+ }
+
+ public void testRegisterDataSetObserver() {
+ MockDataSetObserver datasetObserver = new MockDataSetObserver();
+
+ try {
+ mDatabaseCursor.unregisterDataSetObserver(datasetObserver);
+ fail("Can't unregister DataSetObserver before it is registered.");
+ } catch (IllegalStateException e) {
+ // expected
+ }
+
+ mDatabaseCursor.registerDataSetObserver(datasetObserver);
+
+ try {
+ mDatabaseCursor.registerDataSetObserver(datasetObserver);
+ fail("Can't register DataSetObserver twice before unregister it.");
+ } catch (IllegalStateException e) {
+ // expected
+ }
+
+ mDatabaseCursor.unregisterDataSetObserver(datasetObserver);
+ mDatabaseCursor.registerDataSetObserver(datasetObserver);
+ }
+
+ public void testRegisterContentObserver() {
+ MockContentObserver contentObserver = new MockContentObserver();
+
+ try {
+ mDatabaseCursor.unregisterContentObserver(contentObserver);
+ fail("Can't unregister ContentObserver before it is registered.");
+ } catch (IllegalStateException e) {
+ // expected
+ }
+
+ mDatabaseCursor.registerContentObserver(contentObserver);
+
+ try {
+ mDatabaseCursor.registerContentObserver(contentObserver);
+ fail("Can't register DataSetObserver twice before unregister it.");
+ } catch (IllegalStateException e) {
+ // expected
+ }
+
+ mDatabaseCursor.unregisterContentObserver(contentObserver);
+ mDatabaseCursor.registerContentObserver(contentObserver);
+ }
+
+ public void testSetNotificationUri() {
+ final Uri testUri = Settings.System.getUriFor(Settings.System.TIME_12_24);
+ mDatabaseCursor.setNotificationUri(getInstrumentation().getContext().getContentResolver(),
+ testUri);
+ }
+
+ public void testRespond() {
+ Bundle b = new Bundle();
+ Bundle bundle = mDatabaseCursor.respond(b);
+ assertSame(Bundle.EMPTY, bundle);
+
+ bundle = mDatabaseCursor.respond(null);
+ assertSame(Bundle.EMPTY, bundle);
+ }
+
+ public void testRequery() {
+ MockDataSetObserver mock = new MockDataSetObserver();
+ mDatabaseCursor.registerDataSetObserver(mock);
+ assertFalse(mock.hadCalledOnChanged());
+ mDatabaseCursor.requery();
+ assertTrue(mock.hadCalledOnChanged());
+ }
+
+ public void testOnChange() throws InterruptedException {
+ MockContentObserver mock = new MockContentObserver();
+ mTestAbstractCursor.registerContentObserver(mock);
+ assertFalse(mock.hadCalledOnChange());
+ mTestAbstractCursor.onChange(true);
+ synchronized(mLockObj) {
+ if ( !mock.hadCalledOnChange() ) {
+ mLockObj.wait(5000);
+ }
+ }
+ assertTrue(mock.hadCalledOnChange());
+ }
+
+ public void testOnMove() {
+ assertFalse(mTestAbstractCursor.getOnMoveRet());
+ mTestAbstractCursor.moveToFirst();
+ assertTrue(mTestAbstractCursor.getOnMoveRet());
+ assertEquals(1, mTestAbstractCursor.getRowsMovedSum());
+
+ mTestAbstractCursor.moveToPosition(5);
+ assertTrue(mTestAbstractCursor.getOnMoveRet());
+ assertEquals(6, mTestAbstractCursor.getRowsMovedSum());
+ assertEquals(0, mTestAbstractCursor.getOldPos());
+ assertEquals(5, mTestAbstractCursor.getNewPos());
+ }
+
+ public void testOnMove_samePosition() {
+ mTestAbstractCursor.moveToFirst();
+ mTestAbstractCursor.moveToPosition(5);
+ assertEquals(6, mTestAbstractCursor.getRowsMovedSum());
+ mTestAbstractCursor.moveToPosition(5);
+ // Moving to the same position should either call onMove(5, 5)
+ // or be a no-op. It should no change the RowsMovedSum.
+ assertEquals(6, mTestAbstractCursor.getRowsMovedSum());
+ }
+
+ public void testMoveToPrevious() {
+ // Test moveToFirst, isFirst, moveToNext, getPosition
+ assertTrue(mDatabaseCursor.moveToFirst());
+ assertTrue(mDatabaseCursor.isFirst());
+ assertEquals(0, mDatabaseCursor.getPosition());
+ assertTrue(mDatabaseCursor.moveToNext());
+ assertEquals(1, mDatabaseCursor.getPosition());
+ assertFalse(mDatabaseCursor.isFirst());
+ assertTrue(mDatabaseCursor.moveToNext());
+ assertEquals(2, mDatabaseCursor.getPosition());
+
+ // invoke moveToPosition with a number larger than row count.
+ assertFalse(mDatabaseCursor.moveToPosition(30000));
+ assertEquals(mDatabaseCursor.getCount(), mDatabaseCursor.getPosition());
+
+ assertFalse(mDatabaseCursor.moveToPosition(-1));
+ assertEquals(-1, mDatabaseCursor.getPosition());
+ assertTrue(mDatabaseCursor.isBeforeFirst());
+
+ mDatabaseCursor.moveToPosition(5);
+ assertEquals(5, mDatabaseCursor.getPosition());
+
+ // Test moveToPrevious
+ assertTrue(mDatabaseCursor.moveToPrevious());
+ assertEquals(4, mDatabaseCursor.getPosition());
+ assertTrue(mDatabaseCursor.moveToPrevious());
+ assertEquals(3, mDatabaseCursor.getPosition());
+ assertTrue(mDatabaseCursor.moveToPrevious());
+ assertEquals(2, mDatabaseCursor.getPosition());
+
+ // Test moveToLast, isLast, moveToPrevius, isAfterLast.
+ assertFalse(mDatabaseCursor.isLast());
+ assertTrue(mDatabaseCursor.moveToLast());
+ assertTrue(mDatabaseCursor.isLast());
+ assertFalse(mDatabaseCursor.isAfterLast());
+
+ assertFalse(mDatabaseCursor.moveToNext());
+ assertTrue(mDatabaseCursor.isAfterLast());
+ assertFalse(mDatabaseCursor.moveToNext());
+ assertTrue(mDatabaseCursor.isAfterLast());
+ assertFalse(mDatabaseCursor.isLast());
+ assertTrue(mDatabaseCursor.moveToPrevious());
+ assertTrue(mDatabaseCursor.isLast());
+ assertTrue(mDatabaseCursor.moveToPrevious());
+ assertFalse(mDatabaseCursor.isLast());
+
+ // Test move(int).
+ mDatabaseCursor.moveToFirst();
+ assertEquals(0, mDatabaseCursor.getPosition());
+ assertFalse(mDatabaseCursor.move(-1));
+ assertEquals(-1, mDatabaseCursor.getPosition());
+ assertTrue(mDatabaseCursor.move(1));
+ assertEquals(0, mDatabaseCursor.getPosition());
+
+ assertTrue(mDatabaseCursor.move(5));
+ assertEquals(5, mDatabaseCursor.getPosition());
+ assertTrue(mDatabaseCursor.move(-1));
+ assertEquals(4, mDatabaseCursor.getPosition());
+
+ mDatabaseCursor.moveToLast();
+ assertTrue(mDatabaseCursor.isLast());
+ assertFalse(mDatabaseCursor.isAfterLast());
+ assertFalse(mDatabaseCursor.move(1));
+ assertFalse(mDatabaseCursor.isLast());
+ assertTrue(mDatabaseCursor.isAfterLast());
+ assertTrue(mDatabaseCursor.move(-1));
+ assertTrue(mDatabaseCursor.isLast());
+ assertFalse(mDatabaseCursor.isAfterLast());
+ }
+
+ public void testIsClosed() {
+ assertFalse(mDatabaseCursor.isClosed());
+ mDatabaseCursor.close();
+ assertTrue(mDatabaseCursor.isClosed());
+ }
+
+ public void testGetWindow() {
+ CursorWindow window = new CursorWindow(false);
+ assertEquals(0, window.getNumRows());
+ // fill window from position 0
+ mDatabaseCursor.fillWindow(0, window);
+
+ assertNotNull(mDatabaseCursor.getWindow());
+ assertEquals(mDatabaseCursor.getCount(), window.getNumRows());
+
+ while (mDatabaseCursor.moveToNext()) {
+ assertEquals(mDatabaseCursor.getInt(POSITION1),
+ window.getInt(mDatabaseCursor.getPosition(), POSITION1));
+ }
+ window.clear();
+ }
+
+ public void testGetWantsAllOnMoveCalls() {
+ assertFalse(mDatabaseCursor.getWantsAllOnMoveCalls());
+ }
+
+ public void testIsFieldUpdated() {
+ mTestAbstractCursor.moveToFirst();
+ assertFalse(mTestAbstractCursor.isFieldUpdated(0));
+ }
+
+ public void testGetUpdatedField() {
+ mTestAbstractCursor.moveToFirst();
+ assertNull(mTestAbstractCursor.getUpdatedField(0));
+ }
+
+ public void testGetExtras() {
+ assertSame(Bundle.EMPTY, mDatabaseCursor.getExtras());
+ }
+
+ public void testGetCount() {
+ assertEquals(DATA_COUNT, mDatabaseCursor.getCount());
+ }
+
+ public void testGetColumnNames() {
+ String[] names = mDatabaseCursor.getColumnNames();
+ assertEquals(COLUMN_NAMES1.length, names.length);
+
+ for (int i = 0; i < COLUMN_NAMES1.length; i++) {
+ assertEquals(COLUMN_NAMES1[i], names[i]);
+ }
+ }
+
+ public void testGetColumnName() {
+ assertEquals(COLUMN_NAMES1[0], mDatabaseCursor.getColumnName(0));
+ assertEquals(COLUMN_NAMES1[1], mDatabaseCursor.getColumnName(1));
+ }
+
+ public void testGetColumnIndexOrThrow() {
+ final String COLUMN_FAKE = "fake_name";
+ assertEquals(POSITION0, mDatabaseCursor.getColumnIndex(COLUMN_NAMES1[POSITION0]));
+ assertEquals(POSITION1, mDatabaseCursor.getColumnIndex(COLUMN_NAMES1[POSITION1]));
+ assertEquals(POSITION0, mDatabaseCursor.getColumnIndexOrThrow(COLUMN_NAMES1[POSITION0]));
+ assertEquals(POSITION1, mDatabaseCursor.getColumnIndexOrThrow(COLUMN_NAMES1[POSITION1]));
+
+ try {
+ mDatabaseCursor.getColumnIndexOrThrow(COLUMN_FAKE);
+ fail("IllegalArgumentException expected, but not thrown");
+ } catch (IllegalArgumentException expected) {
+ // expected
+ }
+ }
+
+ public void testGetColumnIndex() {
+ assertEquals(POSITION0, mDatabaseCursor.getColumnIndex(COLUMN_NAMES1[POSITION0]));
+ assertEquals(POSITION1, mDatabaseCursor.getColumnIndex(COLUMN_NAMES1[POSITION1]));
+ }
+
+ public void testGetColumnCount() {
+ assertEquals(COLUMN_NAMES1.length, mDatabaseCursor.getColumnCount());
+ }
+
+ public void testDeactivate() {
+ MockDataSetObserver mock = new MockDataSetObserver();
+ mDatabaseCursor.registerDataSetObserver(mock);
+ assertFalse(mock.hadCalledOnInvalid());
+ mDatabaseCursor.deactivate();
+ assertTrue(mock.hadCalledOnInvalid());
+ }
+
+ public void testCopyStringToBuffer() {
+ CharArrayBuffer ca = new CharArrayBuffer(1000);
+ mTestAbstractCursor.moveToFirst();
+ mTestAbstractCursor.copyStringToBuffer(0, ca);
+ CursorWindow window = new CursorWindow(false);
+ mTestAbstractCursor.fillWindow(0, window);
+
+ StringBuffer sb = new StringBuffer();
+ sb.append(window.getString(0, 0));
+ String str = mTestAbstractCursor.getString(0);
+ assertEquals(str.length(), ca.sizeCopied);
+ assertEquals(sb.toString(), new String(ca.data, 0, ca.sizeCopied));
+ }
+
+ public void testCheckPosition() {
+ // Test with position = -1.
+ try {
+ mTestAbstractCursor.checkPosition();
+ fail("copyStringToBuffer() should throws CursorIndexOutOfBoundsException here.");
+ } catch (CursorIndexOutOfBoundsException e) {
+ // expected
+ }
+
+ // Test with position = count.
+ assertTrue(mTestAbstractCursor.moveToPosition(mTestAbstractCursor.getCount() - 1));
+ mTestAbstractCursor.checkPosition();
+
+ try {
+ assertFalse(mTestAbstractCursor.moveToPosition(mTestAbstractCursor.getCount()));
+ assertEquals(mTestAbstractCursor.getCount(), mTestAbstractCursor.getPosition());
+ mTestAbstractCursor.checkPosition();
+ fail("copyStringToBuffer() should throws CursorIndexOutOfBoundsException here.");
+ } catch (CursorIndexOutOfBoundsException e) {
+ // expected
+ }
+ }
+
+ public void testSetExtras() {
+ Bundle b = new Bundle();
+ mTestAbstractCursor.setExtras(b);
+ assertSame(b, mTestAbstractCursor.getExtras());
+ }
+
+ @SuppressWarnings("unchecked")
+ private static ArrayList createTestList(int rows, int cols) {
+ ArrayList list = new ArrayList();
+ Random ran = new Random();
+
+ for (int i = 0; i < rows; i++) {
+ ArrayList col = new ArrayList();
+ list.add(col);
+
+ for (int j = 0; j < cols; j++) {
+ // generate random number
+ Integer r = ran.nextInt();
+ col.add(r);
+ }
+ }
+
+ return list;
+ }
+
+ private void setupDatabase() {
+ File dbDir = getInstrumentation().getTargetContext().getDir("tests",
+ Context.MODE_PRIVATE);
+ mDatabaseFile = new File(dbDir, "database_test.db");
+ if (mDatabaseFile.exists()) {
+ mDatabaseFile.delete();
+ }
+ mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
+ assertNotNull(mDatabaseFile);
+ mDatabase.execSQL("CREATE TABLE test1 (_id INTEGER PRIMARY KEY, number TEXT);");
+ generateData();
+ mDatabaseCursor = (AbstractCursor) mDatabase.query("test1", null, null, null, null, null,
+ null);
+ }
+
+ private void generateData() {
+ for ( int i = 0; i < DATA_COUNT; i++) {
+ mDatabase.execSQL("INSERT INTO test1 (number) VALUES ('" + i + "');");
+ }
+ }
+
+ private class TestAbstractCursor extends AbstractCursor {
+ private boolean mOnMoveReturnValue;
+ private int mOldPosition;
+ private int mNewPosition;
+ /** The accumulated number of rows this cursor has moved over. */
+ private int mRowsMovedSum;
+ private String[] mColumnNames;
+ private ArrayList
+ * @throws InterruptedException
+ */
+ @LargeTest
+ public void testReaderGetsOldVersionOfDataWhenWriterIsInXact() throws InterruptedException {
+ // redo setup to create WAL enabled database
+ mDatabase.close();
+ new File(mDatabase.getPath()).delete();
+ mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null, null);
+ boolean rslt = mDatabase.enableWriteAheadLogging();
+ assertTrue(rslt);
+ assertNotNull(mDatabase);
+
+ // create a new table and insert 5 records into it.
+ mDatabase.execSQL("CREATE TABLE t1 (i int, j int);");
+ mDatabase.beginTransaction();
+ for (int i = 0; i < 5; i++) {
+ mDatabase.execSQL("insert into t1 values(?,?);", new String[] {i+"", i+""});
+ }
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+
+ // make sure a reader can read the above data
+ ReaderQueryingData r1 = new ReaderQueryingData(5);
+ r1.start();
+ Thread.yield();
+ try {r1.join();} catch (Exception e) {}
+
+ WriterDoingSingleTransaction w = new WriterDoingSingleTransaction();
+ w.start();
+ w.join();
+ }
+
+ private class WriterDoingSingleTransaction extends Thread {
+ @Override public void run() {
+ // start a transaction
+ mDatabase.beginTransactionNonExclusive();
+ mDatabase.execSQL("insert into t1 values(?,?);", new String[] {"11", "11"});
+ assertTrue(mDatabase.isOpen());
+
+ // while the writer is in a transaction, start a reader and make sure it can still
+ // read 5 rows of data (= old data prior to the current transaction)
+ ReaderQueryingData r1 = new ReaderQueryingData(5);
+ r1.start();
+ try {r1.join();} catch (Exception e) {}
+
+ // now, have the writer do the select count(*)
+ // it should execute on the same connection as this transaction
+ // and count(*) should reflect the newly inserted row
+ Long l = DatabaseUtils.longForQuery(mDatabase, "select count(*) from t1", null);
+ assertEquals(6, l.intValue());
+
+ // end transaction
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+
+ // reader should now be able to read 6 rows = new data AFTER this transaction
+ r1 = new ReaderQueryingData(6);
+ r1.start();
+ try {r1.join();} catch (Exception e) {}
+ }
+ }
+
+ private class ReaderQueryingData extends Thread {
+ private int count;
+ /**
+ * constructor with a param to indicate the number of rows expected to be read
+ */
+ public ReaderQueryingData(int count) {
+ this.count = count;
+ }
+ @Override public void run() {
+ Long l = DatabaseUtils.longForQuery(mDatabase, "select count(*) from t1", null);
+ assertEquals(count, l.intValue());
+ }
+ }
+
+ public void testExceptionsFromEnableWriteAheadLogging() {
+ // attach a database
+ // redo setup to create WAL enabled database
+ mDatabase.close();
+ new File(mDatabase.getPath()).delete();
+ mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null, null);
+
+ // attach a database and call enableWriteAheadLogging - should not be allowed
+ mDatabase.execSQL("attach database ':memory:' as memoryDb");
+ assertFalse(mDatabase.isWriteAheadLoggingEnabled());
+ assertFalse(mDatabase.enableWriteAheadLogging());
+ assertFalse(mDatabase.isWriteAheadLoggingEnabled());
+
+ // enableWriteAheadLogging on memory database is not allowed
+ SQLiteDatabase db = SQLiteDatabase.create(null);
+ assertFalse(mDatabase.isWriteAheadLoggingEnabled());
+ assertFalse(db.enableWriteAheadLogging());
+ assertFalse(mDatabase.isWriteAheadLoggingEnabled());
+ db.close();
+ }
+
+ public void testEnableThenDisableWriteAheadLogging() {
+ // Enable WAL.
+ assertFalse(mDatabase.isWriteAheadLoggingEnabled());
+ assertTrue(mDatabase.enableWriteAheadLogging());
+ assertTrue(mDatabase.isWriteAheadLoggingEnabled());
+ assertTrue(DatabaseUtils.stringForQuery(mDatabase, "PRAGMA journal_mode", null)
+ .equalsIgnoreCase("WAL"));
+
+ // Enabling when already enabled should have no observable effect.
+ assertTrue(mDatabase.enableWriteAheadLogging());
+ assertTrue(mDatabase.isWriteAheadLoggingEnabled());
+ assertTrue(DatabaseUtils.stringForQuery(mDatabase, "PRAGMA journal_mode", null)
+ .equalsIgnoreCase("WAL"));
+
+ // Disabling when there are no connections should work.
+ mDatabase.disableWriteAheadLogging();
+ assertFalse(mDatabase.isWriteAheadLoggingEnabled());
+ }
+
+ public void testEnableThenDisableWriteAheadLoggingUsingOpenFlag() {
+ new File(mDatabase.getPath()).delete();
+ mDatabase = SQLiteDatabase.openDatabase(mDatabaseFile.getPath(), null,
+ SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING,
+ null);
+ assertTrue(mDatabase.isWriteAheadLoggingEnabled());
+ assertTrue(DatabaseUtils.stringForQuery(mDatabase, "PRAGMA journal_mode", null)
+ .equalsIgnoreCase("WAL"));
+
+ // Enabling when already enabled should have no observable effect.
+ assertTrue(mDatabase.enableWriteAheadLogging());
+ assertTrue(mDatabase.isWriteAheadLoggingEnabled());
+ assertTrue(DatabaseUtils.stringForQuery(mDatabase, "PRAGMA journal_mode", null)
+ .equalsIgnoreCase("WAL"));
+
+ // Disabling when there are no connections should work.
+ mDatabase.disableWriteAheadLogging();
+ assertFalse(mDatabase.isWriteAheadLoggingEnabled());
+ }
+
+ public void testEnableWriteAheadLoggingFromContextUsingModeFlag() {
+ // Without the MODE_ENABLE_WRITE_AHEAD_LOGGING flag, database opens without WAL.
+ getContext().deleteDatabase(DATABASE_FILE_NAME);
+
+ File f = getContext().getDatabasePath(DATABASE_FILE_NAME);
+ mDatabase = SQLiteDatabase.openOrCreateDatabase(f,null);
+ assertFalse(mDatabase.isWriteAheadLoggingEnabled());
+ mDatabase.close();
+
+ // // With the MODE_ENABLE_WRITE_AHEAD_LOGGING flag, database opens with WAL.
+ // getContext().deleteDatabase(DATABASE_FILE_NAME);
+ // mDatabase = getContext().openOrCreateDatabase(DATABASE_FILE_NAME,
+ // Context.MODE_PRIVATE | Context.MODE_ENABLE_WRITE_AHEAD_LOGGING, null);
+ // assertTrue(mDatabase.isWriteAheadLoggingEnabled());
+ // mDatabase.close();
+ }
+
+ public void testEnableWriteAheadLoggingShouldThrowIfTransactionInProgress() {
+ assertFalse(mDatabase.isWriteAheadLoggingEnabled());
+ String oldJournalMode = DatabaseUtils.stringForQuery(
+ mDatabase, "PRAGMA journal_mode", null);
+
+ // Begin transaction.
+ mDatabase.beginTransaction();
+
+ try {
+ // Attempt to enable WAL should fail.
+ mDatabase.enableWriteAheadLogging();
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException ex) {
+ // expected
+ }
+
+ assertFalse(mDatabase.isWriteAheadLoggingEnabled());
+ assertTrue(DatabaseUtils.stringForQuery(mDatabase, "PRAGMA journal_mode", null)
+ .equalsIgnoreCase(oldJournalMode));
+ }
+
+ public void testDisableWriteAheadLoggingShouldThrowIfTransactionInProgress() {
+ // Enable WAL.
+ assertFalse(mDatabase.isWriteAheadLoggingEnabled());
+ assertTrue(mDatabase.enableWriteAheadLogging());
+ assertTrue(mDatabase.isWriteAheadLoggingEnabled());
+
+ // Begin transaction.
+ mDatabase.beginTransaction();
+
+ try {
+ // Attempt to disable WAL should fail.
+ mDatabase.disableWriteAheadLogging();
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException ex) {
+ // expected
+ }
+
+ assertTrue(mDatabase.isWriteAheadLoggingEnabled());
+ assertTrue(DatabaseUtils.stringForQuery(mDatabase, "PRAGMA journal_mode", null)
+ .equalsIgnoreCase("WAL"));
+ }
+
+ public void testEnableAndDisableForeignKeys() {
+ // Initially off.
+ assertEquals(0, DatabaseUtils.longForQuery(mDatabase, "PRAGMA foreign_keys", null));
+
+ // Enable foreign keys.
+ mDatabase.setForeignKeyConstraintsEnabled(true);
+ assertEquals(1, DatabaseUtils.longForQuery(mDatabase, "PRAGMA foreign_keys", null));
+
+ // Disable foreign keys.
+ mDatabase.setForeignKeyConstraintsEnabled(false);
+ assertEquals(0, DatabaseUtils.longForQuery(mDatabase, "PRAGMA foreign_keys", null));
+
+ // Cannot configure foreign keys if there are transactions in progress.
+ mDatabase.beginTransaction();
+ try {
+ mDatabase.setForeignKeyConstraintsEnabled(true);
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException ex) {
+ // expected
+ }
+ assertEquals(0, DatabaseUtils.longForQuery(mDatabase, "PRAGMA foreign_keys", null));
+ mDatabase.endTransaction();
+
+ // Enable foreign keys should work again after transaction complete.
+ mDatabase.setForeignKeyConstraintsEnabled(true);
+ assertEquals(1, DatabaseUtils.longForQuery(mDatabase, "PRAGMA foreign_keys", null));
+ }
+}
ADDED sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteDiskIOExceptionTest.java
Index: sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteDiskIOExceptionTest.java
==================================================================
--- /dev/null
+++ sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteDiskIOExceptionTest.java
@@ -0,0 +1,28 @@
+/*
+ * 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 org.sqlite.database.sqlite_cts;
+
+import org.sqlite.database.sqlite.SQLiteDiskIOException;
+import android.test.AndroidTestCase;
+
+public class SQLiteDiskIOExceptionTest extends AndroidTestCase {
+ public void testConstructor() {
+ new SQLiteDiskIOException();
+
+ new SQLiteDiskIOException("error");
+ }
+}
ADDED sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteDoneExceptionTest.java
Index: sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteDoneExceptionTest.java
==================================================================
--- /dev/null
+++ sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteDoneExceptionTest.java
@@ -0,0 +1,28 @@
+/*
+ * 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 org.sqlite.database.sqlite_cts;
+
+import org.sqlite.database.sqlite.SQLiteDoneException;
+import android.test.AndroidTestCase;
+
+public class SQLiteDoneExceptionTest extends AndroidTestCase {
+ public void testConstructor() {
+ new SQLiteDoneException();
+
+ new SQLiteDoneException("error");
+ }
+}
ADDED sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteExceptionTest.java
Index: sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteExceptionTest.java
==================================================================
--- /dev/null
+++ sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteExceptionTest.java
@@ -0,0 +1,28 @@
+/*
+ * 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 org.sqlite.database.sqlite_cts;
+
+import org.sqlite.database.sqlite.SQLiteException;
+import android.test.AndroidTestCase;
+
+public class SQLiteExceptionTest extends AndroidTestCase {
+ public void testConstructor() {
+ new SQLiteException();
+
+ new SQLiteException("error");
+ }
+}
ADDED sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteFtsTest.java
Index: sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteFtsTest.java
==================================================================
--- /dev/null
+++ sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteFtsTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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 org.sqlite.database.sqlite_cts;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import org.sqlite.database.sqlite.SQLiteDatabase;
+import android.test.AndroidTestCase;
+
+import java.io.File;
+
+/**
+ * Tests to verify FTS3/4 SQLite support.
+ */
+public class SQLiteFtsTest extends AndroidTestCase {
+
+ private static final String TEST_TABLE = "cts_fts";
+
+ private static final String[] TEST_CONTENT = {
+ "Any sufficiently advanced TECHnology is indistinguishable from magic.",
+ "Those who would give up Essential Liberty to purchase a little Temporary Safety, deserve neither Liberty nor Safety.",
+ "It is poor civic hygiene to install technologies that could someday facilitate a police state.",
+ };
+
+ private SQLiteDatabase mDatabase;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ System.loadLibrary("sqliteX");
+ File f = mContext.getDatabasePath("CTS_FTS");
+ f.mkdirs();
+ if (f.exists()) { f.delete(); }
+ mDatabase = SQLiteDatabase.openOrCreateDatabase(f,null);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ try {
+ final String path = mDatabase.getPath();
+ mDatabase.close();
+ SQLiteDatabase.deleteDatabase(new File(path));
+ } finally {
+ super.tearDown();
+ }
+ }
+
+ public void testFts3Porter() throws Exception {
+ prepareFtsTable(TEST_TABLE, "fts3", "tokenize=porter");
+
+ // Porter should include stemmed words
+ final Cursor cursor = queryFtsTable(TEST_TABLE, "technology");
+ try {
+ assertEquals(2, cursor.getCount());
+ cursor.moveToPosition(0);
+ assertTrue(cursor.getString(0).contains(">TECHnology<"));
+ cursor.moveToPosition(1);
+ assertTrue(cursor.getString(0).contains(">technologies<"));
+ } finally {
+ cursor.close();
+ }
+ }
+
+ public void testFts3Simple() throws Exception {
+ prepareFtsTable(TEST_TABLE, "fts3", "tokenize=simple");
+
+ // Simple shouldn't include stemmed words
+ final Cursor cursor = queryFtsTable(TEST_TABLE, "technology");
+ try {
+ assertEquals(1, cursor.getCount());
+ cursor.moveToPosition(0);
+ assertTrue(cursor.getString(0).contains(">TECHnology<"));
+ } finally {
+ cursor.close();
+ }
+ }
+
+ public void testFts4Simple() throws Exception {
+ prepareFtsTable(TEST_TABLE, "fts4", "tokenize=simple");
+
+ // Simple shouldn't include stemmed words
+ final Cursor cursor = queryFtsTable(TEST_TABLE, "technology");
+ try {
+ assertEquals(1, cursor.getCount());
+ cursor.moveToPosition(0);
+ assertTrue(cursor.getString(0).contains(">TECHnology<"));
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private void prepareFtsTable(String table, String ftsType, String options)
+ throws Exception {
+ mDatabase.execSQL(
+ "CREATE VIRTUAL TABLE " + table + " USING " + ftsType
+ + "(content TEXT, " + options + ");");
+
+ final Resources res = getContext().getResources();
+ final ContentValues values = new ContentValues();
+ for (String content : TEST_CONTENT) {
+ values.clear();
+ values.put("content", content);
+ mDatabase.insert(table, null, values);
+ }
+ }
+
+ private Cursor queryFtsTable(String table, String match) {
+ return mDatabase.query(table, new String[] { "snippet(" + table + ")" },
+ "content MATCH ?", new String[] { match },
+ null, null, "rowid ASC");
+ }
+}
ADDED sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteFullExceptionTest.java
Index: sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteFullExceptionTest.java
==================================================================
--- /dev/null
+++ sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteFullExceptionTest.java
@@ -0,0 +1,28 @@
+/*
+ * 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 org.sqlite.database.sqlite_cts;
+
+import org.sqlite.database.sqlite.SQLiteFullException;
+import android.test.AndroidTestCase;
+
+public class SQLiteFullExceptionTest extends AndroidTestCase {
+ public void testConstructor() {
+ new SQLiteFullException();
+
+ new SQLiteFullException("error");
+ }
+}
ADDED sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteMisuseExceptionTest.java
Index: sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteMisuseExceptionTest.java
==================================================================
--- /dev/null
+++ sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteMisuseExceptionTest.java
@@ -0,0 +1,28 @@
+/*
+ * 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 org.sqlite.database.sqlite_cts;
+
+import org.sqlite.database.sqlite.SQLiteMisuseException;
+import android.test.AndroidTestCase;
+
+public class SQLiteMisuseExceptionTest extends AndroidTestCase {
+ public void testConstructor() {
+ new SQLiteMisuseException();
+
+ new SQLiteMisuseException("error");
+ }
+}
ADDED sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteOpenHelperTest.java
Index: sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteOpenHelperTest.java
==================================================================
--- /dev/null
+++ sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteOpenHelperTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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 org.sqlite.database.sqlite_cts;
+
+import android.content.Context;
+import android.database.Cursor;
+import org.sqlite.database.sqlite.SQLiteCursor;
+import org.sqlite.database.sqlite.SQLiteCursorDriver;
+import org.sqlite.database.sqlite.SQLiteDatabase;
+import org.sqlite.database.sqlite.SQLiteOpenHelper;
+import org.sqlite.database.sqlite.SQLiteQuery;
+import org.sqlite.database.sqlite.SQLiteDatabase.CursorFactory;
+import android.test.AndroidTestCase;
+
+import java.io.File;
+
+/**
+ * Test {@link SQLiteOpenHelper}.
+ */
+public class SQLiteOpenHelperTest extends AndroidTestCase {
+ private static final String TEST_DATABASE_NAME = "database_test.db";
+ static String DATABASE_PATH;
+ private static final int TEST_VERSION = 1;
+ private static final int TEST_ILLEGAL_VERSION = 0;
+ private MockOpenHelper mOpenHelper;
+ private SQLiteDatabase.CursorFactory mFactory = new SQLiteDatabase.CursorFactory() {
+ public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
+ String editTable, SQLiteQuery query) {
+ return new MockCursor(db, masterQuery, editTable, query);
+ }
+ };
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ System.loadLibrary("sqliteX");
+ DATABASE_PATH = mContext.getDatabasePath(TEST_DATABASE_NAME).toString();
+ mOpenHelper = getOpenHelper();
+ }
+
+ public void testConstructor() {
+ new MockOpenHelper(mContext, DATABASE_PATH, mFactory, TEST_VERSION);
+
+ // Test with illegal version number.
+ try {
+ new MockOpenHelper(mContext, DATABASE_PATH, mFactory, TEST_ILLEGAL_VERSION);
+ fail("Constructor of SQLiteOpenHelp should throws a IllegalArgumentException here.");
+ } catch (IllegalArgumentException e) {
+ }
+
+ // Test with null factory
+ new MockOpenHelper(mContext, DATABASE_PATH, null, TEST_VERSION);
+ }
+
+ public void testGetDatabase() {
+ SQLiteDatabase database = null;
+ assertFalse(mOpenHelper.hasCalledOnOpen());
+ // Test getReadableDatabase.
+ database = mOpenHelper.getReadableDatabase();
+ assertNotNull(database);
+ assertTrue(database.isOpen());
+ assertTrue(mOpenHelper.hasCalledOnOpen());
+
+ // Database has been opened, so onOpen can not be invoked.
+ mOpenHelper.resetStatus();
+ assertFalse(mOpenHelper.hasCalledOnOpen());
+ // Test getWritableDatabase.
+ SQLiteDatabase database2 = mOpenHelper.getWritableDatabase();
+ assertSame(database, database2);
+ assertTrue(database.isOpen());
+ assertFalse(mOpenHelper.hasCalledOnOpen());
+
+ mOpenHelper.close();
+ assertFalse(database.isOpen());
+
+ // After close(), onOpen() will be invoked by getWritableDatabase.
+ mOpenHelper.resetStatus();
+ assertFalse(mOpenHelper.hasCalledOnOpen());
+ SQLiteDatabase database3 = mOpenHelper.getWritableDatabase();
+ assertNotNull(database);
+ assertNotSame(database, database3);
+ assertTrue(mOpenHelper.hasCalledOnOpen());
+ assertTrue(database3.isOpen());
+ mOpenHelper.close();
+ assertFalse(database3.isOpen());
+ }
+
+ private MockOpenHelper getOpenHelper() {
+ return new MockOpenHelper(mContext, DATABASE_PATH, mFactory, TEST_VERSION);
+ }
+
+ private class MockOpenHelper extends SQLiteOpenHelper {
+ private boolean mHasCalledOnOpen = false;
+
+ public MockOpenHelper(Context context, String name, CursorFactory factory, int version) {
+ super(context, name, factory, version);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ }
+
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ mHasCalledOnOpen = true;
+ }
+
+ public boolean hasCalledOnOpen() {
+ return mHasCalledOnOpen;
+ }
+
+ public void resetStatus() {
+ mHasCalledOnOpen = false;
+ }
+ }
+
+ private class MockCursor extends SQLiteCursor {
+ public MockCursor(SQLiteDatabase db, SQLiteCursorDriver driver, String editTable,
+ SQLiteQuery query) {
+ super(db, driver, editTable, query);
+ }
+ }
+}
ADDED sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteProgramTest.java
Index: sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteProgramTest.java
==================================================================
--- /dev/null
+++ sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteProgramTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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 org.sqlite.database.sqlite_cts;
+
+
+import android.content.Context;
+import android.database.Cursor;
+import org.sqlite.database.sqlite.SQLiteDatabase;
+import org.sqlite.database.sqlite.SQLiteDoneException;
+import org.sqlite.database.sqlite.SQLiteException;
+import org.sqlite.database.sqlite.SQLiteQuery;
+import org.sqlite.database.sqlite.SQLiteStatement;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+
+import java.io.File;
+
+public class SQLiteProgramTest extends AndroidTestCase {
+ private static final String DATABASE_NAME = "database_test.db";
+
+ private SQLiteDatabase mDatabase;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ System.loadLibrary("sqliteX");
+ File f = mContext.getDatabasePath(DATABASE_NAME);
+ f.mkdirs();
+ if (f.exists()) { f.delete(); }
+ mDatabase = SQLiteDatabase.openOrCreateDatabase(f,null);
+ assertNotNull(mDatabase);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDatabase.close();
+ getContext().deleteDatabase(DATABASE_NAME);
+
+ super.tearDown();
+ }
+
+ public void testBind() {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " +
+ "num1 INTEGER, num2 INTEGER, image BLOB);");
+ mDatabase.execSQL("INSERT INTO test (text1, text2, num1, num2, image) " +
+ "VALUES ('Mike', 'Jack', 12, 30, 'abcdefg');");
+ mDatabase.execSQL("INSERT INTO test (text1, text2, num1, num2, image) " +
+ "VALUES ('test1', 'test2', 213, 589, '123456789');");
+ SQLiteStatement statement;
+
+ statement = mDatabase.compileStatement("SELECT num1 FROM test WHERE num2 = ?;");
+ statement.bindLong(1, 30);
+ assertEquals(12, statement.simpleQueryForLong());
+
+ // re-bind without clearing
+ statement.bindDouble(1, 589.0);
+ assertEquals(213, statement.simpleQueryForLong());
+ statement.close();
+
+ statement = mDatabase.compileStatement("SELECT text1 FROM test WHERE text2 = ?;");
+
+ statement.bindDouble(1, 589.0); // Wrong binding
+ try {
+ statement.simpleQueryForString();
+ fail("Should throw exception (no rows found)");
+ } catch (SQLiteDoneException expected) {
+ // expected
+ }
+ statement.bindString(1, "test2");
+ assertEquals("test1", statement.simpleQueryForString());
+ statement.clearBindings();
+ try {
+ statement.simpleQueryForString();
+ fail("Should throw exception (unbound value)");
+ } catch (SQLiteDoneException expected) {
+ // expected
+ }
+ statement.close();
+
+ Cursor cursor = null;
+ try {
+ cursor = mDatabase.query("test", new String[]{"text1"}, "where text1='a'",
+ new String[]{"foo"}, null, null, null);
+ fail("Should throw exception (no value to bind)");
+ } catch (SQLiteException expected) {
+ // expected
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ try {
+ cursor = mDatabase.query("test", new String[]{"text1"}, "where text1='a'",
+ new String[]{"foo", "bar"}, null, null, null);
+ fail("Should throw exception (index too large)");
+ } catch (SQLiteException expected) {
+ // expected
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ // test positive case
+ statement = mDatabase.compileStatement(
+ "SELECT text1 FROM test WHERE text2 = ? AND num2 = ?;");
+ statement.bindString(1, "Jack");
+ statement.bindLong(2, 30);
+ assertEquals("Mike", statement.simpleQueryForString());
+ statement.close();
+ }
+
+ public void testBindNull() {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " +
+ "num1 INTEGER, num2 INTEGER, image BLOB);");
+
+ SQLiteStatement statement = mDatabase.compileStatement("INSERT INTO test " +
+ "(text1,text2,num1,image) VALUES (?,?,?,?)");
+ statement.bindString(1, "string1");
+ statement.bindString(2, "string2");
+ statement.bindLong(3, 100);
+ statement.bindNull(4);
+ statement.execute();
+ statement.close();
+
+ final int COLUMN_TEXT1_INDEX = 0;
+ final int COLUMN_TEXT2_INDEX = 1;
+ final int COLUMN_NUM1_INDEX = 2;
+ final int COLUMN_IMAGE_INDEX = 3;
+
+ Cursor cursor = mDatabase.query("test", new String[] { "text1", "text2", "num1", "image" },
+ null, null, null, null, null);
+ assertNotNull(cursor);
+ assertEquals(1, cursor.getCount());
+ cursor.moveToFirst();
+ assertEquals("string1", cursor.getString(COLUMN_TEXT1_INDEX));
+ assertEquals("string2", cursor.getString(COLUMN_TEXT2_INDEX));
+ assertEquals(100, cursor.getInt(COLUMN_NUM1_INDEX));
+ assertNull(cursor.getString(COLUMN_IMAGE_INDEX));
+ cursor.close();
+ }
+
+ public void testBindBlob() {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " +
+ "num1 INTEGER, num2 INTEGER, image BLOB);");
+
+ SQLiteStatement statement = mDatabase.compileStatement("INSERT INTO test " +
+ "(text1,text2,num1,image) VALUES (?,?,?,?)");
+ statement.bindString(1, "string1");
+ statement.bindString(2, "string2");
+ statement.bindLong(3, 100);
+ byte[] blob = new byte[] { '1', '2', '3' };
+ statement.bindBlob(4, blob);
+ statement.execute();
+ statement.close();
+
+ final int COLUMN_TEXT1_INDEX = 0;
+ final int COLUMN_TEXT2_INDEX = 1;
+ final int COLUMN_NUM1_INDEX = 2;
+ final int COLUMN_IMAGE_INDEX = 3;
+
+ Cursor cursor = mDatabase.query("test", new String[] { "text1", "text2", "num1", "image" },
+ null, null, null, null, null);
+ assertNotNull(cursor);
+ assertEquals(1, cursor.getCount());
+ cursor.moveToFirst();
+ assertEquals("string1", cursor.getString(COLUMN_TEXT1_INDEX));
+ assertEquals("string2", cursor.getString(COLUMN_TEXT2_INDEX));
+ assertEquals(100, cursor.getInt(COLUMN_NUM1_INDEX));
+ byte[] value = cursor.getBlob(COLUMN_IMAGE_INDEX);
+ MoreAsserts.assertEquals(blob, value);
+ cursor.close();
+ }
+}
ADDED sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteQueryBuilderTest.java
Index: sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteQueryBuilderTest.java
==================================================================
--- /dev/null
+++ sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteQueryBuilderTest.java
@@ -0,0 +1,482 @@
+/*
+ * 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 org.sqlite.database.sqlite_cts;
+
+
+import android.content.Context;
+import android.database.Cursor;
+import org.sqlite.database.sqlite.SQLiteCursor;
+import org.sqlite.database.sqlite.SQLiteCursorDriver;
+import org.sqlite.database.sqlite.SQLiteDatabase;
+import org.sqlite.database.sqlite.SQLiteQuery;
+import org.sqlite.database.sqlite.SQLiteQueryBuilder;
+import android.os.CancellationSignal;
+import android.os.OperationCanceledException;
+import android.test.AndroidTestCase;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Semaphore;
+import java.io.File;
+
+public class SQLiteQueryBuilderTest extends AndroidTestCase {
+ private SQLiteDatabase mDatabase;
+ private final String TEST_TABLE_NAME = "test";
+ private final String EMPLOYEE_TABLE_NAME = "employee";
+ private static final String DATABASE_FILE = "database_test.db";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ System.loadLibrary("sqliteX");
+ File f = mContext.getDatabasePath(DATABASE_FILE);
+ f.mkdirs();
+ if (f.exists()) { f.delete(); }
+ mDatabase = SQLiteDatabase.openOrCreateDatabase(f,null);
+ assertNotNull(mDatabase);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDatabase.close();
+ getContext().deleteDatabase(DATABASE_FILE);
+ super.tearDown();
+ }
+
+ public void testConstructor() {
+ new SQLiteQueryBuilder();
+ }
+
+ public void testSetDistinct() {
+ String expected;
+ SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
+ sqliteQueryBuilder.setTables(TEST_TABLE_NAME);
+ sqliteQueryBuilder.setDistinct(false);
+ sqliteQueryBuilder.appendWhere("age=20");
+ String sql = sqliteQueryBuilder.buildQuery(new String[] { "age", "address" },
+ null, null, null, null, null, null);
+ assertEquals(TEST_TABLE_NAME, sqliteQueryBuilder.getTables());
+ expected = "SELECT age, address FROM " + TEST_TABLE_NAME + " WHERE (age=20)";
+ assertEquals(expected, sql);
+
+ sqliteQueryBuilder = new SQLiteQueryBuilder();
+ sqliteQueryBuilder.setTables(EMPLOYEE_TABLE_NAME);
+ sqliteQueryBuilder.setDistinct(true);
+ sqliteQueryBuilder.appendWhere("age>32");
+ sql = sqliteQueryBuilder.buildQuery(new String[] { "age", "address" },
+ null, null, null, null, null, null);
+ assertEquals(EMPLOYEE_TABLE_NAME, sqliteQueryBuilder.getTables());
+ expected = "SELECT DISTINCT age, address FROM " + EMPLOYEE_TABLE_NAME + " WHERE (age>32)";
+ assertEquals(expected, sql);
+
+ sqliteQueryBuilder = new SQLiteQueryBuilder();
+ sqliteQueryBuilder.setTables(EMPLOYEE_TABLE_NAME);
+ sqliteQueryBuilder.setDistinct(true);
+ sqliteQueryBuilder.appendWhereEscapeString("age>32");
+ sql = sqliteQueryBuilder.buildQuery(new String[] { "age", "address" },
+ null, null, null, null, null, null);
+ assertEquals(EMPLOYEE_TABLE_NAME, sqliteQueryBuilder.getTables());
+ expected = "SELECT DISTINCT age, address FROM " + EMPLOYEE_TABLE_NAME
+ + " WHERE ('age>32')";
+ assertEquals(expected, sql);
+ }
+
+ public void testSetProjectionMap() {
+ String expected;
+ Map projectMap = new HashMap();
+ projectMap.put("EmployeeName", "name");
+ projectMap.put("EmployeeAge", "age");
+ projectMap.put("EmployeeAddress", "address");
+ SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
+ sqliteQueryBuilder.setTables(TEST_TABLE_NAME);
+ sqliteQueryBuilder.setDistinct(false);
+ sqliteQueryBuilder.setProjectionMap(projectMap);
+ String sql = sqliteQueryBuilder.buildQuery(new String[] { "EmployeeName", "EmployeeAge" },
+ null, null, null, null, null, null);
+ expected = "SELECT name, age FROM " + TEST_TABLE_NAME;
+ assertEquals(expected, sql);
+
+ sql = sqliteQueryBuilder.buildQuery(null, // projectionIn is null
+ null, null, null, null, null, null);
+ assertTrue(sql.matches("SELECT (age|name|address), (age|name|address), (age|name|address) "
+ + "FROM " + TEST_TABLE_NAME));
+ assertTrue(sql.contains("age"));
+ assertTrue(sql.contains("name"));
+ assertTrue(sql.contains("address"));
+
+ sqliteQueryBuilder.setProjectionMap(null);
+ sql = sqliteQueryBuilder.buildQuery(new String[] { "name", "address" },
+ null, null, null, null, null, null);
+ assertTrue(sql.matches("SELECT (name|address), (name|address) "
+ + "FROM " + TEST_TABLE_NAME));
+ assertTrue(sql.contains("name"));
+ assertTrue(sql.contains("address"));
+ }
+
+ public void testSetCursorFactory() {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, " +
+ "name TEXT, age INTEGER, address TEXT);");
+ mDatabase.execSQL("INSERT INTO test (name, age, address) VALUES ('Mike', '20', 'LA');");
+ mDatabase.execSQL("INSERT INTO test (name, age, address) VALUES ('jack', '40', 'LA');");
+
+ SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
+ sqliteQueryBuilder.setTables(TEST_TABLE_NAME);
+ Cursor cursor = sqliteQueryBuilder.query(mDatabase, new String[] { "name", "age" },
+ null, null, null, null, null);
+ assertNotNull(cursor);
+ assertTrue(cursor instanceof SQLiteCursor);
+
+ SQLiteDatabase.CursorFactory factory = new SQLiteDatabase.CursorFactory() {
+ public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
+ String editTable, SQLiteQuery query) {
+ return new MockCursor(db, masterQuery, editTable, query);
+ }
+ };
+
+ sqliteQueryBuilder.setCursorFactory(factory);
+ cursor = sqliteQueryBuilder.query(mDatabase, new String[] { "name", "age" },
+ null, null, null, null, null);
+ assertNotNull(cursor);
+ assertTrue(cursor instanceof MockCursor);
+ }
+
+ private static class MockCursor extends SQLiteCursor {
+ public MockCursor(SQLiteDatabase db, SQLiteCursorDriver driver,
+ String editTable, SQLiteQuery query) {
+ super(db, driver, editTable, query);
+ }
+ }
+
+ public void testBuildQueryString() {
+ String expected;
+ final String[] DEFAULT_TEST_PROJECTION = new String [] { "name", "age", "sum(salary)" };
+ final String DEFAULT_TEST_WHERE = "age > 25";
+ final String DEFAULT_HAVING = "sum(salary) > 3000";
+
+ String sql = SQLiteQueryBuilder.buildQueryString(false, "Employee",
+ DEFAULT_TEST_PROJECTION,
+ DEFAULT_TEST_WHERE, "name", DEFAULT_HAVING, "name", "100");
+
+ expected = "SELECT name, age, sum(salary) FROM Employee WHERE " + DEFAULT_TEST_WHERE +
+ " GROUP BY name " +
+ "HAVING " + DEFAULT_HAVING + " " +
+ "ORDER BY name " +
+ "LIMIT 100";
+ assertEquals(expected, sql);
+ }
+
+ public void testBuildQuery() {
+ final String[] DEFAULT_TEST_PROJECTION = new String[] { "name", "sum(salary)" };
+ final String DEFAULT_TEST_WHERE = "age > 25";
+ final String DEFAULT_HAVING = "sum(salary) > 2000";
+
+ SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
+ sqliteQueryBuilder.setTables(TEST_TABLE_NAME);
+ sqliteQueryBuilder.setDistinct(false);
+ String sql = sqliteQueryBuilder.buildQuery(DEFAULT_TEST_PROJECTION,
+ DEFAULT_TEST_WHERE, null, "name", DEFAULT_HAVING, "name", "2");
+ String expected = "SELECT name, sum(salary) FROM " + TEST_TABLE_NAME
+ + " WHERE (" + DEFAULT_TEST_WHERE + ") " +
+ "GROUP BY name HAVING " + DEFAULT_HAVING + " ORDER BY name LIMIT 2";
+ assertEquals(expected, sql);
+ }
+
+ public void testAppendColumns() {
+ StringBuilder sb = new StringBuilder();
+ String[] columns = new String[] { "name", "age" };
+
+ assertEquals("", sb.toString());
+ SQLiteQueryBuilder.appendColumns(sb, columns);
+ assertEquals("name, age ", sb.toString());
+ }
+
+ public void testQuery() {
+ createEmployeeTable();
+
+ SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
+ sqliteQueryBuilder.setTables("Employee");
+ Cursor cursor = sqliteQueryBuilder.query(mDatabase,
+ new String[] { "name", "sum(salary)" }, null, null,
+ "name", "sum(salary)>1000", "name");
+ assertNotNull(cursor);
+ assertEquals(3, cursor.getCount());
+
+ final int COLUMN_NAME_INDEX = 0;
+ final int COLUMN_SALARY_INDEX = 1;
+ cursor.moveToFirst();
+ assertEquals("Jim", cursor.getString(COLUMN_NAME_INDEX));
+ assertEquals(4500, cursor.getInt(COLUMN_SALARY_INDEX));
+ cursor.moveToNext();
+ assertEquals("Mike", cursor.getString(COLUMN_NAME_INDEX));
+ assertEquals(4000, cursor.getInt(COLUMN_SALARY_INDEX));
+ cursor.moveToNext();
+ assertEquals("jack", cursor.getString(COLUMN_NAME_INDEX));
+ assertEquals(3500, cursor.getInt(COLUMN_SALARY_INDEX));
+
+ sqliteQueryBuilder = new SQLiteQueryBuilder();
+ sqliteQueryBuilder.setTables(EMPLOYEE_TABLE_NAME);
+ cursor = sqliteQueryBuilder.query(mDatabase,
+ new String[] { "name", "sum(salary)" }, null, null,
+ "name", "sum(salary)>1000", "name", "2" // limit is 2
+ );
+ assertNotNull(cursor);
+ assertEquals(2, cursor.getCount());
+ cursor.moveToFirst();
+ assertEquals("Jim", cursor.getString(COLUMN_NAME_INDEX));
+ assertEquals(4500, cursor.getInt(COLUMN_SALARY_INDEX));
+ cursor.moveToNext();
+ assertEquals("Mike", cursor.getString(COLUMN_NAME_INDEX));
+ assertEquals(4000, cursor.getInt(COLUMN_SALARY_INDEX));
+ }
+
+ public void testUnionQuery() {
+ String expected;
+ String[] innerProjection = new String[] {"name", "age", "location"};
+ SQLiteQueryBuilder employeeQueryBuilder = new SQLiteQueryBuilder();
+ SQLiteQueryBuilder peopleQueryBuilder = new SQLiteQueryBuilder();
+
+ employeeQueryBuilder.setTables("employee");
+ peopleQueryBuilder.setTables("people");
+
+ String employeeSubQuery = employeeQueryBuilder.buildUnionSubQuery(
+ "_id", innerProjection,
+ null, 2, "employee",
+ "age=25",
+ null, null, null);
+ String peopleSubQuery = peopleQueryBuilder.buildUnionSubQuery(
+ "_id", innerProjection,
+ null, 2, "people",
+ "location=LA",
+ null, null, null);
+ expected = "SELECT name, age, location FROM employee WHERE (age=25)";
+ assertEquals(expected, employeeSubQuery);
+ expected = "SELECT name, age, location FROM people WHERE (location=LA)";
+ assertEquals(expected, peopleSubQuery);
+
+ SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
+
+ unionQueryBuilder.setDistinct(true);
+
+ String unionQuery = unionQueryBuilder.buildUnionQuery(
+ new String[] { employeeSubQuery, peopleSubQuery }, null, null);
+ expected = "SELECT name, age, location FROM employee WHERE (age=25) " +
+ "UNION SELECT name, age, location FROM people WHERE (location=LA)";
+ assertEquals(expected, unionQuery);
+ }
+
+ public void testCancelableQuery_WhenNotCanceled_ReturnsResultSet() {
+ createEmployeeTable();
+
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
+ sqliteQueryBuilder.setTables("Employee");
+ Cursor cursor = sqliteQueryBuilder.query(mDatabase,
+ new String[] { "name", "sum(salary)" }, null, null,
+ "name", "sum(salary)>1000", "name", null, cancellationSignal);
+
+ assertEquals(3, cursor.getCount());
+ }
+
+ public void testCancelableQuery_WhenCanceledBeforeQuery_ThrowsImmediately() {
+ createEmployeeTable();
+
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
+ sqliteQueryBuilder.setTables("Employee");
+
+ cancellationSignal.cancel();
+ try {
+ sqliteQueryBuilder.query(mDatabase,
+ new String[] { "name", "sum(salary)" }, null, null,
+ "name", "sum(salary)>1000", "name", null, cancellationSignal);
+ fail("Expected OperationCanceledException");
+ } catch (OperationCanceledException ex) {
+ // expected
+ }
+ }
+
+ public void testCancelableQuery_WhenCanceledAfterQuery_ThrowsWhenExecuted() {
+ createEmployeeTable();
+
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
+ sqliteQueryBuilder.setTables("Employee");
+
+ Cursor cursor = sqliteQueryBuilder.query(mDatabase,
+ new String[] { "name", "sum(salary)" }, null, null,
+ "name", "sum(salary)>1000", "name", null, cancellationSignal);
+
+ cancellationSignal.cancel();
+ try {
+ cursor.getCount(); // force execution
+ fail("Expected OperationCanceledException");
+ } catch (OperationCanceledException ex) {
+ // expected
+ }
+ }
+
+ public void testCancelableQuery_WhenCanceledDueToContention_StopsWaitingAndThrows() {
+ createEmployeeTable();
+
+ for (int i = 0; i < 5; i++) {
+ final CancellationSignal cancellationSignal = new CancellationSignal();
+ final Semaphore barrier1 = new Semaphore(0);
+ final Semaphore barrier2 = new Semaphore(0);
+ Thread contentionThread = new Thread() {
+ @Override
+ public void run() {
+ mDatabase.beginTransaction(); // acquire the only available connection
+ barrier1.release(); // release query to start running
+ try {
+ barrier2.acquire(); // wait for test to end
+ } catch (InterruptedException e) {
+ }
+ mDatabase.endTransaction(); // release the connection
+ }
+ };
+ Thread cancellationThread = new Thread() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(300);
+ } catch (InterruptedException ex) {
+ }
+ cancellationSignal.cancel();
+ }
+ };
+ try {
+ SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
+ sqliteQueryBuilder.setTables("Employee");
+
+ contentionThread.start();
+ cancellationThread.start();
+
+ try {
+ barrier1.acquire(); // wait for contention thread to start transaction
+ } catch (InterruptedException e) {
+ }
+
+ final long startTime = System.nanoTime();
+ try {
+ Cursor cursor = sqliteQueryBuilder.query(mDatabase,
+ new String[] { "name", "sum(salary)" }, null, null,
+ "name", "sum(salary)>1000", "name", null, cancellationSignal);
+ cursor.getCount(); // force execution
+ fail("Expected OperationCanceledException");
+ } catch (OperationCanceledException ex) {
+ // expected
+ }
+
+ // We want to confirm that the query really was blocked trying to acquire a
+ // connection for a certain amount of time before it was freed by cancel.
+ final long waitTime = System.nanoTime() - startTime;
+ if (waitTime > 150 * 1000000L) {
+ return; // success!
+ }
+ } finally {
+ barrier1.release();
+ barrier2.release();
+ try {
+ contentionThread.join();
+ cancellationThread.join();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ // Occasionally we might miss the timing deadline due to factors in the
+ // environment, but if after several trials we still couldn't demonstrate
+ // that the query was blocked, then the test must be broken.
+ fail("Could not prove that the query actually blocked before cancel() was called.");
+ }
+
+ public void testCancelableQuery_WhenCanceledDuringLongRunningQuery_CancelsQueryAndThrows() {
+ // Populate a table with a bunch of integers.
+ mDatabase.execSQL("CREATE TABLE x (v INTEGER);");
+ for (int i = 0; i < 100; i++) {
+ mDatabase.execSQL("INSERT INTO x VALUES (?)", new Object[] { i });
+ }
+
+ for (int i = 0; i < 5; i++) {
+ final CancellationSignal cancellationSignal = new CancellationSignal();
+ Thread cancellationThread = new Thread() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(300);
+ } catch (InterruptedException ex) {
+ }
+ cancellationSignal.cancel();
+ }
+ };
+ try {
+ // Build an unsatisfiable 5-way cross-product query over 100 values but
+ // produces no output. This should force SQLite to loop for a long time
+ // as it tests 10^10 combinations.
+ SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
+ sqliteQueryBuilder.setTables("x AS a, x AS b, x AS c, x AS d, x AS e");
+
+ cancellationThread.start();
+
+ final long startTime = System.nanoTime();
+ try {
+ Cursor cursor = sqliteQueryBuilder.query(mDatabase, null,
+ "a.v + b.v + c.v + d.v + e.v > 1000000",
+ null, null, null, null, null, cancellationSignal);
+ cursor.getCount(); // force execution
+ fail("Expected OperationCanceledException");
+ } catch (OperationCanceledException ex) {
+ // expected
+ }
+
+ // We want to confirm that the query really was running and then got
+ // canceled midway.
+ final long waitTime = System.nanoTime() - startTime;
+ if (waitTime > 150 * 1000000L && waitTime < 600 * 1000000L) {
+ return; // success!
+ }
+ } finally {
+ try {
+ cancellationThread.join();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ // Occasionally we might miss the timing deadline due to factors in the
+ // environment, but if after several trials we still couldn't demonstrate
+ // that the query was canceled, then the test must be broken.
+ fail("Could not prove that the query actually canceled midway during execution.");
+ }
+
+ private void createEmployeeTable() {
+ mDatabase.execSQL("CREATE TABLE employee (_id INTEGER PRIMARY KEY, " +
+ "name TEXT, month INTEGER, salary INTEGER);");
+ mDatabase.execSQL("INSERT INTO employee (name, month, salary) " +
+ "VALUES ('Mike', '1', '1000');");
+ mDatabase.execSQL("INSERT INTO employee (name, month, salary) " +
+ "VALUES ('Mike', '2', '3000');");
+ mDatabase.execSQL("INSERT INTO employee (name, month, salary) " +
+ "VALUES ('jack', '1', '2000');");
+ mDatabase.execSQL("INSERT INTO employee (name, month, salary) " +
+ "VALUES ('jack', '3', '1500');");
+ mDatabase.execSQL("INSERT INTO employee (name, month, salary) " +
+ "VALUES ('Jim', '1', '1000');");
+ mDatabase.execSQL("INSERT INTO employee (name, month, salary) " +
+ "VALUES ('Jim', '3', '3500');");
+ }
+}
ADDED sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteQueryTest.java
Index: sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteQueryTest.java
==================================================================
--- /dev/null
+++ sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteQueryTest.java
@@ -0,0 +1,25 @@
+/*
+ * 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 org.sqlite.database.sqlite_cts;
+
+import junit.framework.TestCase;
+
+public class SQLiteQueryTest extends TestCase {
+ public void testMethods() {
+ // cannot obtain an instance of SQLiteQuery
+ }
+}
ADDED sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteStatementTest.java
Index: sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteStatementTest.java
==================================================================
--- /dev/null
+++ sqlite3/src/androidTest/java/org/sqlite/database/sqlite_cts/SQLiteStatementTest.java
@@ -0,0 +1,354 @@
+/*
+ * 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 org.sqlite.database.sqlite_cts;
+
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import org.sqlite.database.DatabaseUtils;
+import org.sqlite.database.SQLException;
+import org.sqlite.database.sqlite.SQLiteDatabase;
+import org.sqlite.database.sqlite.SQLiteDoneException;
+import org.sqlite.database.sqlite.SQLiteStatement;
+import android.os.ParcelFileDescriptor;
+import android.support.test.filters.Suppress;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.File;
+
+public class SQLiteStatementTest extends AndroidTestCase {
+ private static final String STRING1 = "this is a test";
+ private static final String STRING2 = "another test";
+
+ private static final byte[][] BLOBS = new byte [][] {
+ parseBlob("86FADCF1A820666AEBD0789F47932151A2EF734269E8AC4E39630AB60519DFD8"),
+ new byte[1],
+ null,
+ parseBlob("00"),
+ parseBlob("FF"),
+ parseBlob("D7B500FECF25F7A4D83BF823D3858690790F2526013DE6CAE9A69170E2A1E47238"),
+ };
+
+ private static final String DATABASE_NAME = "database_test.db";
+
+ private static final int CURRENT_DATABASE_VERSION = 42;
+ private SQLiteDatabase mDatabase;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ System.loadLibrary("sqliteX");
+ getContext().deleteDatabase(DATABASE_NAME);
+ File f = mContext.getDatabasePath(DATABASE_NAME);
+ f.mkdirs();
+ if (f.exists()) { f.delete(); }
+ mDatabase = SQLiteDatabase.openOrCreateDatabase(f, null);
+ assertNotNull(mDatabase);
+ mDatabase.setVersion(CURRENT_DATABASE_VERSION);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDatabase.close();
+ getContext().deleteDatabase(DATABASE_NAME);
+ super.tearDown();
+ }
+
+ private void populateDefaultTable() {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
+ }
+
+ private void populateBlobTable() {
+ mDatabase.execSQL("CREATE TABLE blob_test (_id INTEGER PRIMARY KEY, data BLOB)");
+ for (int i = 0; i < BLOBS.length; i++) {
+ ContentValues values = new ContentValues();
+ values.put("_id", i);
+ values.put("data", BLOBS[i]);
+ mDatabase.insert("blob_test", null, values);
+ }
+ }
+
+ public void testExecute() {
+ mDatabase.disableWriteAheadLogging();
+ populateDefaultTable();
+
+ assertEquals(0, DatabaseUtils.longForQuery(mDatabase, "select count(*) from test", null));
+
+ // test update
+ // insert 2 rows and then update them.
+ SQLiteStatement statement1 = mDatabase.compileStatement(
+ "INSERT INTO test (data) VALUES ('" + STRING2 + "')");
+ assertEquals(1, statement1.executeInsert());
+ assertEquals(2, statement1.executeInsert());
+ SQLiteStatement statement2 =
+ mDatabase.compileStatement("UPDATE test set data = 'a' WHERE _id > 0");
+ assertEquals(2, statement2.executeUpdateDelete());
+ statement2.close();
+ // should still have 2 rows in the table
+ assertEquals(2, DatabaseUtils.longForQuery(mDatabase, "select count(*) from test", null));
+
+ // test delete
+ // insert 2 more rows and delete 3 of them
+ assertEquals(3, statement1.executeInsert());
+ assertEquals(4, statement1.executeInsert());
+ statement1.close();
+ statement2 = mDatabase.compileStatement("DELETE from test WHERE _id < 4");
+ assertEquals(3, statement2.executeUpdateDelete());
+ statement2.close();
+ // should still have 1 row1 in the table
+ assertEquals(1, DatabaseUtils.longForQuery(mDatabase, "select count(*) from test", null));
+
+ // if the SQL statement is something that causes rows of data to
+ // be returned, executeUpdateDelete() (and execute()) throw an exception.
+ statement2 = mDatabase.compileStatement("SELECT count(*) FROM test");
+ try {
+ statement2.executeUpdateDelete();
+ fail("exception expected");
+ } catch (SQLException e) {
+ // expected
+ } finally {
+ statement2.close();
+ }
+ }
+
+ public void testExecuteInsert() {
+ populateDefaultTable();
+
+ Cursor c = mDatabase.query("test", null, null, null, null, null, null);
+ assertEquals(0, c.getCount());
+
+ // test insert
+ SQLiteStatement statement = mDatabase.compileStatement(
+ "INSERT INTO test (data) VALUES ('" + STRING2 + "')");
+ assertEquals(1, statement.executeInsert());
+ statement.close();
+
+ // try to insert another row with the same id. last inserted rowid should be -1
+ statement = mDatabase.compileStatement("insert or ignore into test values(1, 1);");
+ assertEquals(-1, statement.executeInsert());
+ statement.close();
+
+ c = mDatabase.query("test", null, null, null, null, null, null);
+ assertEquals(1, c.getCount());
+
+ c.moveToFirst();
+ assertEquals(STRING2, c.getString(c.getColumnIndex("data")));
+ c.close();
+
+ // if the sql statement is something that causes rows of data to
+ // be returned, executeInsert() throws an exception
+ statement = mDatabase.compileStatement(
+ "SELECT * FROM test WHERE data=\"" + STRING2 + "\"");
+ try {
+ statement.executeInsert();
+ fail("exception expected");
+ } catch (SQLException e) {
+ // expected
+ } finally {
+ statement.close();
+
+ }
+ }
+
+ public void testSimpleQueryForLong() {
+ mDatabase.execSQL("CREATE TABLE test (num INTEGER NOT NULL, str TEXT NOT NULL);");
+ mDatabase.execSQL("INSERT INTO test VALUES (1234, 'hello');");
+ SQLiteStatement statement =
+ mDatabase.compileStatement("SELECT num FROM test WHERE str = ?");
+
+ // test query long
+ statement.bindString(1, "hello");
+ long value = statement.simpleQueryForLong();
+ assertEquals(1234, value);
+
+ // test query returns zero rows
+ statement.bindString(1, "world");
+
+ try {
+ statement.simpleQueryForLong();
+ fail("There should be a SQLiteDoneException thrown out.");
+ } catch (SQLiteDoneException e) {
+ // expected.
+ }
+
+ statement.close();
+ }
+
+ public void testSimpleQueryForString() {
+ mDatabase.execSQL("CREATE TABLE test (num INTEGER NOT NULL, str TEXT NOT NULL);");
+ mDatabase.execSQL("INSERT INTO test VALUES (1234, 'hello');");
+ SQLiteStatement statement =
+ mDatabase.compileStatement("SELECT str FROM test WHERE num = ?");
+
+ // test query String
+ statement.bindLong(1, 1234);
+ String value = statement.simpleQueryForString();
+ assertEquals("hello", value);
+
+ // test query returns zero rows
+ statement.bindLong(1, 5678);
+
+ try {
+ statement.simpleQueryForString();
+ fail("There should be a SQLiteDoneException thrown out.");
+ } catch (SQLiteDoneException e) {
+ // expected.
+ }
+
+ statement.close();
+ }
+
+ @Suppress
+ public void testSimpleQueryForBlobFileDescriptorSuccessNormal() throws IOException {
+ doTestSimpleQueryForBlobFileDescriptorSuccess(0);
+ }
+
+ @Suppress
+ public void testSimpleQueryForBlobFileDescriptorSuccessEmpty() throws IOException {
+ doTestSimpleQueryForBlobFileDescriptorSuccess(1);
+ }
+
+ @Suppress
+ public void testSimpleQueryForBlobFileDescriptorSuccessNull() {
+ populateBlobTable();
+
+ String sql = "SELECT data FROM blob_test WHERE _id = " + 2;
+ SQLiteStatement stm = mDatabase.compileStatement(sql);
+ assertNull(stm.simpleQueryForBlobFileDescriptor());
+ }
+
+ @Suppress
+ public void testSimpleQueryForBlobFileDescriptorSuccess00() throws IOException {
+ doTestSimpleQueryForBlobFileDescriptorSuccess(3);
+ }
+
+ @Suppress
+ public void testSimpleQueryForBlobFileDescriptorSuccessFF() throws IOException {
+ doTestSimpleQueryForBlobFileDescriptorSuccess(4);
+ }
+
+ @Suppress
+ public void testSimpleQueryForBlobFileDescriptorSuccessEmbeddedNul() throws IOException {
+ doTestSimpleQueryForBlobFileDescriptorSuccess(5);
+ }
+
+ @Suppress
+ private void doTestSimpleQueryForBlobFileDescriptorSuccess(int i) throws IOException {
+ populateBlobTable();
+
+ String sql = "SELECT data FROM blob_test WHERE _id = " + i;
+ SQLiteStatement stm = mDatabase.compileStatement(sql);
+ ParcelFileDescriptor fd = stm.simpleQueryForBlobFileDescriptor();
+ assertFileDescriptorContent(BLOBS[i], fd);
+ }
+
+ @Suppress
+ public void testSimpleQueryForBlobFileDescriptorSuccessParam() throws IOException {
+ populateBlobTable();
+
+ String sql = "SELECT data FROM blob_test WHERE _id = ?";
+ SQLiteStatement stm = mDatabase.compileStatement(sql);
+ stm.bindLong(1, 0);
+ ParcelFileDescriptor fd = stm.simpleQueryForBlobFileDescriptor();
+ assertFileDescriptorContent(BLOBS[0], fd);
+ }
+
+ public void testGetBlobFailureNoParam() throws Exception {
+ populateBlobTable();
+
+ String sql = "SELECT data FROM blob_test WHERE _id = 100";
+ SQLiteStatement stm = mDatabase.compileStatement(sql);
+ ParcelFileDescriptor fd = null;
+ SQLiteDoneException expectedException = null;
+ try {
+ fd = stm.simpleQueryForBlobFileDescriptor();
+ } catch (SQLiteDoneException ex) {
+ expectedException = ex;
+ } finally {
+ if (fd != null) {
+ fd.close();
+ fd = null;
+ }
+ }
+ assertNotNull("Should have thrown SQLiteDoneException", expectedException);
+ }
+
+ public void testGetBlobFailureParam() throws Exception {
+ populateBlobTable();
+
+ String sql = "SELECT data FROM blob_test WHERE _id = ?";
+ SQLiteStatement stm = mDatabase.compileStatement(sql);
+ stm.bindLong(1, 100);
+ ParcelFileDescriptor fd = null;
+ SQLiteDoneException expectedException = null;
+ try {
+ fd = stm.simpleQueryForBlobFileDescriptor();
+ } catch (SQLiteDoneException ex) {
+ expectedException = ex;
+ } finally {
+ if (fd != null) {
+ fd.close();
+ fd = null;
+ }
+ }
+ assertNotNull("Should have thrown SQLiteDoneException", expectedException);
+ }
+
+ /*
+ * Convert string of hex digits to byte array.
+ * Results are undefined for poorly formed string.
+ *
+ * @param src hex string
+ */
+ private static byte[] parseBlob(String src) {
+ int len = src.length();
+ byte[] result = new byte[len / 2];
+
+ for (int i = 0; i < len/2; i++) {
+ int val;
+ char c1 = src.charAt(i*2);
+ char c2 = src.charAt(i*2+1);
+ int val1 = Character.digit(c1, 16);
+ int val2 = Character.digit(c2, 16);
+ val = (val1 << 4) | val2;
+ result[i] = (byte)val;
+ }
+ return result;
+ }
+
+ private static void assertFileDescriptorContent(byte[] expected, ParcelFileDescriptor fd)
+ throws IOException {
+ assertInputStreamContent(expected, new ParcelFileDescriptor.AutoCloseInputStream(fd));
+ }
+
+ private static void assertInputStreamContent(byte[] expected, InputStream is)
+ throws IOException {
+ try {
+ byte[] observed = new byte[expected.length];
+ int count = is.read(observed);
+ assertEquals(expected.length, count);
+ assertEquals(-1, is.read());
+ MoreAsserts.assertEquals(expected, observed);
+ } finally {
+ is.close();
+ }
+ }
+}
ADDED sqlite3/src/main/java/org/sqlite/database/DatabaseUtils.java
Index: sqlite3/src/main/java/org/sqlite/database/DatabaseUtils.java
==================================================================
--- /dev/null
+++ sqlite3/src/main/java/org/sqlite/database/DatabaseUtils.java
@@ -0,0 +1,1461 @@
+/*
+ * 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 org.sqlite.database;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import org.sqlite.database.sqlite.SQLiteAbortException;
+import org.sqlite.database.sqlite.SQLiteConstraintException;
+import org.sqlite.database.sqlite.SQLiteDatabase;
+import org.sqlite.database.sqlite.SQLiteDatabaseCorruptException;
+import org.sqlite.database.sqlite.SQLiteDiskIOException;
+import org.sqlite.database.sqlite.SQLiteException;
+import org.sqlite.database.sqlite.SQLiteFullException;
+import org.sqlite.database.sqlite.SQLiteProgram;
+import org.sqlite.database.sqlite.SQLiteStatement;
+
+import android.database.CursorWindow;
+import android.os.OperationCanceledException;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+import android.util.Log;
+
+import android.database.Cursor;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintStream;
+import java.text.Collator;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Static utility methods for dealing with databases and {@link Cursor}s.
+ */
+public class DatabaseUtils {
+ private static final String TAG = "DatabaseUtils";
+
+ private static final boolean DEBUG = false;
+
+ /** One of the values returned by {@link #getSqlStatementType(String)}. */
+ public static final int STATEMENT_SELECT = 1;
+ /** One of the values returned by {@link #getSqlStatementType(String)}. */
+ public static final int STATEMENT_UPDATE = 2;
+ /** One of the values returned by {@link #getSqlStatementType(String)}. */
+ public static final int STATEMENT_ATTACH = 3;
+ /** One of the values returned by {@link #getSqlStatementType(String)}. */
+ public static final int STATEMENT_BEGIN = 4;
+ /** One of the values returned by {@link #getSqlStatementType(String)}. */
+ public static final int STATEMENT_COMMIT = 5;
+ /** One of the values returned by {@link #getSqlStatementType(String)}. */
+ public static final int STATEMENT_ABORT = 6;
+ /** One of the values returned by {@link #getSqlStatementType(String)}. */
+ public static final int STATEMENT_PRAGMA = 7;
+ /** One of the values returned by {@link #getSqlStatementType(String)}. */
+ public static final int STATEMENT_DDL = 8;
+ /** One of the values returned by {@link #getSqlStatementType(String)}. */
+ public static final int STATEMENT_UNPREPARED = 9;
+ /** One of the values returned by {@link #getSqlStatementType(String)}. */
+ public static final int STATEMENT_OTHER = 99;
+
+ /**
+ * Special function for writing an exception result at the header of
+ * a parcel, to be used when returning an exception from a transaction.
+ * exception will be re-thrown by the function in another process
+ * @param reply Parcel to write to
+ * @param e The Exception to be written.
+ * @see Parcel#writeNoException
+ * @see Parcel#writeException
+ */
+ public static final void writeExceptionToParcel(Parcel reply, Exception e) {
+ int code = 0;
+ boolean logException = true;
+ if (e instanceof FileNotFoundException) {
+ code = 1;
+ logException = false;
+ } else if (e instanceof IllegalArgumentException) {
+ code = 2;
+ } else if (e instanceof UnsupportedOperationException) {
+ code = 3;
+ } else if (e instanceof SQLiteAbortException) {
+ code = 4;
+ } else if (e instanceof SQLiteConstraintException) {
+ code = 5;
+ } else if (e instanceof SQLiteDatabaseCorruptException) {
+ code = 6;
+ } else if (e instanceof SQLiteFullException) {
+ code = 7;
+ } else if (e instanceof SQLiteDiskIOException) {
+ code = 8;
+ } else if (e instanceof SQLiteException) {
+ code = 9;
+ } else if (e instanceof OperationApplicationException) {
+ code = 10;
+ } else if (e instanceof OperationCanceledException) {
+ code = 11;
+ logException = false;
+ } else {
+ reply.writeException(e);
+ Log.e(TAG, "Writing exception to parcel", e);
+ return;
+ }
+ reply.writeInt(code);
+ reply.writeString(e.getMessage());
+
+ if (logException) {
+ Log.e(TAG, "Writing exception to parcel", e);
+ }
+ }
+
+ /**
+ * Special function for reading an exception result from the header of
+ * a parcel, to be used after receiving the result of a transaction. This
+ * will throw the exception for you if it had been written to the Parcel,
+ * otherwise return and let you read the normal result data from the Parcel.
+ * @param reply Parcel to read from
+ * @see Parcel#writeNoException
+ * @see Parcel#readException
+ */
+// public static final void readExceptionFromParcel(Parcel reply) {
+// int code = reply.readExceptionCode();
+// if (code == 0) return;
+// String msg = reply.readString();
+// DatabaseUtils.readExceptionFromParcel(reply, msg, code);
+// }
+//
+// public static void readExceptionWithFileNotFoundExceptionFromParcel(
+// Parcel reply) throws FileNotFoundException {
+// int code = reply.readExceptionCode();
+// if (code == 0) return;
+// String msg = reply.readString();
+// if (code == 1) {
+// throw new FileNotFoundException(msg);
+// } else {
+// DatabaseUtils.readExceptionFromParcel(reply, msg, code);
+// }
+// }
+//
+// public static void readExceptionWithOperationApplicationExceptionFromParcel(
+// Parcel reply) throws OperationApplicationException {
+// int code = reply.readExceptionCode();
+// if (code == 0) return;
+// String msg = reply.readString();
+// if (code == 10) {
+// throw new OperationApplicationException(msg);
+// } else {
+// DatabaseUtils.readExceptionFromParcel(reply, msg, code);
+// }
+// }
+
+ private static final void readExceptionFromParcel(Parcel reply, String msg, int code) {
+ switch (code) {
+ case 2:
+ throw new IllegalArgumentException(msg);
+ case 3:
+ throw new UnsupportedOperationException(msg);
+ case 4:
+ throw new SQLiteAbortException(msg);
+ case 5:
+ throw new SQLiteConstraintException(msg);
+ case 6:
+ throw new SQLiteDatabaseCorruptException(msg);
+ case 7:
+ throw new SQLiteFullException(msg);
+ case 8:
+ throw new SQLiteDiskIOException(msg);
+ case 9:
+ throw new SQLiteException(msg);
+ case 11:
+ throw new OperationCanceledException(msg);
+ default:
+ reply.readException(code, msg);
+ }
+ }
+
+ /**
+ * Binds the given Object to the given SQLiteProgram using the proper
+ * typing. For example, bind numbers as longs/doubles, and everything else
+ * as a string by call toString() on it.
+ *
+ * @param prog the program to bind the object to
+ * @param index the 1-based index to bind at
+ * @param value the value to bind
+ */
+ public static void bindObjectToProgram(SQLiteProgram prog, int index,
+ Object value) {
+ if (value == null) {
+ prog.bindNull(index);
+ } else if (value instanceof Double || value instanceof Float) {
+ prog.bindDouble(index, ((Number)value).doubleValue());
+ } else if (value instanceof Number) {
+ prog.bindLong(index, ((Number)value).longValue());
+ } else if (value instanceof Boolean) {
+ Boolean bool = (Boolean)value;
+ if (bool) {
+ prog.bindLong(index, 1);
+ } else {
+ prog.bindLong(index, 0);
+ }
+ } else if (value instanceof byte[]){
+ prog.bindBlob(index, (byte[]) value);
+ } else {
+ prog.bindString(index, value.toString());
+ }
+ }
+
+ /**
+ * Returns data type of the given object's value.
+ *
+ * Returned values are
+ *
+ *
{@link Cursor#FIELD_TYPE_NULL}
+ *
{@link Cursor#FIELD_TYPE_INTEGER}
+ *
{@link Cursor#FIELD_TYPE_FLOAT}
+ *
{@link Cursor#FIELD_TYPE_STRING}
+ *
{@link Cursor#FIELD_TYPE_BLOB}
+ *
+ *
+ *
+ * @param obj the object whose value type is to be returned
+ * @return object value type
+ * @hide
+ */
+ public static int getTypeOfObject(Object obj) {
+ if (obj == null) {
+ return Cursor.FIELD_TYPE_NULL;
+ } else if (obj instanceof byte[]) {
+ return Cursor.FIELD_TYPE_BLOB;
+ } else if (obj instanceof Float || obj instanceof Double) {
+ return Cursor.FIELD_TYPE_FLOAT;
+ } else if (obj instanceof Long || obj instanceof Integer
+ || obj instanceof Short || obj instanceof Byte) {
+ return Cursor.FIELD_TYPE_INTEGER;
+ } else {
+ return Cursor.FIELD_TYPE_STRING;
+ }
+ }
+
+ /**
+ * Fills the specified cursor window by iterating over the contents of the cursor.
+ * The window is filled until the cursor is exhausted or the window runs out
+ * of space.
+ *
+ * The original position of the cursor is left unchanged by this operation.
+ *
+ * @param cursor The cursor that contains the data to put in the window.
+ * @param position The start position for filling the window.
+ * @param window The window to fill.
+ * @hide
+ */
+ public static void cursorFillWindow(final Cursor cursor,
+ int position, final CursorWindow window) {
+ if (position < 0 || position >= cursor.getCount()) {
+ return;
+ }
+ final int oldPos = cursor.getPosition();
+ final int numColumns = cursor.getColumnCount();
+ window.clear();
+ window.setStartPosition(position);
+ window.setNumColumns(numColumns);
+ if (cursor.moveToPosition(position)) {
+ rowloop: do {
+ if (!window.allocRow()) {
+ break;
+ }
+ for (int i = 0; i < numColumns; i++) {
+ final int type = cursor.getType(i);
+ final boolean success;
+ switch (type) {
+ case Cursor.FIELD_TYPE_NULL:
+ success = window.putNull(position, i);
+ break;
+
+ case Cursor.FIELD_TYPE_INTEGER:
+ success = window.putLong(cursor.getLong(i), position, i);
+ break;
+
+ case Cursor.FIELD_TYPE_FLOAT:
+ success = window.putDouble(cursor.getDouble(i), position, i);
+ break;
+
+ case Cursor.FIELD_TYPE_BLOB: {
+ final byte[] value = cursor.getBlob(i);
+ success = value != null ? window.putBlob(value, position, i)
+ : window.putNull(position, i);
+ break;
+ }
+
+ default: // assume value is convertible to String
+ case Cursor.FIELD_TYPE_STRING: {
+ final String value = cursor.getString(i);
+ success = value != null ? window.putString(value, position, i)
+ : window.putNull(position, i);
+ break;
+ }
+ }
+ if (!success) {
+ window.freeLastRow();
+ break rowloop;
+ }
+ }
+ position += 1;
+ } while (cursor.moveToNext());
+ }
+ cursor.moveToPosition(oldPos);
+ }
+
+ /**
+ * Appends an SQL string to the given StringBuilder, including the opening
+ * and closing single quotes. Any single quotes internal to sqlString will
+ * be escaped.
+ *
+ * This method is deprecated because we want to encourage everyone
+ * to use the "?" binding form. However, when implementing a
+ * ContentProvider, one may want to add WHERE clauses that were
+ * not provided by the caller. Since "?" is a positional form,
+ * using it in this case could break the caller because the
+ * indexes would be shifted to accomodate the ContentProvider's
+ * internal bindings. In that case, it may be necessary to
+ * construct a WHERE clause manually. This method is useful for
+ * those cases.
+ *
+ * @param sb the StringBuilder that the SQL string will be appended to
+ * @param sqlString the raw string to be appended, which may contain single
+ * quotes
+ */
+ public static void appendEscapedSQLString(StringBuilder sb, String sqlString) {
+ sb.append('\'');
+ if (sqlString.indexOf('\'') != -1) {
+ int length = sqlString.length();
+ for (int i = 0; i < length; i++) {
+ char c = sqlString.charAt(i);
+ if (c == '\'') {
+ sb.append('\'');
+ }
+ sb.append(c);
+ }
+ } else
+ sb.append(sqlString);
+ sb.append('\'');
+ }
+
+ /**
+ * SQL-escape a string.
+ */
+ public static String sqlEscapeString(String value) {
+ StringBuilder escaper = new StringBuilder();
+
+ DatabaseUtils.appendEscapedSQLString(escaper, value);
+
+ return escaper.toString();
+ }
+
+ /**
+ * Appends an Object to an SQL string with the proper escaping, etc.
+ */
+ public static final void appendValueToSql(StringBuilder sql, Object value) {
+ if (value == null) {
+ sql.append("NULL");
+ } else if (value instanceof Boolean) {
+ Boolean bool = (Boolean)value;
+ if (bool) {
+ sql.append('1');
+ } else {
+ sql.append('0');
+ }
+ } else {
+ appendEscapedSQLString(sql, value.toString());
+ }
+ }
+
+ /**
+ * Concatenates two SQL WHERE clauses, handling empty or null values.
+ */
+ public static String concatenateWhere(String a, String b) {
+ if (TextUtils.isEmpty(a)) {
+ return b;
+ }
+ if (TextUtils.isEmpty(b)) {
+ return a;
+ }
+
+ return "(" + a + ") AND (" + b + ")";
+ }
+
+ /**
+ * return the collation key
+ * @param name
+ * @return the collation key
+ */
+ public static String getCollationKey(String name) {
+ byte [] arr = getCollationKeyInBytes(name);
+ try {
+ return new String(arr, 0, getKeyLen(arr), "ISO8859_1");
+ } catch (Exception ex) {
+ return "";
+ }
+ }
+
+ /**
+ * return the collation key in hex format
+ * @param name
+ * @return the collation key in hex format
+ */
+ public static String getHexCollationKey(String name) {
+ byte[] arr = getCollationKeyInBytes(name);
+ char[] keys = encodeHex(arr);
+ return new String(keys, 0, getKeyLen(arr) * 2);
+ }
+
+
+ /**
+ * Used building output as Hex
+ */
+ private static final char[] DIGITS = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+ };
+
+ private static char[] encodeHex(byte[] input) {
+ int l = input.length;
+ char[] out = new char[l << 1];
+
+ // two characters form the hex value.
+ for (int i = 0, j = 0; i < l; i++) {
+ out[j++] = DIGITS[(0xF0 & input[i]) >>> 4 ];
+ out[j++] = DIGITS[ 0x0F & input[i] ];
+ }
+
+ return out;
+ }
+
+ private static int getKeyLen(byte[] arr) {
+ if (arr[arr.length - 1] != 0) {
+ return arr.length;
+ } else {
+ // remove zero "termination"
+ return arr.length-1;
+ }
+ }
+
+ private static byte[] getCollationKeyInBytes(String name) {
+ if (mColl == null) {
+ mColl = Collator.getInstance();
+ mColl.setStrength(Collator.PRIMARY);
+ }
+ return mColl.getCollationKey(name).toByteArray();
+ }
+
+ private static Collator mColl = null;
+ /**
+ * Prints the contents of a Cursor to System.out. The position is restored
+ * after printing.
+ *
+ * @param cursor the cursor to print
+ */
+ public static void dumpCursor(Cursor cursor) {
+ dumpCursor(cursor, System.out);
+ }
+
+ /**
+ * Prints the contents of a Cursor to a PrintSteam. The position is restored
+ * after printing.
+ *
+ * @param cursor the cursor to print
+ * @param stream the stream to print to
+ */
+ public static void dumpCursor(Cursor cursor, PrintStream stream) {
+ stream.println(">>>>> Dumping cursor " + cursor);
+ if (cursor != null) {
+ int startPos = cursor.getPosition();
+
+ cursor.moveToPosition(-1);
+ while (cursor.moveToNext()) {
+ dumpCurrentRow(cursor, stream);
+ }
+ cursor.moveToPosition(startPos);
+ }
+ stream.println("<<<<<");
+ }
+
+ /**
+ * Prints the contents of a Cursor to a StringBuilder. The position
+ * is restored after printing.
+ *
+ * @param cursor the cursor to print
+ * @param sb the StringBuilder to print to
+ */
+ public static void dumpCursor(Cursor cursor, StringBuilder sb) {
+ sb.append(">>>>> Dumping cursor " + cursor + "\n");
+ if (cursor != null) {
+ int startPos = cursor.getPosition();
+
+ cursor.moveToPosition(-1);
+ while (cursor.moveToNext()) {
+ dumpCurrentRow(cursor, sb);
+ }
+ cursor.moveToPosition(startPos);
+ }
+ sb.append("<<<<<\n");
+ }
+
+ /**
+ * Prints the contents of a Cursor to a String. The position is restored
+ * after printing.
+ *
+ * @param cursor the cursor to print
+ * @return a String that contains the dumped cursor
+ */
+ public static String dumpCursorToString(Cursor cursor) {
+ StringBuilder sb = new StringBuilder();
+ dumpCursor(cursor, sb);
+ return sb.toString();
+ }
+
+ /**
+ * Prints the contents of a Cursor's current row to System.out.
+ *
+ * @param cursor the cursor to print from
+ */
+ public static void dumpCurrentRow(Cursor cursor) {
+ dumpCurrentRow(cursor, System.out);
+ }
+
+ /**
+ * Prints the contents of a Cursor's current row to a PrintSteam.
+ *
+ * @param cursor the cursor to print
+ * @param stream the stream to print to
+ */
+ public static void dumpCurrentRow(Cursor cursor, PrintStream stream) {
+ String[] cols = cursor.getColumnNames();
+ stream.println("" + cursor.getPosition() + " {");
+ int length = cols.length;
+ for (int i = 0; i< length; i++) {
+ String value;
+ try {
+ value = cursor.getString(i);
+ } catch (SQLiteException e) {
+ // assume that if the getString threw this exception then the column is not
+ // representable by a string, e.g. it is a BLOB.
+ value = "";
+ }
+ stream.println(" " + cols[i] + '=' + value);
+ }
+ stream.println("}");
+ }
+
+ /**
+ * Prints the contents of a Cursor's current row to a StringBuilder.
+ *
+ * @param cursor the cursor to print
+ * @param sb the StringBuilder to print to
+ */
+ public static void dumpCurrentRow(Cursor cursor, StringBuilder sb) {
+ String[] cols = cursor.getColumnNames();
+ sb.append("" + cursor.getPosition() + " {\n");
+ int length = cols.length;
+ for (int i = 0; i < length; i++) {
+ String value;
+ try {
+ value = cursor.getString(i);
+ } catch (SQLiteException e) {
+ // assume that if the getString threw this exception then the column is not
+ // representable by a string, e.g. it is a BLOB.
+ value = "";
+ }
+ sb.append(" " + cols[i] + '=' + value + "\n");
+ }
+ sb.append("}\n");
+ }
+
+ /**
+ * Dump the contents of a Cursor's current row to a String.
+ *
+ * @param cursor the cursor to print
+ * @return a String that contains the dumped cursor row
+ */
+ public static String dumpCurrentRowToString(Cursor cursor) {
+ StringBuilder sb = new StringBuilder();
+ dumpCurrentRow(cursor, sb);
+ return sb.toString();
+ }
+
+ /**
+ * Reads a String out of a field in a Cursor and writes it to a Map.
+ *
+ * @param cursor The cursor to read from
+ * @param field The TEXT field to read
+ * @param values The {@link ContentValues} to put the value into, with the field as the key
+ */
+ public static void cursorStringToContentValues(Cursor cursor, String field,
+ ContentValues values) {
+ cursorStringToContentValues(cursor, field, values, field);
+ }
+
+ /**
+ * Reads a String out of a field in a Cursor and writes it to an InsertHelper.
+ *
+ * @param cursor The cursor to read from
+ * @param field The TEXT field to read
+ * @param inserter The InsertHelper to bind into
+ * @param index the index of the bind entry in the InsertHelper
+ */
+ public static void cursorStringToInsertHelper(Cursor cursor, String field,
+ InsertHelper inserter, int index) {
+ inserter.bind(index, cursor.getString(cursor.getColumnIndexOrThrow(field)));
+ }
+
+ /**
+ * Reads a String out of a field in a Cursor and writes it to a Map.
+ *
+ * @param cursor The cursor to read from
+ * @param field The TEXT field to read
+ * @param values The {@link ContentValues} to put the value into, with the field as the key
+ * @param key The key to store the value with in the map
+ */
+ public static void cursorStringToContentValues(Cursor cursor, String field,
+ ContentValues values, String key) {
+ values.put(key, cursor.getString(cursor.getColumnIndexOrThrow(field)));
+ }
+
+ /**
+ * Reads an Integer out of a field in a Cursor and writes it to a Map.
+ *
+ * @param cursor The cursor to read from
+ * @param field The INTEGER field to read
+ * @param values The {@link ContentValues} to put the value into, with the field as the key
+ */
+ public static void cursorIntToContentValues(Cursor cursor, String field, ContentValues values) {
+ cursorIntToContentValues(cursor, field, values, field);
+ }
+
+ /**
+ * Reads a Integer out of a field in a Cursor and writes it to a Map.
+ *
+ * @param cursor The cursor to read from
+ * @param field The INTEGER field to read
+ * @param values The {@link ContentValues} to put the value into, with the field as the key
+ * @param key The key to store the value with in the map
+ */
+ public static void cursorIntToContentValues(Cursor cursor, String field, ContentValues values,
+ String key) {
+ int colIndex = cursor.getColumnIndex(field);
+ if (!cursor.isNull(colIndex)) {
+ values.put(key, cursor.getInt(colIndex));
+ } else {
+ values.put(key, (Integer) null);
+ }
+ }
+
+ /**
+ * Reads a Long out of a field in a Cursor and writes it to a Map.
+ *
+ * @param cursor The cursor to read from
+ * @param field The INTEGER field to read
+ * @param values The {@link ContentValues} to put the value into, with the field as the key
+ */
+ public static void cursorLongToContentValues(Cursor cursor, String field, ContentValues values)
+ {
+ cursorLongToContentValues(cursor, field, values, field);
+ }
+
+ /**
+ * Reads a Long out of a field in a Cursor and writes it to a Map.
+ *
+ * @param cursor The cursor to read from
+ * @param field The INTEGER field to read
+ * @param values The {@link ContentValues} to put the value into
+ * @param key The key to store the value with in the map
+ */
+ public static void cursorLongToContentValues(Cursor cursor, String field, ContentValues values,
+ String key) {
+ int colIndex = cursor.getColumnIndex(field);
+ if (!cursor.isNull(colIndex)) {
+ Long value = Long.valueOf(cursor.getLong(colIndex));
+ values.put(key, value);
+ } else {
+ values.put(key, (Long) null);
+ }
+ }
+
+ /**
+ * Reads a Double out of a field in a Cursor and writes it to a Map.
+ *
+ * @param cursor The cursor to read from
+ * @param field The REAL field to read
+ * @param values The {@link ContentValues} to put the value into
+ */
+ public static void cursorDoubleToCursorValues(Cursor cursor, String field, ContentValues values)
+ {
+ cursorDoubleToContentValues(cursor, field, values, field);
+ }
+
+ /**
+ * Reads a Double out of a field in a Cursor and writes it to a Map.
+ *
+ * @param cursor The cursor to read from
+ * @param field The REAL field to read
+ * @param values The {@link ContentValues} to put the value into
+ * @param key The key to store the value with in the map
+ */
+ public static void cursorDoubleToContentValues(Cursor cursor, String field,
+ ContentValues values, String key) {
+ int colIndex = cursor.getColumnIndex(field);
+ if (!cursor.isNull(colIndex)) {
+ values.put(key, cursor.getDouble(colIndex));
+ } else {
+ values.put(key, (Double) null);
+ }
+ }
+
+ /**
+ * Read the entire contents of a cursor row and store them in a ContentValues.
+ *
+ * @param cursor the cursor to read from.
+ * @param values the {@link ContentValues} to put the row into.
+ */
+ public static void cursorRowToContentValues(Cursor cursor, ContentValues values) {
+ String[] columns = cursor.getColumnNames();
+ int length = columns.length;
+ for (int i = 0; i < length; i++) {
+ if (cursor.getType(i) == Cursor.FIELD_TYPE_BLOB) {
+ values.put(columns[i], cursor.getBlob(i));
+ } else {
+ values.put(columns[i], cursor.getString(i));
+ }
+ }
+ }
+
+ /**
+ * Picks a start position for {@link Cursor#fillWindow} such that the
+ * window will contain the requested row and a useful range of rows
+ * around it.
+ *
+ * When the data set is too large to fit in a cursor window, seeking the
+ * cursor can become a very expensive operation since we have to run the
+ * query again when we move outside the bounds of the current window.
+ *
+ * We try to choose a start position for the cursor window such that
+ * 1/3 of the window's capacity is used to hold rows before the requested
+ * position and 2/3 of the window's capacity is used to hold rows after the
+ * requested position.
+ *
+ * @param cursorPosition The row index of the row we want to get.
+ * @param cursorWindowCapacity The estimated number of rows that can fit in
+ * a cursor window, or 0 if unknown.
+ * @return The recommended start position, always less than or equal to
+ * the requested row.
+ * @hide
+ */
+ public static int cursorPickFillWindowStartPosition(
+ int cursorPosition, int cursorWindowCapacity) {
+ return Math.max(cursorPosition - cursorWindowCapacity / 3, 0);
+ }
+
+ /**
+ * Query the table for the number of rows in the table.
+ * @param db the database the table is in
+ * @param table the name of the table to query
+ * @return the number of rows in the table
+ */
+ public static long queryNumEntries(SQLiteDatabase db, String table) {
+ return queryNumEntries(db, table, null, null);
+ }
+
+ /**
+ * Query the table for the number of rows in the table.
+ * @param db the database the table is in
+ * @param table the name of the table to query
+ * @param selection A filter declaring which rows to return,
+ * formatted as an SQL WHERE clause (excluding the WHERE itself).
+ * Passing null will count all rows for the given table
+ * @return the number of rows in the table filtered by the selection
+ */
+ public static long queryNumEntries(SQLiteDatabase db, String table, String selection) {
+ return queryNumEntries(db, table, selection, null);
+ }
+
+ /**
+ * Query the table for the number of rows in the table.
+ * @param db the database the table is in
+ * @param table the name of the table to query
+ * @param selection A filter declaring which rows to return,
+ * formatted as an SQL WHERE clause (excluding the WHERE itself).
+ * Passing null will count all rows for the given table
+ * @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.
+ * @return the number of rows in the table filtered by the selection
+ */
+ public static long queryNumEntries(SQLiteDatabase db, String table, String selection,
+ String[] selectionArgs) {
+ String s = (!TextUtils.isEmpty(selection)) ? " where " + selection : "";
+ return longForQuery(db, "select count(*) from " + table + s,
+ selectionArgs);
+ }
+
+ /**
+ * Query the table to check whether a table is empty or not
+ * @param db the database the table is in
+ * @param table the name of the table to query
+ * @return True if the table is empty
+ * @hide
+ */
+ public static boolean queryIsEmpty(SQLiteDatabase db, String table) {
+ long isEmpty = longForQuery(db, "select exists(select 1 from " + table + ")", null);
+ return isEmpty == 0;
+ }
+
+ /**
+ * Utility method to run the query on the db and return the value in the
+ * first column of the first row.
+ */
+ public static long longForQuery(SQLiteDatabase db, String query, String[] selectionArgs) {
+ SQLiteStatement prog = db.compileStatement(query);
+ try {
+ return longForQuery(prog, selectionArgs);
+ } finally {
+ prog.close();
+ }
+ }
+
+ /**
+ * Utility method to run the pre-compiled query and return the value in the
+ * first column of the first row.
+ */
+ public static long longForQuery(SQLiteStatement prog, String[] selectionArgs) {
+ prog.bindAllArgsAsStrings(selectionArgs);
+ return prog.simpleQueryForLong();
+ }
+
+ /**
+ * Utility method to run the query on the db and return the value in the
+ * first column of the first row.
+ */
+ public static String stringForQuery(SQLiteDatabase db, String query, String[] selectionArgs) {
+ SQLiteStatement prog = db.compileStatement(query);
+ try {
+ return stringForQuery(prog, selectionArgs);
+ } finally {
+ prog.close();
+ }
+ }
+
+ /**
+ * Utility method to run the pre-compiled query and return the value in the
+ * first column of the first row.
+ */
+ public static String stringForQuery(SQLiteStatement prog, String[] selectionArgs) {
+ prog.bindAllArgsAsStrings(selectionArgs);
+ return prog.simpleQueryForString();
+ }
+
+ /**
+ * Utility method to run the query on the db and return the blob value in the
+ * first column of the first row.
+ *
+ * @return A read-only file descriptor for a copy of the blob value.
+ */
+ public static ParcelFileDescriptor blobFileDescriptorForQuery(SQLiteDatabase db,
+ String query, String[] selectionArgs) {
+ SQLiteStatement prog = db.compileStatement(query);
+ try {
+ return blobFileDescriptorForQuery(prog, selectionArgs);
+ } finally {
+ prog.close();
+ }
+ }
+
+ /**
+ * Utility method to run the pre-compiled query and return the blob value in the
+ * first column of the first row.
+ *
+ * @return A read-only file descriptor for a copy of the blob value.
+ */
+ public static ParcelFileDescriptor blobFileDescriptorForQuery(SQLiteStatement prog,
+ String[] selectionArgs) {
+ prog.bindAllArgsAsStrings(selectionArgs);
+ return prog.simpleQueryForBlobFileDescriptor();
+ }
+
+ /**
+ * Reads a String out of a column in a Cursor and writes it to a ContentValues.
+ * Adds nothing to the ContentValues if the column isn't present or if its value is null.
+ *
+ * @param cursor The cursor to read from
+ * @param column The column to read
+ * @param values The {@link ContentValues} to put the value into
+ */
+ public static void cursorStringToContentValuesIfPresent(Cursor cursor, ContentValues values,
+ String column) {
+ final int index = cursor.getColumnIndex(column);
+ if (index != -1 && !cursor.isNull(index)) {
+ values.put(column, cursor.getString(index));
+ }
+ }
+
+ /**
+ * Reads a Long out of a column in a Cursor and writes it to a ContentValues.
+ * Adds nothing to the ContentValues if the column isn't present or if its value is null.
+ *
+ * @param cursor The cursor to read from
+ * @param column The column to read
+ * @param values The {@link ContentValues} to put the value into
+ */
+ public static void cursorLongToContentValuesIfPresent(Cursor cursor, ContentValues values,
+ String column) {
+ final int index = cursor.getColumnIndex(column);
+ if (index != -1 && !cursor.isNull(index)) {
+ values.put(column, cursor.getLong(index));
+ }
+ }
+
+ /**
+ * Reads a Short out of a column in a Cursor and writes it to a ContentValues.
+ * Adds nothing to the ContentValues if the column isn't present or if its value is null.
+ *
+ * @param cursor The cursor to read from
+ * @param column The column to read
+ * @param values The {@link ContentValues} to put the value into
+ */
+ public static void cursorShortToContentValuesIfPresent(Cursor cursor, ContentValues values,
+ String column) {
+ final int index = cursor.getColumnIndex(column);
+ if (index != -1 && !cursor.isNull(index)) {
+ values.put(column, cursor.getShort(index));
+ }
+ }
+
+ /**
+ * Reads a Integer out of a column in a Cursor and writes it to a ContentValues.
+ * Adds nothing to the ContentValues if the column isn't present or if its value is null.
+ *
+ * @param cursor The cursor to read from
+ * @param column The column to read
+ * @param values The {@link ContentValues} to put the value into
+ */
+ public static void cursorIntToContentValuesIfPresent(Cursor cursor, ContentValues values,
+ String column) {
+ final int index = cursor.getColumnIndex(column);
+ if (index != -1 && !cursor.isNull(index)) {
+ values.put(column, cursor.getInt(index));
+ }
+ }
+
+ /**
+ * Reads a Float out of a column in a Cursor and writes it to a ContentValues.
+ * Adds nothing to the ContentValues if the column isn't present or if its value is null.
+ *
+ * @param cursor The cursor to read from
+ * @param column The column to read
+ * @param values The {@link ContentValues} to put the value into
+ */
+ public static void cursorFloatToContentValuesIfPresent(Cursor cursor, ContentValues values,
+ String column) {
+ final int index = cursor.getColumnIndex(column);
+ if (index != -1 && !cursor.isNull(index)) {
+ values.put(column, cursor.getFloat(index));
+ }
+ }
+
+ /**
+ * Reads a Double out of a column in a Cursor and writes it to a ContentValues.
+ * Adds nothing to the ContentValues if the column isn't present or if its value is null.
+ *
+ * @param cursor The cursor to read from
+ * @param column The column to read
+ * @param values The {@link ContentValues} to put the value into
+ */
+ public static void cursorDoubleToContentValuesIfPresent(Cursor cursor, ContentValues values,
+ String column) {
+ final int index = cursor.getColumnIndex(column);
+ if (index != -1 && !cursor.isNull(index)) {
+ values.put(column, cursor.getDouble(index));
+ }
+ }
+
+ /**
+ * This class allows users to do multiple inserts into a table using
+ * the same statement.
+ *
+ * This class is not thread-safe.
+ *
+ *
+ * @deprecated Use {@link SQLiteStatement} instead.
+ */
+ @Deprecated
+ public static class InsertHelper {
+ private final SQLiteDatabase mDb;
+ private final String mTableName;
+ private HashMap mColumns;
+ private String mInsertSQL = null;
+ private SQLiteStatement mInsertStatement = null;
+ private SQLiteStatement mReplaceStatement = null;
+ private SQLiteStatement mPreparedStatement = null;
+
+ /**
+ * {@hide}
+ *
+ * These are the columns returned by sqlite's "PRAGMA
+ * table_info(...)" command that we depend on.
+ */
+ public static final int TABLE_INFO_PRAGMA_COLUMNNAME_INDEX = 1;
+
+ /**
+ * This field was accidentally exposed in earlier versions of the platform
+ * so we can hide it but we can't remove it.
+ *
+ * @hide
+ */
+ public static final int TABLE_INFO_PRAGMA_DEFAULT_INDEX = 4;
+
+ /**
+ * @param db the SQLiteDatabase to insert into
+ * @param tableName the name of the table to insert into
+ */
+ public InsertHelper(SQLiteDatabase db, String tableName) {
+ mDb = db;
+ mTableName = tableName;
+ }
+
+ private void buildSQL() throws SQLException {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("INSERT INTO ");
+ sb.append(mTableName);
+ sb.append(" (");
+
+ StringBuilder sbv = new StringBuilder(128);
+ sbv.append("VALUES (");
+
+ int i = 1;
+ Cursor cur = null;
+ try {
+ cur = mDb.rawQuery("PRAGMA table_info(" + mTableName + ")", null);
+ mColumns = new HashMap(cur.getCount());
+ while (cur.moveToNext()) {
+ String columnName = cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX);
+ String defaultValue = cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX);
+
+ mColumns.put(columnName, i);
+ sb.append("'");
+ sb.append(columnName);
+ sb.append("'");
+
+ if (defaultValue == null) {
+ sbv.append("?");
+ } else {
+ sbv.append("COALESCE(?, ");
+ sbv.append(defaultValue);
+ sbv.append(")");
+ }
+
+ sb.append(i == cur.getCount() ? ") " : ", ");
+ sbv.append(i == cur.getCount() ? ");" : ", ");
+ ++i;
+ }
+ } finally {
+ if (cur != null) cur.close();
+ }
+
+ sb.append(sbv);
+
+ mInsertSQL = sb.toString();
+ if (DEBUG) Log.v(TAG, "insert statement is " + mInsertSQL);
+ }
+
+ private SQLiteStatement getStatement(boolean allowReplace) throws SQLException {
+ if (allowReplace) {
+ if (mReplaceStatement == null) {
+ if (mInsertSQL == null) buildSQL();
+ // chop "INSERT" off the front and prepend "INSERT OR REPLACE" instead.
+ String replaceSQL = "INSERT OR REPLACE" + mInsertSQL.substring(6);
+ mReplaceStatement = mDb.compileStatement(replaceSQL);
+ }
+ return mReplaceStatement;
+ } else {
+ if (mInsertStatement == null) {
+ if (mInsertSQL == null) buildSQL();
+ mInsertStatement = mDb.compileStatement(mInsertSQL);
+ }
+ return mInsertStatement;
+ }
+ }
+
+ /**
+ * Performs an insert, adding a new row with the given values.
+ *
+ * @param values the set of values with which to populate the
+ * new row
+ * @param allowReplace if true, the statement does "INSERT OR
+ * REPLACE" instead of "INSERT", silently deleting any
+ * previously existing rows that would cause a conflict
+ *
+ * @return the row ID of the newly inserted row, or -1 if an
+ * error occurred
+ */
+ private long insertInternal(ContentValues values, boolean allowReplace) {
+ // Start a transaction even though we don't really need one.
+ // This is to help maintain compatibility with applications that
+ // access InsertHelper from multiple threads even though they never should have.
+ // The original code used to lock the InsertHelper itself which was prone
+ // to deadlocks. Starting a transaction achieves the same mutual exclusion
+ // effect as grabbing a lock but without the potential for deadlocks.
+ mDb.beginTransactionNonExclusive();
+ try {
+ SQLiteStatement stmt = getStatement(allowReplace);
+ stmt.clearBindings();
+ if (DEBUG) Log.v(TAG, "--- inserting in table " + mTableName);
+ for (Map.Entry e: values.valueSet()) {
+ final String key = e.getKey();
+ int i = getColumnIndex(key);
+ DatabaseUtils.bindObjectToProgram(stmt, i, e.getValue());
+ if (DEBUG) {
+ Log.v(TAG, "binding " + e.getValue() + " to column " +
+ i + " (" + key + ")");
+ }
+ }
+ long result = stmt.executeInsert();
+ mDb.setTransactionSuccessful();
+ return result;
+ } catch (SQLException e) {
+ Log.e(TAG, "Error inserting " + values + " into table " + mTableName, e);
+ return -1;
+ } finally {
+ mDb.endTransaction();
+ }
+ }
+
+ /**
+ * Returns the index of the specified column. This is index is suitagble for use
+ * in calls to bind().
+ * @param key the column name
+ * @return the index of the column
+ */
+ public int getColumnIndex(String key) {
+ getStatement(false);
+ final Integer index = mColumns.get(key);
+ if (index == null) {
+ throw new IllegalArgumentException("column '" + key + "' is invalid");
+ }
+ return index;
+ }
+
+ /**
+ * Bind the value to an index. A prepareForInsert() or prepareForReplace()
+ * without a matching execute() must have already have been called.
+ * @param index the index of the slot to which to bind
+ * @param value the value to bind
+ */
+ public void bind(int index, double value) {
+ mPreparedStatement.bindDouble(index, value);
+ }
+
+ /**
+ * Bind the value to an index. A prepareForInsert() or prepareForReplace()
+ * without a matching execute() must have already have been called.
+ * @param index the index of the slot to which to bind
+ * @param value the value to bind
+ */
+ public void bind(int index, float value) {
+ mPreparedStatement.bindDouble(index, value);
+ }
+
+ /**
+ * Bind the value to an index. A prepareForInsert() or prepareForReplace()
+ * without a matching execute() must have already have been called.
+ * @param index the index of the slot to which to bind
+ * @param value the value to bind
+ */
+ public void bind(int index, long value) {
+ mPreparedStatement.bindLong(index, value);
+ }
+
+ /**
+ * Bind the value to an index. A prepareForInsert() or prepareForReplace()
+ * without a matching execute() must have already have been called.
+ * @param index the index of the slot to which to bind
+ * @param value the value to bind
+ */
+ public void bind(int index, int value) {
+ mPreparedStatement.bindLong(index, value);
+ }
+
+ /**
+ * Bind the value to an index. A prepareForInsert() or prepareForReplace()
+ * without a matching execute() must have already have been called.
+ * @param index the index of the slot to which to bind
+ * @param value the value to bind
+ */
+ public void bind(int index, boolean value) {
+ mPreparedStatement.bindLong(index, value ? 1 : 0);
+ }
+
+ /**
+ * Bind null to an index. A prepareForInsert() or prepareForReplace()
+ * without a matching execute() must have already have been called.
+ * @param index the index of the slot to which to bind
+ */
+ public void bindNull(int index) {
+ mPreparedStatement.bindNull(index);
+ }
+
+ /**
+ * Bind the value to an index. A prepareForInsert() or prepareForReplace()
+ * without a matching execute() must have already have been called.
+ * @param index the index of the slot to which to bind
+ * @param value the value to bind
+ */
+ public void bind(int index, byte[] value) {
+ if (value == null) {
+ mPreparedStatement.bindNull(index);
+ } else {
+ mPreparedStatement.bindBlob(index, value);
+ }
+ }
+
+ /**
+ * Bind the value to an index. A prepareForInsert() or prepareForReplace()
+ * without a matching execute() must have already have been called.
+ * @param index the index of the slot to which to bind
+ * @param value the value to bind
+ */
+ public void bind(int index, String value) {
+ if (value == null) {
+ mPreparedStatement.bindNull(index);
+ } else {
+ mPreparedStatement.bindString(index, value);
+ }
+ }
+
+ /**
+ * Performs an insert, adding a new row with the given values.
+ * If the table contains conflicting rows, an error is
+ * returned.
+ *
+ * @param values the set of values with which to populate the
+ * new row
+ *
+ * @return the row ID of the newly inserted row, or -1 if an
+ * error occurred
+ */
+ public long insert(ContentValues values) {
+ return insertInternal(values, false);
+ }
+
+ /**
+ * Execute the previously prepared insert or replace using the bound values
+ * since the last call to prepareForInsert or prepareForReplace.
+ *
+ *
Note that calling bind() and then execute() is not thread-safe. The only thread-safe
+ * way to use this class is to call insert() or replace().
+ *
+ * @return the row ID of the newly inserted row, or -1 if an
+ * error occurred
+ */
+ public long execute() {
+ if (mPreparedStatement == null) {
+ throw new IllegalStateException("you must prepare this inserter before calling "
+ + "execute");
+ }
+ try {
+ if (DEBUG) Log.v(TAG, "--- doing insert or replace in table " + mTableName);
+ return mPreparedStatement.executeInsert();
+ } catch (SQLException e) {
+ Log.e(TAG, "Error executing InsertHelper with table " + mTableName, e);
+ return -1;
+ } finally {
+ // you can only call this once per prepare
+ mPreparedStatement = null;
+ }
+ }
+
+ /**
+ * Prepare the InsertHelper for an insert. The pattern for this is:
+ *
+ *
prepareForInsert()
+ *
bind(index, value);
+ *
bind(index, value);
+ *
...
+ *
bind(index, value);
+ *
execute();
+ *
+ */
+ public void prepareForInsert() {
+ mPreparedStatement = getStatement(false);
+ mPreparedStatement.clearBindings();
+ }
+
+ /**
+ * Prepare the InsertHelper for a replace. The pattern for this is:
+ *
+ *
prepareForReplace()
+ *
bind(index, value);
+ *
bind(index, value);
+ *
...
+ *
bind(index, value);
+ *
execute();
+ *
+ */
+ public void prepareForReplace() {
+ mPreparedStatement = getStatement(true);
+ mPreparedStatement.clearBindings();
+ }
+
+ /**
+ * Performs an insert, adding a new row with the given values.
+ * If the table contains conflicting rows, they are deleted
+ * and replaced with the new row.
+ *
+ * @param values the set of values with which to populate the
+ * new row
+ *
+ * @return the row ID of the newly inserted row, or -1 if an
+ * error occurred
+ */
+ public long replace(ContentValues values) {
+ return insertInternal(values, true);
+ }
+
+ /**
+ * Close this object and release any resources associated with
+ * it. The behavior of calling insert() after
+ * calling this method is undefined.
+ */
+ public void close() {
+ if (mInsertStatement != null) {
+ mInsertStatement.close();
+ mInsertStatement = null;
+ }
+ if (mReplaceStatement != null) {
+ mReplaceStatement.close();
+ mReplaceStatement = null;
+ }
+ mInsertSQL = null;
+ mColumns = null;
+ }
+ }
+
+ /**
+ * Creates a db and populates it with the sql statements in sqlStatements.
+ *
+ * @param context the context to use to create the db
+ * @param dbName the name of the db to create
+ * @param dbVersion the version to set on the db
+ * @param sqlStatements the statements to use to populate the db. This should be a single string
+ * of the form returned by sqlite3's .dump command (statements separated by
+ * semicolons)
+ */
+ static public void createDbFromSqlStatements(
+ Context context, String dbName, int dbVersion, String sqlStatements) {
+
+ File f = context.getDatabasePath(dbName);
+ f.getParentFile().mkdirs();
+ SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(f, null);
+
+ // TODO: this is not quite safe since it assumes that all semicolons at the end of a line
+ // terminate statements. It is possible that a text field contains ;\n. We will have to fix
+ // this if that turns out to be a problem.
+ String[] statements = TextUtils.split(sqlStatements, ";\n");
+ for (String statement : statements) {
+ if (TextUtils.isEmpty(statement)) continue;
+ db.execSQL(statement);
+ }
+ db.setVersion(dbVersion);
+ db.close();
+ }
+
+ /**
+ * Returns one of the following which represent the type of the given SQL statement.
+ *
+ *
{@link #STATEMENT_SELECT}
+ *
{@link #STATEMENT_UPDATE}
+ *
{@link #STATEMENT_ATTACH}
+ *
{@link #STATEMENT_BEGIN}
+ *
{@link #STATEMENT_COMMIT}
+ *
{@link #STATEMENT_ABORT}
+ *
{@link #STATEMENT_OTHER}
+ *
+ * @param sql the SQL statement whose type is returned by this method
+ * @return one of the values listed above
+ */
+ public static int getSqlStatementType(String sql) {
+ sql = sql.trim();
+ if (sql.length() < 3) {
+ return STATEMENT_OTHER;
+ }
+ String prefixSql = sql.substring(0, 3).toUpperCase(Locale.ROOT);
+ if (prefixSql.equals("SEL")) {
+ return STATEMENT_SELECT;
+ } else if (prefixSql.equals("INS") ||
+ prefixSql.equals("UPD") ||
+ prefixSql.equals("REP") ||
+ prefixSql.equals("DEL")) {
+ return STATEMENT_UPDATE;
+ } else if (prefixSql.equals("ATT")) {
+ return STATEMENT_ATTACH;
+ } else if (prefixSql.equals("COM")) {
+ return STATEMENT_COMMIT;
+ } else if (prefixSql.equals("END")) {
+ return STATEMENT_COMMIT;
+ } else if (prefixSql.equals("ROL")) {
+ return STATEMENT_ABORT;
+ } else if (prefixSql.equals("BEG")) {
+ return STATEMENT_BEGIN;
+ } else if (prefixSql.equals("PRA")) {
+ return STATEMENT_PRAGMA;
+ } else if (prefixSql.equals("CRE") || prefixSql.equals("DRO") ||
+ prefixSql.equals("ALT")) {
+ return STATEMENT_DDL;
+ } else if (prefixSql.equals("ANA") || prefixSql.equals("DET")) {
+ return STATEMENT_UNPREPARED;
+ }
+ return STATEMENT_OTHER;
+ }
+
+ /**
+ * Appends one set of selection args to another. This is useful when adding a selection
+ * argument to a user provided set.
+ */
+ public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) {
+ if (originalValues == null || originalValues.length == 0) {
+ return newValues;
+ }
+ String[] result = new String[originalValues.length + newValues.length ];
+ System.arraycopy(originalValues, 0, result, 0, originalValues.length);
+ System.arraycopy(newValues, 0, result, originalValues.length, newValues.length);
+ return result;
+ }
+
+ /**
+ * Returns column index of "_id" column, or -1 if not found.
+ * @hide
+ */
+ public static int findRowIdColumnIndex(String[] columnNames) {
+ int length = columnNames.length;
+ for (int i = 0; i < length; i++) {
+ if (columnNames[i].equals("_id")) {
+ return i;
+ }
+ }
+ return -1;
+ }
+}
Index: sqlite3/src/main/jni/sqlite/sqlite3.c
==================================================================
--- sqlite3/src/main/jni/sqlite/sqlite3.c
+++ sqlite3/src/main/jni/sqlite/sqlite3.c
@@ -943,13 +943,15 @@
**
** Similar is true for Mac OS X. LFS is only supported on Mac OS X 9 and later.
*/
#ifndef SQLITE_DISABLE_LFS
# define _LARGE_FILE 1
+#if 0
# ifndef _FILE_OFFSET_BITS
# define _FILE_OFFSET_BITS 64
# endif
+#endif
# define _LARGEFILE_SOURCE 1
#endif
/* The GCC_VERSION and MSVC_VERSION macros are used to
** conditionally include optimizations for each of these compilers. A
Index: sqlite3test/build.gradle
==================================================================
--- sqlite3test/build.gradle
+++ sqlite3test/build.gradle
@@ -1,10 +1,9 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
- buildToolsVersion '25.0.0'
defaultConfig {
applicationId "org.sqlite.customsqlitetest"
minSdkVersion 16
targetSdkVersion 23