forked from requery/sqlite-android
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSQLiteConnection.java
More file actions
1587 lines (1451 loc) · 65.1 KB
/
SQLiteConnection.java
File metadata and controls
1587 lines (1451 loc) · 65.1 KB
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
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
/*
** Modified to support SQLite extensions by the SQLite developers:
** sqlite-dev@sqlite.org.
*/
package io.requery.android.database.sqlite;
import android.annotation.SuppressLint;
import android.database.Cursor;
import android.database.sqlite.SQLiteBindOrColumnIndexOutOfRangeException;
import android.database.sqlite.SQLiteDatabaseLockedException;
import android.database.sqlite.SQLiteException;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.util.Printer;
import androidx.collection.LruCache;
import androidx.core.os.CancellationSignal;
import androidx.core.os.OperationCanceledException;
import io.requery.android.database.CursorWindow;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Represents a SQLite database connection.
* Each connection wraps an instance of a native <code>sqlite3</code> object.
* <p>
* When database connection pooling is enabled, there can be multiple active
* connections to the same database. Otherwise there is typically only one
* connection per database.
* </p><p>
* When the SQLite WAL feature is enabled, multiple readers and one writer
* can concurrently access the database. Without WAL, readers and writers
* are mutually exclusive.
* </p>
*
* <h2>Ownership and concurrency guarantees</h2>
* <p>
* Connection objects are not thread-safe. They are acquired as needed to
* perform a database operation and are then returned to the pool. At any
* given time, a connection is either owned and used by a {@link SQLiteSession}
* object or the {@link SQLiteConnectionPool}. Those classes are
* responsible for serializing operations to guard against concurrent
* use of a connection.
* </p><p>
* The guarantee of having a single owner allows this class to be implemented
* without locks and greatly simplifies resource management.
* </p>
*
* <h2>Encapsulation guarantees</h2>
* <p>
* The connection object object owns *all* of the SQLite related native
* objects that are associated with the connection. What's more, there are
* no other objects in the system that are capable of obtaining handles to
* those native objects. Consequently, when the connection is closed, we do
* not have to worry about what other components might have references to
* its associated SQLite state -- there are none.
* </p><p>
* Encapsulation is what ensures that the connection object's
* lifecycle does not become a tortured mess of finalizers and reference
* queues.
* </p>
*
* <h2>Reentrance</h2>
* <p>
* This class must tolerate reentrant execution of SQLite operations because
* triggers may call custom SQLite functions that perform additional queries.
* </p>
*
* @hide
*/
@SuppressWarnings("TryFinallyCanBeTryWithResources")
public final class SQLiteConnection implements CancellationSignal.OnCancelListener {
private static final String TAG = "SQLiteConnection";
private static final boolean DEBUG = false;
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
private static final Pattern TRIM_SQL_PATTERN = Pattern.compile("[\\s]*\\n+[\\s]*");
private final CloseGuard mCloseGuard = CloseGuard.get();
private final SQLiteConnectionPool mPool;
private final SQLiteDatabaseConfiguration mConfiguration;
private final int mConnectionId;
private final boolean mIsPrimaryConnection;
private final boolean mIsReadOnlyConnection;
private final PreparedStatementCache mPreparedStatementCache;
private PreparedStatement mPreparedStatementPool;
// The recent operations log.
private final OperationLog mRecentOperations = new OperationLog();
// The native SQLiteConnection pointer. (FOR INTERNAL USE ONLY)
private long mConnectionPtr;
private boolean mOnlyAllowReadOnlyOperations;
// The number of times attachCancellationSignal has been called.
// Because SQLite statement execution can be reentrant, we keep track of how many
// times we have attempted to attach a cancellation signal to the connection so that
// we can ensure that we detach the signal at the right time.
private int mCancellationSignalAttachCount;
private static native long nativeOpen(String path, int openFlags, String label,
boolean enableTrace, boolean enableProfile);
private static native void nativeClose(long connectionPtr);
private static native void nativeRegisterCustomFunction(long connectionPtr,
SQLiteCustomFunction function);
private static native void nativeRegisterFunction(long connectionPtr,
SQLiteFunction function);
private static native void nativeRegisterLocalizedCollators(long connectionPtr, String locale);
private static native long nativePrepareStatement(long connectionPtr, String sql);
private static native void nativeFinalizeStatement(long connectionPtr, long statementPtr);
private static native int nativeGetParameterCount(long connectionPtr, long statementPtr);
private static native boolean nativeIsReadOnly(long connectionPtr, long statementPtr);
private static native int nativeGetColumnCount(long connectionPtr, long statementPtr);
private static native String nativeGetColumnName(long connectionPtr, long statementPtr,
int index);
private static native void nativeBindNull(long connectionPtr, long statementPtr,
int index);
private static native void nativeBindLong(long connectionPtr, long statementPtr,
int index, long value);
private static native void nativeBindDouble(long connectionPtr, long statementPtr,
int index, double value);
private static native void nativeBindString(long connectionPtr, long statementPtr,
int index, String value);
private static native void nativeBindBlob(long connectionPtr, long statementPtr,
int index, byte[] value);
private static native void nativeResetStatementAndClearBindings(
long connectionPtr, long statementPtr);
private static native void nativeExecute(long connectionPtr, long statementPtr);
private static native long nativeExecuteForLong(long connectionPtr, long statementPtr);
private static native String nativeExecuteForString(long connectionPtr, long statementPtr);
private static native int nativeExecuteForBlobFileDescriptor(
long connectionPtr, long statementPtr);
private static native int nativeExecuteForChangedRowCount(long connectionPtr, long statementPtr);
private static native long nativeExecuteForLastInsertedRowId(
long connectionPtr, long statementPtr);
private static native long nativeExecuteForCursorWindow(
long connectionPtr, long statementPtr, long winPtr,
int startPos, int requiredPos, boolean countAllRows);
private static native int nativeGetDbLookaside(long connectionPtr);
private static native void nativeCancel(long connectionPtr);
private static native void nativeResetCancel(long connectionPtr, boolean cancelable);
private static native boolean nativeHasCodec();
private static native void nativeLoadExtension(long connectionPtr, String file, String proc);
private static native void nativeRegisterUpdateHook(long connectionPtr, SQLiteUpdateHook updateCallback);
public static boolean hasCodec(){ return nativeHasCodec(); }
private SQLiteConnection(SQLiteConnectionPool pool,
SQLiteDatabaseConfiguration configuration,
int connectionId, boolean primaryConnection) {
mPool = pool;
mConfiguration = new SQLiteDatabaseConfiguration(configuration);
mConnectionId = connectionId;
mIsPrimaryConnection = primaryConnection;
mIsReadOnlyConnection = (configuration.openFlags & SQLiteDatabase.OPEN_READONLY) != 0;
mPreparedStatementCache = new PreparedStatementCache(
mConfiguration.maxSqlCacheSize);
mCloseGuard.open("close");
}
@SuppressWarnings("ThrowFromFinallyBlock")
@Override
protected void finalize() throws Throwable {
try {
if (mPool != null && mConnectionPtr != 0) {
mPool.onConnectionLeaked();
}
dispose(true);
} finally {
super.finalize();
}
}
// Called by SQLiteConnectionPool only.
static SQLiteConnection open(SQLiteConnectionPool pool,
SQLiteDatabaseConfiguration configuration,
int connectionId, boolean primaryConnection) {
SQLiteConnection connection = new SQLiteConnection(pool, configuration,
connectionId, primaryConnection);
try {
connection.open();
return connection;
} catch (SQLiteException ex) {
connection.dispose(false);
throw ex;
}
}
// Called by SQLiteConnectionPool only.
// Closes the database closes and releases all of its associated resources.
// Do not call methods on the connection after it is closed. It will probably crash.
void close() {
dispose(false);
}
private void open() {
mConnectionPtr = nativeOpen(mConfiguration.path,
// remove the wal flag as its a custom flag not supported by sqlite3_open_v2
mConfiguration.openFlags & ~SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING,
mConfiguration.label,
SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME);
setPageSize();
setForeignKeyModeFromConfiguration();
setJournalSizeLimit();
setAutoCheckpointInterval();
if (!nativeHasCodec()) {
setWalModeFromConfiguration();
setLocaleFromConfiguration();
}
// Register (deprecated) custom functions.
final int customFunctionCount = mConfiguration.customFunctions.size();
for (int i = 0; i < customFunctionCount; i++) {
SQLiteCustomFunction function = mConfiguration.customFunctions.get(i);
nativeRegisterCustomFunction(mConnectionPtr, function);
}
// Register functions
final int functionCount = mConfiguration.functions.size();
for (int i = 0; i < functionCount; i++) {
SQLiteFunction function = mConfiguration.functions.get(i);
nativeRegisterFunction(mConnectionPtr, function);
}
// Register custom extensions
for (SQLiteCustomExtension extension : mConfiguration.customExtensions) {
nativeLoadExtension(mConnectionPtr, extension.path, extension.entryPoint);
}
final SQLiteUpdateHook sqliteUpdateHook = mConfiguration.sqliteUpdateHook;
if (sqliteUpdateHook != null) {
nativeRegisterUpdateHook(mConnectionPtr, sqliteUpdateHook);
}
}
private void dispose(boolean finalized) {
if (mCloseGuard != null) {
if (finalized) {
mCloseGuard.warnIfOpen();
}
mCloseGuard.close();
}
if (mConnectionPtr != 0) {
final int cookie = mRecentOperations.beginOperation("close", null, null);
try {
mPreparedStatementCache.evictAll();
nativeClose(mConnectionPtr);
mConnectionPtr = 0;
} finally {
mRecentOperations.endOperation(cookie);
}
}
}
private void setPageSize() {
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
final long newValue = SQLiteGlobal.getDefaultPageSize();
long value = executeForLong("PRAGMA page_size", null, null);
if (value != newValue) {
execute("PRAGMA page_size=" + newValue, null, null);
}
}
}
private void setAutoCheckpointInterval() {
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
final long newValue = SQLiteGlobal.getWALAutoCheckpoint();
long value = executeForLong("PRAGMA wal_autocheckpoint", null, null);
if (value != newValue) {
executeForLong("PRAGMA wal_autocheckpoint=" + newValue, null, null);
}
}
}
private void setJournalSizeLimit() {
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
final long newValue = SQLiteGlobal.getJournalSizeLimit();
long value = executeForLong("PRAGMA journal_size_limit", null, null);
if (value != newValue) {
executeForLong("PRAGMA journal_size_limit=" + newValue, null, null);
}
}
}
private void setForeignKeyModeFromConfiguration() {
if (!mIsReadOnlyConnection) {
final long newValue = mConfiguration.foreignKeyConstraintsEnabled ? 1 : 0;
long value = executeForLong("PRAGMA foreign_keys", null, null);
if (value != newValue) {
execute("PRAGMA foreign_keys=" + newValue, null, null);
}
}
}
private void setWalModeFromConfiguration() {
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
if ((mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) {
setJournalMode("WAL");
setSyncMode(SQLiteGlobal.getWALSyncMode());
} else {
setJournalMode(SQLiteGlobal.getDefaultJournalMode());
setSyncMode(SQLiteGlobal.getDefaultSyncMode());
}
}
}
private void setSyncMode(String newValue) {
String value = executeForString("PRAGMA synchronous", null, null);
if (!canonicalizeSyncMode(value).equalsIgnoreCase(
canonicalizeSyncMode(newValue))) {
execute("PRAGMA synchronous=" + newValue, null, null);
}
}
private static String canonicalizeSyncMode(String value) {
switch (value) {
case "0":
return "OFF";
case "1":
return "NORMAL";
case "2":
return "FULL";
}
return value;
}
private void setJournalMode(String newValue) {
String value = executeForString("PRAGMA journal_mode", null, null);
if (!value.equalsIgnoreCase(newValue)) {
try {
String result = executeForString("PRAGMA journal_mode=" + newValue, null, null);
if (result.equalsIgnoreCase(newValue)) {
return;
}
// PRAGMA journal_mode silently fails and returns the original journal
// mode in some cases if the journal mode could not be changed.
} catch (SQLiteException ex) {
// This error (SQLITE_BUSY) occurs if one connection has the database
// open in WAL mode and another tries to change it to non-WAL.
if (!(ex instanceof SQLiteDatabaseLockedException)) {
throw ex;
}
}
// Because we always disable WAL mode when a database is first opened
// (even if we intend to re-enable it), we can encounter problems if
// there is another open connection to the database somewhere.
// This can happen for a variety of reasons such as an application opening
// the same database in multiple processes at the same time or if there is a
// crashing content provider service that the ActivityManager has
// removed from its registry but whose process hasn't quite died yet
// by the time it is restarted in a new process.
//
// If we don't change the journal mode, nothing really bad happens.
// In the worst case, an application that enables WAL might not actually
// get it, although it can still use connection pooling.
Log.w(TAG, "Could not change the database journal mode of '"
+ mConfiguration.label + "' from '" + value + "' to '" + newValue
+ "' because the database is locked. This usually means that "
+ "there are other open connections to the database which prevents "
+ "the database from enabling or disabling write-ahead logging mode. "
+ "Proceeding without changing the journal mode.");
}
}
private void setLocaleFromConfiguration() {
// Register the localized collators.
final String newLocale = mConfiguration.locale.toString();
nativeRegisterLocalizedCollators(mConnectionPtr, newLocale);
// If the database is read-only, we cannot modify the android metadata table
// or existing indexes.
if (mIsReadOnlyConnection) {
return;
}
try {
// Ensure the android metadata table exists.
execute("CREATE TABLE IF NOT EXISTS android_metadata (locale TEXT)", null, null);
// Check whether the locale was actually changed.
final String oldLocale = executeForString("SELECT locale FROM android_metadata "
+ "UNION SELECT NULL ORDER BY locale DESC LIMIT 1", null, null);
if (oldLocale != null && oldLocale.equals(newLocale)) {
return;
}
// Go ahead and update the indexes using the new locale.
execute("BEGIN", null, null);
boolean success = false;
try {
execute("DELETE FROM android_metadata", null, null);
execute("INSERT INTO android_metadata (locale) VALUES(?)",
new Object[] { newLocale }, null);
execute("REINDEX LOCALIZED", null, null);
success = true;
} finally {
execute(success ? "COMMIT" : "ROLLBACK", null, null);
}
} catch (RuntimeException ex) {
throw new SQLiteException("Failed to change locale for db '" + mConfiguration.label
+ "' to '" + newLocale + "'.");
}
}
public void enableLocalizedCollators() {
if (nativeHasCodec()) {
setLocaleFromConfiguration();
}
}
// Called by SQLiteConnectionPool only.
void reconfigure(SQLiteDatabaseConfiguration configuration) {
mOnlyAllowReadOnlyOperations = false;
// Register (deprecated) custom functions.
final int customFunctionCount = configuration.customFunctions.size();
for (int i = 0; i < customFunctionCount; i++) {
SQLiteCustomFunction function = configuration.customFunctions.get(i);
if (!mConfiguration.customFunctions.contains(function)) {
nativeRegisterCustomFunction(mConnectionPtr, function);
}
}
// Register Functions
final int functionCount = configuration.functions.size();
for (int i = 0; i < functionCount; i++) {
SQLiteFunction function = configuration.functions.get(i);
if (!mConfiguration.functions.contains(function)) {
nativeRegisterFunction(mConnectionPtr, function);
}
}
final SQLiteUpdateHook updateHook = configuration.sqliteUpdateHook;
if (updateHook != null) {
nativeRegisterUpdateHook(mConnectionPtr, updateHook);
}
// Remember what changed.
boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
!= mConfiguration.foreignKeyConstraintsEnabled;
boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags)
& SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
boolean localeChanged = !configuration.locale.equals(mConfiguration.locale);
// Update configuration parameters.
mConfiguration.updateParametersFrom(configuration);
// Update prepared statement cache size.
/* mPreparedStatementCache.resize(configuration.maxSqlCacheSize); */
// Update foreign key mode.
if (foreignKeyModeChanged) {
setForeignKeyModeFromConfiguration();
}
// Update WAL.
if (walModeChanged) {
setWalModeFromConfiguration();
}
// Update locale.
if (localeChanged) {
setLocaleFromConfiguration();
}
}
// Called by SQLiteConnectionPool only.
// When set to true, executing write operations will throw SQLiteException.
// Preparing statements that might write is ok, just don't execute them.
void setOnlyAllowReadOnlyOperations(boolean readOnly) {
mOnlyAllowReadOnlyOperations = readOnly;
}
// Called by SQLiteConnectionPool only.
// Returns true if the prepared statement cache contains the specified SQL.
boolean isPreparedStatementInCache(String sql) {
return mPreparedStatementCache.get(sql) != null;
}
/**
* Returns true if this is the primary database connection.
* @return True if this is the primary database connection.
*/
public boolean isPrimaryConnection() {
return mIsPrimaryConnection;
}
/**
* Prepares a statement for execution but does not bind its parameters or execute it.
* <p>
* This method can be used to check for syntax errors during compilation
* prior to execution of the statement. If the {@code outStatementInfo} argument
* is not null, the provided {@link SQLiteStatementInfo} object is populated
* with information about the statement.
* </p><p>
* A prepared statement makes no reference to the arguments that may eventually
* be bound to it, consequently it it possible to cache certain prepared statements
* such as SELECT or INSERT/UPDATE statements. If the statement is cacheable,
* then it will be stored in the cache for later.
* </p><p>
* To take advantage of this behavior as an optimization, the connection pool
* provides a method to acquire a connection that already has a given SQL statement
* in its prepared statement cache so that it is ready for execution.
* </p>
*
* @param sql The SQL statement to prepare.
* @param outStatementInfo The {@link SQLiteStatementInfo} object to populate
* with information about the statement, or null if none.
*
* @throws SQLiteException if an error occurs, such as a syntax error.
*/
public void prepare(String sql, SQLiteStatementInfo outStatementInfo) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
final int cookie = mRecentOperations.beginOperation("prepare", sql, null);
try {
final PreparedStatement statement = acquirePreparedStatement(sql);
try {
if (outStatementInfo != null) {
outStatementInfo.numParameters = statement.mNumParameters;
outStatementInfo.readOnly = statement.mReadOnly;
final int columnCount = nativeGetColumnCount(
mConnectionPtr, statement.mStatementPtr);
if (columnCount == 0) {
outStatementInfo.columnNames = EMPTY_STRING_ARRAY;
} else {
outStatementInfo.columnNames = new String[columnCount];
for (int i = 0; i < columnCount; i++) {
outStatementInfo.columnNames[i] = nativeGetColumnName(
mConnectionPtr, statement.mStatementPtr, i);
}
}
}
} finally {
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
mRecentOperations.endOperation(cookie);
}
}
/**
* Executes a statement that does not return a result.
*
* @param sql The SQL statement to execute.
* @param bindArgs The arguments to bind, or null if none.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
*
* @throws SQLiteException if an error occurs, such as a syntax error
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
public void execute(String sql, Object[] bindArgs,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
final int cookie = mRecentOperations.beginOperation("execute", sql, bindArgs);
try {
final PreparedStatement statement = acquirePreparedStatement(sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
applyBlockGuardPolicy(statement);
attachCancellationSignal(cancellationSignal);
try {
nativeExecute(mConnectionPtr, statement.mStatementPtr);
} finally {
detachCancellationSignal(cancellationSignal);
}
} finally {
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
mRecentOperations.endOperation(cookie);
}
}
/**
* Executes a statement that returns a single <code>long</code> result.
*
* @param sql The SQL statement to execute.
* @param bindArgs The arguments to bind, or null if none.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* @return The value of the first column in the first row of the result set
* as a <code>long</code>, or zero if none.
*
* @throws SQLiteException if an error occurs, such as a syntax error
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
public long executeForLong(String sql, Object[] bindArgs,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
final int cookie = mRecentOperations.beginOperation("executeForLong", sql, bindArgs);
try {
final PreparedStatement statement = acquirePreparedStatement(sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
applyBlockGuardPolicy(statement);
attachCancellationSignal(cancellationSignal);
try {
return nativeExecuteForLong(mConnectionPtr, statement.mStatementPtr);
} finally {
detachCancellationSignal(cancellationSignal);
}
} finally {
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
mRecentOperations.endOperation(cookie);
}
}
/**
* Executes a statement that returns a single {@link String} result.
*
* @param sql The SQL statement to execute.
* @param bindArgs The arguments to bind, or null if none.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* @return The value of the first column in the first row of the result set
* as a <code>String</code>, or null if none.
*
* @throws SQLiteException if an error occurs, such as a syntax error
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
public String executeForString(String sql, Object[] bindArgs,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
final int cookie = mRecentOperations.beginOperation("executeForString", sql, bindArgs);
try {
final PreparedStatement statement = acquirePreparedStatement(sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
applyBlockGuardPolicy(statement);
attachCancellationSignal(cancellationSignal);
try {
return nativeExecuteForString(mConnectionPtr, statement.mStatementPtr);
} finally {
detachCancellationSignal(cancellationSignal);
}
} finally {
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
mRecentOperations.endOperation(cookie);
}
}
/**
* Executes a statement that returns a single BLOB result as a
* file descriptor to a shared memory region.
*
* @param sql The SQL statement to execute.
* @param bindArgs The arguments to bind, or null if none.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* @return The file descriptor for a shared memory region that contains
* the value of the first column in the first row of the result set as a BLOB,
* or null if none.
*
* @throws SQLiteException if an error occurs, such as a syntax error
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
final int cookie = mRecentOperations.beginOperation("executeForBlobFileDescriptor",
sql, bindArgs);
try {
final PreparedStatement statement = acquirePreparedStatement(sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
applyBlockGuardPolicy(statement);
attachCancellationSignal(cancellationSignal);
try {
int fd = nativeExecuteForBlobFileDescriptor(
mConnectionPtr, statement.mStatementPtr);
return fd >= 0 ? ParcelFileDescriptor.adoptFd(fd) : null;
} finally {
detachCancellationSignal(cancellationSignal);
}
} finally {
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
mRecentOperations.endOperation(cookie);
}
}
/**
* Executes a statement that returns a count of the number of rows
* that were changed. Use for UPDATE or DELETE SQL statements.
*
* @param sql The SQL statement to execute.
* @param bindArgs The arguments to bind, or null if none.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* @return The number of rows that were changed.
*
* @throws SQLiteException if an error occurs, such as a syntax error
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
public int executeForChangedRowCount(String sql, Object[] bindArgs,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
int changedRows = 0;
final int cookie = mRecentOperations.beginOperation("executeForChangedRowCount",
sql, bindArgs);
try {
final PreparedStatement statement = acquirePreparedStatement(sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
applyBlockGuardPolicy(statement);
attachCancellationSignal(cancellationSignal);
try {
changedRows = nativeExecuteForChangedRowCount(
mConnectionPtr, statement.mStatementPtr);
return changedRows;
} finally {
detachCancellationSignal(cancellationSignal);
}
} finally {
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
if (mRecentOperations.endOperationDeferLog(cookie)) {
mRecentOperations.logOperation(cookie, "changedRows=" + changedRows);
}
}
}
/**
* Executes a statement that returns the row id of the last row inserted
* by the statement. Use for INSERT SQL statements.
*
* @param sql The SQL statement to execute.
* @param bindArgs The arguments to bind, or null if none.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* @return The row id of the last row that was inserted, or 0 if none.
*
* @throws SQLiteException if an error occurs, such as a syntax error
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
public long executeForLastInsertedRowId(String sql, Object[] bindArgs,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
final int cookie = mRecentOperations.beginOperation("executeForLastInsertedRowId",
sql, bindArgs);
try {
final PreparedStatement statement = acquirePreparedStatement(sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
applyBlockGuardPolicy(statement);
attachCancellationSignal(cancellationSignal);
try {
return nativeExecuteForLastInsertedRowId(
mConnectionPtr, statement.mStatementPtr);
} finally {
detachCancellationSignal(cancellationSignal);
}
} finally {
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
mRecentOperations.endOperation(cookie);
}
}
/**
* Executes a statement and populates the specified {@link CursorWindow}
* with a range of results. Returns the number of rows that were counted
* during query execution.
*
* @param sql The SQL statement to execute.
* @param bindArgs The arguments to bind, or null if none.
* @param window The cursor window to clear and fill.
* @param startPos The start position for filling the window.
* @param requiredPos The position of a row that MUST be in the window.
* If it won't fit, then the query should discard part of what it filled
* so that it does. Must be greater than or equal to <code>startPos</code>.
* @param countAllRows True to count all rows that the query would return
* regagless of whether they fit in the window.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* @return The number of rows that were counted during query execution. Might
* not be all rows in the result set unless <code>countAllRows</code> is true.
*
* @throws SQLiteException if an error occurs, such as a syntax error
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
public int executeForCursorWindow(String sql,
Object[] bindArgs,
CursorWindow window,
int startPos,
int requiredPos,
boolean countAllRows,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
if (window == null) {
throw new IllegalArgumentException("window must not be null.");
}
window.acquireReference();
try {
int actualPos = -1;
int countedRows = -1;
int filledRows = -1;
final int cookie = mRecentOperations.beginOperation("executeForCursorWindow",
sql, bindArgs);
try {
final PreparedStatement statement = acquirePreparedStatement(sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
applyBlockGuardPolicy(statement);
attachCancellationSignal(cancellationSignal);
try {
final long result = nativeExecuteForCursorWindow(
mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,
startPos, requiredPos, countAllRows);
actualPos = (int)(result >> 32);
countedRows = (int)result;
filledRows = window.getNumRows();
window.setStartPosition(actualPos);
return countedRows;
} finally {
detachCancellationSignal(cancellationSignal);
}
} finally {
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
if (mRecentOperations.endOperationDeferLog(cookie)) {
mRecentOperations.logOperation(cookie, "window='" + window
+ "', startPos=" + startPos
+ ", actualPos=" + actualPos
+ ", filledRows=" + filledRows
+ ", countedRows=" + countedRows);
}
}
} finally {
window.releaseReference();
}
}
private PreparedStatement acquirePreparedStatement(String sql) {
PreparedStatement statement = mPreparedStatementCache.get(sql);
boolean skipCache = false;
if (statement != null) {
if (!statement.mInUse) {
return statement;
}
// The statement is already in the cache but is in use (this statement appears
// to be not only re-entrant but recursive!). So prepare a new copy of the
// statement but do not cache it.
skipCache = true;
}
final long statementPtr = nativePrepareStatement(mConnectionPtr, sql);
try {
final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
final int type = SQLiteStatementType.getSqlStatementType(sql);
final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly);
if (!skipCache && isCacheable(type)) {
mPreparedStatementCache.put(sql, statement);
statement.mInCache = true;
}
} catch (RuntimeException ex) {
// Finalize the statement if an exception occurred and we did not add
// it to the cache. If it is already in the cache, then leave it there.
if (statement == null || !statement.mInCache) {
nativeFinalizeStatement(mConnectionPtr, statementPtr);
}
throw ex;
}
statement.mInUse = true;
return statement;
}
private void releasePreparedStatement(PreparedStatement statement) {
statement.mInUse = false;
if (statement.mInCache) {
try {
nativeResetStatementAndClearBindings(mConnectionPtr, statement.mStatementPtr);
} catch (SQLiteException ex) {
// The statement could not be reset due to an error. Remove it from the cache.
// When remove() is called, the cache will invoke its entryRemoved() callback,
// which will in turn call finalizePreparedStatement() to finalize and
// recycle the statement.
if (DEBUG) {
Log.d(TAG, "Could not reset prepared statement due to an exception. "
+ "Removing it from the cache. SQL: "
+ trimSqlForDisplay(statement.mSql), ex);
}
mPreparedStatementCache.remove(statement.mSql);
}
} else {
finalizePreparedStatement(statement);
}
}
private void finalizePreparedStatement(PreparedStatement statement) {
nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr);
recyclePreparedStatement(statement);
}
private void attachCancellationSignal(CancellationSignal cancellationSignal) {
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
mCancellationSignalAttachCount += 1;
if (mCancellationSignalAttachCount == 1) {
// Reset cancellation flag before executing the statement.