SQLite Android Bindings

Check-in [2574c498b9]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Add modified versions of some of the test files from the Android Compatibility Test Suite (https://android.googlesource.com/platform/cts/).
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | experimental
Files: files | file ages | folders
SHA1: 2574c498b9a320bc320d92aa7b1df600757838b5
User & Date: dan 2017-11-13 21:02:26.811
Context
2017-11-14
21:13
Add further cts tests to this project. (check-in: 09d6816449 user: dan tags: experimental)
2017-11-13
21:02
Add modified versions of some of the test files from the Android Compatibility Test Suite (https://android.googlesource.com/platform/cts/). (check-in: 2574c498b9 user: dan tags: experimental)
18:48
Upgrade to gradle 3.0. Remove ApplicationTest class (not required?). (check-in: df18f25465 user: dan tags: experimental)
Changes
Unified Diff Ignore Whitespace Patch
Added sqlite3/src/androidTest/java/org/sqlite/database/SQLiteAbortExceptionTest.java.
























































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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;

import org.sqlite.database.sqlite.SQLiteAbortException;
import android.test.AndroidTestCase;

public class SQLiteAbortExceptionTest extends AndroidTestCase {
    public void testConstructor() {
        new SQLiteAbortException();

        new SQLiteAbortException("error");
    }
}
Added sqlite3/src/androidTest/java/org/sqlite/database/SQLiteClosableTest.java.






















































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/*
 * 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;

import org.sqlite.database.sqlite.SQLiteClosable;
import android.test.AndroidTestCase;

public class SQLiteClosableTest extends AndroidTestCase {
    private class MockSQLiteClosable extends SQLiteClosable {
        private boolean mOnAllReferencesReleasedCalled = false;
        private boolean mOnAllReferencesReleasedFromContainerCalled = false;

        @Override
        protected void onAllReferencesReleased() {
            mOnAllReferencesReleasedCalled = true;
        }

        protected void onAllReferencesReleasedFromContainer() {
            mOnAllReferencesReleasedFromContainerCalled = true;
        }

        public boolean isOnAllReferencesReleasedCalled() {
            return mOnAllReferencesReleasedCalled;
        }

        public boolean isOnAllReferencesReleasedFromContainerCalled() {
            return mOnAllReferencesReleasedFromContainerCalled;
        }
    }

    public void testAcquireReference() {
        MockSQLiteClosable closable = new MockSQLiteClosable();

        closable.acquireReference();
        closable.releaseReference();

        assertFalse(closable.isOnAllReferencesReleasedCalled());
        closable.releaseReference();
        // the reference count is 0 now.
        assertTrue(closable.isOnAllReferencesReleasedCalled());

        try {
            closable.acquireReference();
            fail("should throw IllegalStateException.");
        } catch (IllegalStateException e) {
        }
    }

    public void testReleaseReferenceFromContainer() {
        MockSQLiteClosable closable = new MockSQLiteClosable();

        closable.acquireReference();
        closable.releaseReferenceFromContainer();

        // the reference count is 1 now.
        assertFalse(closable.isOnAllReferencesReleasedFromContainerCalled());
        closable.releaseReferenceFromContainer();
        // the reference count is 0 now.
        assertTrue(closable.isOnAllReferencesReleasedFromContainerCalled());
    }
}
Added sqlite3/src/androidTest/java/org/sqlite/database/SQLiteCursorTest.java.
































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
/*
 * 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;


import android.content.Context;
import android.database.AbstractCursor;
import android.database.Cursor;
import android.database.CursorWindow;
import android.database.DataSetObserver;
import android.database.StaleDataException;
import org.sqlite.database.sqlite.SQLiteCursor;
import org.sqlite.database.sqlite.SQLiteDatabase;
import org.sqlite.database.sqlite.SQLiteDirectCursorDriver;
import android.test.AndroidTestCase;

import java.io.File;
import java.util.Arrays;

/**
 * Test {@link AbstractCursor}.
 */
public class SQLiteCursorTest extends AndroidTestCase {
    private SQLiteDatabase mDatabase;
    private static final String[] COLUMNS = new String[] { "_id", "number_1", "number_2" };
    private static final String TABLE_NAME = "test";
    private static final String TABLE_COLUMNS = " number_1 INTEGER, number_2 INTEGER";
    private static final int DEFAULT_TABLE_VALUE_BEGINS = 1;
    private static final int TEST_COUNT = 10;
    private static final String TEST_SQL = "SELECT * FROM test ORDER BY number_1";
    private static final String DATABASE_FILE = "database_test.db";

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        System.loadLibrary("sqliteX");
        File f = mContext.getDatabasePath(MyHelper.DATABASE_NAME);
        f.mkdirs();
        if (f.exists()) { f.delete(); }
        mDatabase = SQLiteDatabase.openOrCreateDatabase(f, null);
        createTable(TABLE_NAME, TABLE_COLUMNS);
        addValuesIntoTable(TABLE_NAME, DEFAULT_TABLE_VALUE_BEGINS, TEST_COUNT);
    }

    @Override
    protected void tearDown() throws Exception {
        mDatabase.close();
        getContext().deleteDatabase(DATABASE_FILE);
        super.tearDown();
    }

    public void testConstructor() {
        SQLiteDirectCursorDriver cursorDriver = new SQLiteDirectCursorDriver(mDatabase,
                TEST_SQL, TABLE_NAME, null);
        try {
            new SQLiteCursor(mDatabase, cursorDriver, TABLE_NAME, null);
            fail("constructor didn't throw IllegalArgumentException when SQLiteQuery is null");
        } catch (IllegalArgumentException e) {
        }

        // get SQLiteCursor by querying database
        SQLiteCursor cursor = getCursor();
        assertNotNull(cursor);
    }

    public void testClose() {
        SQLiteCursor cursor = getCursor();
        assertTrue(cursor.moveToFirst());
        assertFalse(cursor.isClosed());
        assertTrue(cursor.requery());
        cursor.close();
        assertFalse(cursor.requery());
        try {
            cursor.moveToFirst();
            fail("moveToFirst didn't throw IllegalStateException after closed.");
        } catch (IllegalStateException e) {
        }
        assertTrue(cursor.isClosed());
    }

    public void testRegisterDataSetObserver() {
        SQLiteCursor cursor = getCursor();
        MockCursorWindow cursorWindow = new MockCursorWindow(false);

        MockObserver observer = new MockObserver();

        cursor.setWindow(cursorWindow);
        // Before registering, observer can't be notified.
        assertFalse(observer.hasInvalidated());
        cursor.moveToLast();
        assertFalse(cursorWindow.isClosed());
        cursor.deactivate();
        assertFalse(observer.hasInvalidated());
        // deactivate() will close the CursorWindow
        assertTrue(cursorWindow.isClosed());

        // test registering DataSetObserver
        assertTrue(cursor.requery());
        cursor.registerDataSetObserver(observer);
        assertFalse(observer.hasInvalidated());
        cursor.moveToLast();
        assertEquals(TEST_COUNT, cursor.getInt(1));
        cursor.deactivate();
        // deactivate method can invoke invalidate() method, can be observed by DataSetObserver.
        assertTrue(observer.hasInvalidated());

        try {
            cursor.getInt(1);
            fail("After deactivating, cursor cannot execute getting value operations.");
        } catch (StaleDataException e) {
        }

        assertTrue(cursor.requery());
        cursor.moveToLast();
        assertEquals(TEST_COUNT, cursor.getInt(1));

        // can't register a same observer twice.
        try {
            cursor.registerDataSetObserver(observer);
            fail("didn't throw IllegalStateException when register existed observer");
        } catch (IllegalStateException e) {
        }

        // after unregistering, observer can't be notified.
        cursor.unregisterDataSetObserver(observer);
        observer.resetStatus();
        assertFalse(observer.hasInvalidated());
        cursor.deactivate();
        assertFalse(observer.hasInvalidated());
    }

    public void testRequery() {
        final String DELETE = "DELETE FROM " + TABLE_NAME + " WHERE number_1 =";
        final String DELETE_1 = DELETE + "1;";
        final String DELETE_2 = DELETE + "2;";

        mDatabase.execSQL(DELETE_1);
        // when cursor is created, it refreshes CursorWindow and populates cursor count
        SQLiteCursor cursor = getCursor();
        MockObserver observer = new MockObserver();
        cursor.registerDataSetObserver(observer);
        assertEquals(TEST_COUNT - 1, cursor.getCount());
        assertFalse(observer.hasChanged());

        mDatabase.execSQL(DELETE_2);
        // when getCount() has invoked once, it can no longer refresh CursorWindow.
        assertEquals(TEST_COUNT - 1, cursor.getCount());

        assertTrue(cursor.requery());
        // only after requery, getCount can get most up-to-date counting info now.
        assertEquals(TEST_COUNT - 2, cursor.getCount());
        assertTrue(observer.hasChanged());
    }

    public void testRequery2() {
        mDatabase.disableWriteAheadLogging();
        mDatabase.execSQL("create table testRequery2 (i int);");
        mDatabase.execSQL("insert into testRequery2 values(1);");
        mDatabase.execSQL("insert into testRequery2 values(2);");
        Cursor c = mDatabase.rawQuery("select * from testRequery2 order by i", null);
        assertEquals(2, c.getCount());
        assertTrue(c.moveToFirst());
        assertEquals(1, c.getInt(0));
        assertTrue(c.moveToNext());
        assertEquals(2, c.getInt(0));
        // add more data to the table and requery
        mDatabase.execSQL("insert into testRequery2 values(3);");
        assertTrue(c.requery());
        assertEquals(3, c.getCount());
        assertTrue(c.moveToFirst());
        assertEquals(1, c.getInt(0));
        assertTrue(c.moveToNext());
        assertEquals(2, c.getInt(0));
        assertTrue(c.moveToNext());
        assertEquals(3, c.getInt(0));
        // close the database and see if requery throws an exception
        mDatabase.close();
        assertFalse(c.requery());
    }

    public void testGetColumnIndex() {
        SQLiteCursor cursor = getCursor();

        for (int i = 0; i < COLUMNS.length; i++) {
            assertEquals(i, cursor.getColumnIndex(COLUMNS[i]));
        }

        assertTrue(Arrays.equals(COLUMNS, cursor.getColumnNames()));
    }

    public void testSetSelectionArguments() {
        final String SELECTION = "_id > ?";
        int TEST_ARG1 = 2;
        int TEST_ARG2 = 5;
        SQLiteCursor cursor = (SQLiteCursor) mDatabase.query(TABLE_NAME, null, SELECTION,
                new String[] { Integer.toString(TEST_ARG1) }, null, null, null);
        assertEquals(TEST_COUNT - TEST_ARG1, cursor.getCount());
        cursor.setSelectionArguments(new String[] { Integer.toString(TEST_ARG2) });
        cursor.requery();
        assertEquals(TEST_COUNT - TEST_ARG2, cursor.getCount());
    }

    public void testOnMove() {
        // Do not test this API. It is callback which:
        // 1. The callback mechanism has been tested in super class
        // 2. The functionality is implementation details, no need to test
    }

    private void createTable(String tableName, String columnNames) {
        String sql = "Create TABLE " + tableName + " (_id INTEGER PRIMARY KEY, "
                + columnNames + " );";
        mDatabase.execSQL(sql);
    }

    private void addValuesIntoTable(String tableName, int start, int end) {
        for (int i = start; i <= end; i++) {
            mDatabase.execSQL("INSERT INTO " + tableName + "(number_1) VALUES ('" + i + "');");
        }
    }

    private SQLiteCursor getCursor() {
        SQLiteCursor cursor = (SQLiteCursor) mDatabase.query(TABLE_NAME, null, null,
                null, null, null, null);
        return cursor;
    }

    private class MockObserver extends DataSetObserver {
        private boolean mHasChanged = false;
        private boolean mHasInvalidated = false;

        @Override
        public void onChanged() {
            super.onChanged();
            mHasChanged = true;
        }

        @Override
        public void onInvalidated() {
            super.onInvalidated();
            mHasInvalidated = true;
        }

        protected void resetStatus() {
            mHasChanged = false;
            mHasInvalidated = false;
        }

        protected boolean hasChanged() {
            return mHasChanged;
        }

        protected boolean hasInvalidated () {
            return mHasInvalidated;
        }
    }

    private class MockCursorWindow extends CursorWindow {
        private boolean mIsClosed = false;

        public MockCursorWindow(boolean localWindow) {
            super(localWindow);
        }

        @Override
        public void close() {
            super.close();
            mIsClosed = true;
        }

        public boolean isClosed() {
            return mIsClosed;
        }
    }
}
Added sqlite3/src/androidTest/java/org/sqlite/database/SQLiteFtsTest.java.
































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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;

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(MyHelper.DATABASE_NAME);
        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/SQLiteStatementTest.java.




































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
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;


import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.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, ExtraUtils.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, ExtraUtils.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, ExtraUtils.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();
        }
    }
}