This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
yap-6.3/packages/myddas/sqlite3/Android/jni/sqlite/android_database_SQLiteConnection.cpp
Vitor Santos Costa d680fa0755 missing
2016-04-05 08:18:09 +01:00

1015 lines
36 KiB
C++

/*
* 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 to support SQLite extensions by the SQLite developers:
** sqlite-dev@sqlite.org.
*/
#define LOG_TAG "SQLiteConnection"
#include "ALog-priv.h"
#include <JNIHelp.h>
#include <jni.h>
#include <assert.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#if 0
#include <androidfw/CursorWindow.h>
#endif
#include <sqlite3.h>
#if 0
#include <sqlite3_android.h>
#endif
#include "android_database_SQLiteCommon.h"
#include <string>
// Set to 1 to use UTF16 storage for localized indexes.
#define UTF16_STORAGE 0
namespace android {
/* Busy timeout in milliseconds.
* If another connection (possibly in another process) has the database locked
* for
* longer than this amount of time then SQLite will generate a SQLITE_BUSY
* error.
* The SQLITE_BUSY error is then raised as a SQLiteDatabaseLockedException.
*
* In ordinary usage, busy timeouts are quite rare. Most databases only ever
* have a single open connection at a time unless they are using WAL. When
* using
* WAL, a timeout could occur if one connection is busy performing an
* auto-checkpoint
* operation. The busy timeout needs to be long enough to tolerate slow I/O
* write
* operations but not so long as to cause the application to hang indefinitely
* if
* there is a problem acquiring a database lock.
*/
static const int BUSY_TIMEOUT_MS = 2500;
static JavaVM *gpJavaVM = 0;
static struct {
jfieldID name;
jfieldID numArgs;
jmethodID dispatchCallback;
} gSQLiteCustomFunctionClassInfo;
static struct { jclass clazz; } gStringClassInfo;
struct SQLiteConnection {
// Open flags.
// Must be kept in sync with the constants defined in SQLiteDatabase.java.
enum {
OPEN_READWRITE = 0x00000000,
OPEN_READONLY = 0x00000001,
OPEN_READ_MASK = 0x00000001,
NO_LOCALIZED_COLLATORS = 0x00000010,
CREATE_IF_NECESSARY = 0x10000000,
};
sqlite3 *const db;
const int openFlags;
std::string path;
std::string label;
volatile bool canceled;
SQLiteConnection(sqlite3 *db, int openFlags, const std::string &path,
const std::string &label)
: db(db), openFlags(openFlags), path(path), label(label),
canceled(false) {}
};
// Called each time a statement begins execution, when tracing is enabled.
static void sqliteTraceCallback(void *data, const char *sql) {
SQLiteConnection *connection = static_cast<SQLiteConnection *>(data);
ALOG(LOG_VERBOSE, SQLITE_TRACE_TAG, "%s: \"%s\"\n", connection->label.c_str(),
sql);
}
// Called each time a statement finishes execution, when profiling is enabled.
static void sqliteProfileCallback(void *data, const char *sql,
sqlite3_uint64 tm) {
SQLiteConnection *connection = static_cast<SQLiteConnection *>(data);
ALOG(LOG_VERBOSE, SQLITE_PROFILE_TAG, "%s: \"%s\" took %0.3f ms\n",
connection->label.c_str(), sql, tm * 0.000001f);
}
// Called after each SQLite VM instruction when cancelation is enabled.
static int sqliteProgressHandlerCallback(void *data) {
SQLiteConnection *connection = static_cast<SQLiteConnection *>(data);
return connection->canceled;
}
/*
** This function is a collation sequence callback equivalent to the built-in
** BINARY sequence.
**
** Stock Android uses a modified version of sqlite3.c that calls out to a module
** named "sqlite3_android" to add extra built-in collations and functions to
** all database handles. Specifically, collation sequence "LOCALIZED". For now,
** this module does not include sqlite3_android (since it is difficult to build
** with the NDK only). Instead, this function is registered as "LOCALIZED" for
*all
** new database handles.
*/
static int coll_localized(void *not_used, int nKey1, const void *pKey1,
int nKey2, const void *pKey2) {
int rc, n;
n = nKey1 < nKey2 ? nKey1 : nKey2;
rc = memcmp(pKey1, pKey2, n);
if (rc == 0) {
rc = nKey1 - nKey2;
}
return rc;
}
static jlong nativeOpen(JNIEnv *env, jclass clazz, jstring pathStr,
jint openFlags, jstring labelStr, jboolean enableTrace,
jboolean enableProfile) {
int sqliteFlags;
if (openFlags & SQLiteConnection::CREATE_IF_NECESSARY) {
sqliteFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
} else if (openFlags & SQLiteConnection::OPEN_READONLY) {
sqliteFlags = SQLITE_OPEN_READONLY;
} else {
sqliteFlags = SQLITE_OPEN_READWRITE;
}
const char *pathChars = env->GetStringUTFChars(pathStr, NULL);
std::string path(pathChars);
env->ReleaseStringUTFChars(pathStr, pathChars);
const char *labelChars = env->GetStringUTFChars(labelStr, NULL);
std::string label(labelChars);
env->ReleaseStringUTFChars(labelStr, labelChars);
sqlite3 *db;
int err = sqlite3_open_v2(path.c_str(), &db, sqliteFlags, NULL);
if (err != SQLITE_OK) {
throw_sqlite3_exception_errcode(env, err, "Could not open database");
return 0;
}
err =
sqlite3_create_collation(db, "localized", SQLITE_UTF8, 0, coll_localized);
if (err != SQLITE_OK) {
throw_sqlite3_exception_errcode(env, err, "Could not register collation");
sqlite3_close(db);
return 0;
}
// Check that the database is really read/write when that is what we asked
// for.
if ((sqliteFlags & SQLITE_OPEN_READWRITE) && sqlite3_db_readonly(db, NULL)) {
throw_sqlite3_exception(env, db,
"Could not open the database in read/write mode.");
sqlite3_close(db);
return 0;
}
// Set the default busy handler to retry automatically before returning
// SQLITE_BUSY.
err = sqlite3_busy_timeout(db, BUSY_TIMEOUT_MS);
if (err != SQLITE_OK) {
throw_sqlite3_exception(env, db, "Could not set busy timeout");
sqlite3_close(db);
return 0;
}
// Register custom Android functions.
#if 0
err = register_android_functions(db, UTF16_STORAGE);
if (err) {
throw_sqlite3_exception(env, db, "Could not register Android SQL functions.");
sqlite3_close(db);
return 0;
}
#endif
// Create wrapper object.
SQLiteConnection *connection =
new SQLiteConnection(db, openFlags, path, label);
// Enable tracing and profiling if requested.
if (enableTrace) {
sqlite3_trace(db, &sqliteTraceCallback, connection);
}
if (enableProfile) {
sqlite3_profile(db, &sqliteProfileCallback, connection);
}
ALOGV("Opened connection %p with label '%s'", db, label.c_str());
return reinterpret_cast<jlong>(connection);
}
static void nativeClose(JNIEnv *env, jclass clazz, jlong connectionPtr) {
SQLiteConnection *connection =
reinterpret_cast<SQLiteConnection *>(connectionPtr);
if (connection) {
ALOGV("Closing connection %p", connection->db);
int err = sqlite3_close(connection->db);
if (err != SQLITE_OK) {
// This can happen if sub-objects aren't closed first. Make sure the
// caller knows.
ALOGE("sqlite3_close(%p) failed: %d", connection->db, err);
throw_sqlite3_exception(env, connection->db, "Count not close db.");
return;
}
delete connection;
}
}
// Called each time a custom function is evaluated.
static void sqliteCustomFunctionCallback(sqlite3_context *context, int argc,
sqlite3_value **argv) {
JNIEnv *env = 0;
gpJavaVM->GetEnv((void **)&env, JNI_VERSION_1_4);
// Get the callback function object.
// Create a new local reference to it in case the callback tries to do
// something
// dumb like unregister the function (thereby destroying the global ref) while
// it is running.
jobject functionObjGlobal =
reinterpret_cast<jobject>(sqlite3_user_data(context));
jobject functionObj = env->NewLocalRef(functionObjGlobal);
jobjectArray argsArray =
env->NewObjectArray(argc, gStringClassInfo.clazz, NULL);
if (argsArray) {
for (int i = 0; i < argc; i++) {
const jchar *arg =
static_cast<const jchar *>(sqlite3_value_text16(argv[i]));
if (!arg) {
ALOGW("NULL argument in custom_function_callback. This should not "
"happen.");
} else {
size_t argLen = sqlite3_value_bytes16(argv[i]) / sizeof(jchar);
jstring argStr = env->NewString(arg, argLen);
if (!argStr) {
goto error; // out of memory error
}
env->SetObjectArrayElement(argsArray, i, argStr);
env->DeleteLocalRef(argStr);
}
}
// TODO: Support functions that return values.
env->CallVoidMethod(functionObj,
gSQLiteCustomFunctionClassInfo.dispatchCallback,
argsArray);
error:
env->DeleteLocalRef(argsArray);
}
env->DeleteLocalRef(functionObj);
if (env->ExceptionCheck()) {
ALOGE("An exception was thrown by custom SQLite function.");
/* LOGE_EX(env); */
env->ExceptionClear();
}
}
// Called when a custom function is destroyed.
static void sqliteCustomFunctionDestructor(void *data) {
jobject functionObjGlobal = reinterpret_cast<jobject>(data);
JNIEnv *env = 0;
gpJavaVM->GetEnv((void **)&env, JNI_VERSION_1_4);
env->DeleteGlobalRef(functionObjGlobal);
}
static void nativeRegisterCustomFunction(JNIEnv *env, jclass clazz,
jlong connectionPtr,
jobject functionObj) {
SQLiteConnection *connection =
reinterpret_cast<SQLiteConnection *>(connectionPtr);
jstring nameStr = jstring(
env->GetObjectField(functionObj, gSQLiteCustomFunctionClassInfo.name));
jint numArgs =
env->GetIntField(functionObj, gSQLiteCustomFunctionClassInfo.numArgs);
jobject functionObjGlobal = env->NewGlobalRef(functionObj);
const char *name = env->GetStringUTFChars(nameStr, NULL);
int err =
sqlite3_create_function_v2(connection->db, name, numArgs, SQLITE_UTF16,
reinterpret_cast<void *>(functionObjGlobal),
&sqliteCustomFunctionCallback, NULL, NULL,
&sqliteCustomFunctionDestructor);
env->ReleaseStringUTFChars(nameStr, name);
if (err != SQLITE_OK) {
ALOGE("sqlite3_create_function returned %d", err);
env->DeleteGlobalRef(functionObjGlobal);
throw_sqlite3_exception(env, connection->db);
return;
}
}
static void nativeRegisterLocalizedCollators(JNIEnv *env, jclass clazz,
jlong connectionPtr,
jstring localeStr) {
#if 0
SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
const char* locale = env->GetStringUTFChars(localeStr, NULL);
int err = register_localized_collators(connection->db, locale, UTF16_STORAGE);
env->ReleaseStringUTFChars(localeStr, locale);
if (err != SQLITE_OK) {
throw_sqlite3_exception(env, connection->db);
}
#endif
}
static jlong nativePrepareStatement(JNIEnv *env, jclass clazz,
jlong connectionPtr, jstring sqlString) {
SQLiteConnection *connection =
reinterpret_cast<SQLiteConnection *>(connectionPtr);
jsize sqlLength = env->GetStringLength(sqlString);
const jchar *sql = env->GetStringCritical(sqlString, NULL);
sqlite3_stmt *statement;
int err = sqlite3_prepare16_v2(connection->db, sql, sqlLength * sizeof(jchar),
&statement, NULL);
env->ReleaseStringCritical(sqlString, sql);
if (err != SQLITE_OK) {
// Error messages like 'near ")": syntax error' are not
// always helpful enough, so construct an error string that
// includes the query itself.
const char *query = env->GetStringUTFChars(sqlString, NULL);
char *message = (char *)malloc(strlen(query) + 50);
if (message) {
strcpy(message, ", while compiling: "); // less than 50 chars
strcat(message, query);
}
env->ReleaseStringUTFChars(sqlString, query);
throw_sqlite3_exception(env, connection->db, message);
free(message);
return 0;
}
ALOGV("Prepared statement %p on connection %p", statement, connection->db);
return reinterpret_cast<jlong>(statement);
}
static void nativeFinalizeStatement(JNIEnv *env, jclass clazz,
jlong connectionPtr, jlong statementPtr) {
SQLiteConnection *connection =
reinterpret_cast<SQLiteConnection *>(connectionPtr);
sqlite3_stmt *statement = reinterpret_cast<sqlite3_stmt *>(statementPtr);
// We ignore the result of sqlite3_finalize because it is really telling us
// about
// whether any errors occurred while executing the statement. The statement
// itself
// is always finalized regardless.
ALOGV("Finalized statement %p on connection %p", statement, connection->db);
sqlite3_finalize(statement);
}
static jint nativeGetParameterCount(JNIEnv *env, jclass clazz,
jlong connectionPtr, jlong statementPtr) {
// SQLiteConnection *connection =
// reinterpret_cast<SQLiteConnection *>(connectionPtr);
sqlite3_stmt *statement = reinterpret_cast<sqlite3_stmt *>(statementPtr);
return sqlite3_bind_parameter_count(statement);
}
static jboolean nativeIsReadOnly(JNIEnv *env, jclass clazz, jlong connectionPtr,
jlong statementPtr) {
// SQLiteConnection *connection =
// reinterpret_cast<SQLiteConnection *>(connectionPtr);
sqlite3_stmt *statement = reinterpret_cast<sqlite3_stmt *>(statementPtr);
return sqlite3_stmt_readonly(statement) != 0;
}
static jint nativeGetColumnCount(JNIEnv *env, jclass clazz, jlong connectionPtr,
jlong statementPtr) {
// SQLiteConnection *connection =
// reinterpret_cast<SQLiteConnection *>(connectionPtr);
sqlite3_stmt *statement = reinterpret_cast<sqlite3_stmt *>(statementPtr);
return sqlite3_column_count(statement);
}
static jstring nativeGetColumnName(JNIEnv *env, jclass clazz,
jlong connectionPtr, jlong statementPtr,
jint index) {
// SQLiteConnection *connection =
// reinterpret_cast<SQLiteConnection *>(connectionPtr);
sqlite3_stmt *statement = reinterpret_cast<sqlite3_stmt *>(statementPtr);
const jchar *name =
static_cast<const jchar *>(sqlite3_column_name16(statement, index));
if (name) {
size_t length = 0;
while (name[length]) {
length += 1;
}
return env->NewString(name, length);
}
return NULL;
}
static void nativeBindNull(JNIEnv *env, jclass clazz, jlong connectionPtr,
jlong statementPtr, jint index) {
SQLiteConnection *connection =
reinterpret_cast<SQLiteConnection *>(connectionPtr);
sqlite3_stmt *statement = reinterpret_cast<sqlite3_stmt *>(statementPtr);
int err = sqlite3_bind_null(statement, index);
if (err != SQLITE_OK) {
throw_sqlite3_exception(env, connection->db, NULL);
}
}
static void nativeBindLong(JNIEnv *env, jclass clazz, jlong connectionPtr,
jlong statementPtr, jint index, jlong value) {
SQLiteConnection *connection =
reinterpret_cast<SQLiteConnection *>(connectionPtr);
sqlite3_stmt *statement = reinterpret_cast<sqlite3_stmt *>(statementPtr);
int err = sqlite3_bind_int64(statement, index, value);
if (err != SQLITE_OK) {
throw_sqlite3_exception(env, connection->db, NULL);
}
}
static void nativeBindDouble(JNIEnv *env, jclass clazz, jlong connectionPtr,
jlong statementPtr, jint index, jdouble value) {
SQLiteConnection *connection =
reinterpret_cast<SQLiteConnection *>(connectionPtr);
sqlite3_stmt *statement = reinterpret_cast<sqlite3_stmt *>(statementPtr);
int err = sqlite3_bind_double(statement, index, value);
if (err != SQLITE_OK) {
throw_sqlite3_exception(env, connection->db, NULL);
}
}
static void nativeBindString(JNIEnv *env, jclass clazz, jlong connectionPtr,
jlong statementPtr, jint index,
jstring valueString) {
SQLiteConnection *connection =
reinterpret_cast<SQLiteConnection *>(connectionPtr);
sqlite3_stmt *statement = reinterpret_cast<sqlite3_stmt *>(statementPtr);
jsize valueLength = env->GetStringLength(valueString);
const jchar *value = env->GetStringCritical(valueString, NULL);
int err = sqlite3_bind_text16(statement, index, value,
valueLength * sizeof(jchar), SQLITE_TRANSIENT);
env->ReleaseStringCritical(valueString, value);
if (err != SQLITE_OK) {
throw_sqlite3_exception(env, connection->db, NULL);
}
}
static void nativeBindBlob(JNIEnv *env, jclass clazz, jlong connectionPtr,
jlong statementPtr, jint index,
jbyteArray valueArray) {
SQLiteConnection *connection =
reinterpret_cast<SQLiteConnection *>(connectionPtr);
sqlite3_stmt *statement = reinterpret_cast<sqlite3_stmt *>(statementPtr);
jsize valueLength = env->GetArrayLength(valueArray);
jbyte *value =
static_cast<jbyte *>(env->GetPrimitiveArrayCritical(valueArray, NULL));
int err =
sqlite3_bind_blob(statement, index, value, valueLength, SQLITE_TRANSIENT);
env->ReleasePrimitiveArrayCritical(valueArray, value, JNI_ABORT);
if (err != SQLITE_OK) {
throw_sqlite3_exception(env, connection->db, NULL);
}
}
static void nativeResetStatementAndClearBindings(JNIEnv *env, jclass clazz,
jlong connectionPtr,
jlong statementPtr) {
SQLiteConnection *connection =
reinterpret_cast<SQLiteConnection *>(connectionPtr);
sqlite3_stmt *statement = reinterpret_cast<sqlite3_stmt *>(statementPtr);
int err = sqlite3_reset(statement);
if (err == SQLITE_OK) {
err = sqlite3_clear_bindings(statement);
}
if (err != SQLITE_OK) {
throw_sqlite3_exception(env, connection->db, NULL);
}
}
static int executeNonQuery(JNIEnv *env, SQLiteConnection *connection,
sqlite3_stmt *statement) {
int err = sqlite3_step(statement);
if (err == SQLITE_ROW) {
throw_sqlite3_exception(env, "Queries can be performed using "
"SQLiteDatabase query or rawQuery methods "
"only.");
} else if (err != SQLITE_DONE) {
throw_sqlite3_exception(env, connection->db);
}
return err;
}
static void nativeExecute(JNIEnv *env, jclass clazz, jlong connectionPtr,
jlong statementPtr) {
SQLiteConnection *connection =
reinterpret_cast<SQLiteConnection *>(connectionPtr);
sqlite3_stmt *statement = reinterpret_cast<sqlite3_stmt *>(statementPtr);
executeNonQuery(env, connection, statement);
}
static jint nativeExecuteForChangedRowCount(JNIEnv *env, jclass clazz,
jlong connectionPtr,
jlong statementPtr) {
SQLiteConnection *connection =
reinterpret_cast<SQLiteConnection *>(connectionPtr);
sqlite3_stmt *statement = reinterpret_cast<sqlite3_stmt *>(statementPtr);
int err = executeNonQuery(env, connection, statement);
return err == SQLITE_DONE ? sqlite3_changes(connection->db) : -1;
}
static jlong nativeExecuteForLastInsertedRowId(JNIEnv *env, jclass clazz,
jlong connectionPtr,
jlong statementPtr) {
SQLiteConnection *connection =
reinterpret_cast<SQLiteConnection *>(connectionPtr);
sqlite3_stmt *statement = reinterpret_cast<sqlite3_stmt *>(statementPtr);
int err = executeNonQuery(env, connection, statement);
return err == SQLITE_DONE && sqlite3_changes(connection->db) > 0
? sqlite3_last_insert_rowid(connection->db)
: -1;
}
static int executeOneRowQuery(JNIEnv *env, SQLiteConnection *connection,
sqlite3_stmt *statement) {
int err = sqlite3_step(statement);
if (err != SQLITE_ROW) {
throw_sqlite3_exception(env, connection->db);
}
return err;
}
static jlong nativeExecuteForLong(JNIEnv *env, jclass clazz,
jlong connectionPtr, jlong statementPtr) {
SQLiteConnection *connection =
reinterpret_cast<SQLiteConnection *>(connectionPtr);
sqlite3_stmt *statement = reinterpret_cast<sqlite3_stmt *>(statementPtr);
int err = executeOneRowQuery(env, connection, statement);
if (err == SQLITE_ROW && sqlite3_column_count(statement) >= 1) {
return sqlite3_column_int64(statement, 0);
}
return -1;
}
static jstring nativeExecuteForString(JNIEnv *env, jclass clazz,
jlong connectionPtr, jlong statementPtr) {
SQLiteConnection *connection =
reinterpret_cast<SQLiteConnection *>(connectionPtr);
sqlite3_stmt *statement = reinterpret_cast<sqlite3_stmt *>(statementPtr);
int err = executeOneRowQuery(env, connection, statement);
if (err == SQLITE_ROW && sqlite3_column_count(statement) >= 1) {
const jchar *text =
static_cast<const jchar *>(sqlite3_column_text16(statement, 0));
if (text) {
size_t length = sqlite3_column_bytes16(statement, 0) / sizeof(jchar);
return env->NewString(text, length);
}
}
return NULL;
}
static int createAshmemRegionWithData(JNIEnv *env, const void *data,
size_t length) {
#if 0
int error = 0;
int fd = ashmem_create_region(NULL, length);
if (fd < 0) {
error = errno;
ALOGE("ashmem_create_region failed: %s", strerror(error));
} else {
if (length > 0) {
void* ptr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
error = errno;
ALOGE("mmap failed: %s", strerror(error));
} else {
memcpy(ptr, data, length);
munmap(ptr, length);
}
}
if (!error) {
if (ashmem_set_prot_region(fd, PROT_READ) < 0) {
error = errno;
ALOGE("ashmem_set_prot_region failed: %s", strerror(errno));
} else {
return fd;
}
}
close(fd);
}
#endif
jniThrowIOException(env, -1);
return -1;
}
static jint nativeExecuteForBlobFileDescriptor(JNIEnv *env, jclass clazz,
jlong connectionPtr,
jlong statementPtr) {
SQLiteConnection *connection =
reinterpret_cast<SQLiteConnection *>(connectionPtr);
sqlite3_stmt *statement = reinterpret_cast<sqlite3_stmt *>(statementPtr);
int err = executeOneRowQuery(env, connection, statement);
if (err == SQLITE_ROW && sqlite3_column_count(statement) >= 1) {
const void *blob = sqlite3_column_blob(statement, 0);
if (blob) {
int length = sqlite3_column_bytes(statement, 0);
if (length >= 0) {
return createAshmemRegionWithData(env, blob, length);
}
}
}
return -1;
}
/*
** Note: The following symbols must be in the same order as the corresponding
** elements in the aMethod[] array in function nativeExecuteForCursorWindow().
*/
enum CWMethodNames {
CW_CLEAR = 0,
CW_SETNUMCOLUMNS = 1,
CW_ALLOCROW = 2,
CW_FREELASTROW = 3,
CW_PUTNULL = 4,
CW_PUTLONG = 5,
CW_PUTDOUBLE = 6,
CW_PUTSTRING = 7,
CW_PUTBLOB = 8
};
/*
** An instance of this structure represents a single CursorWindow java method.
*/
struct CWMethod {
jmethodID id; /* Method id */
const char *zName; /* Method name */
const char *zSig; /* Method JNI signature */
};
/*
** Append the contents of the row that SQL statement pStmt currently points to
** to the CursorWindow object passed as the second argument. The CursorWindow
** currently contains iRow rows. Return true on success or false if an error
** occurs.
*/
static jboolean copyRowToWindow(JNIEnv *pEnv, jobject win, int iRow,
sqlite3_stmt *pStmt, CWMethod *aMethod) {
int nCol = sqlite3_column_count(pStmt);
int i;
jboolean bOk;
bOk = pEnv->CallBooleanMethod(win, aMethod[CW_ALLOCROW].id);
for (i = 0; bOk && i < nCol; i++) {
switch (sqlite3_column_type(pStmt, i)) {
case SQLITE_NULL: {
bOk = pEnv->CallBooleanMethod(win, aMethod[CW_PUTNULL].id, iRow, i);
break;
}
case SQLITE_INTEGER: {
jlong val = sqlite3_column_int64(pStmt, i);
bOk = pEnv->CallBooleanMethod(win, aMethod[CW_PUTLONG].id, val, iRow, i);
break;
}
case SQLITE_FLOAT: {
jdouble val = sqlite3_column_double(pStmt, i);
bOk =
pEnv->CallBooleanMethod(win, aMethod[CW_PUTDOUBLE].id, val, iRow, i);
break;
}
case SQLITE_TEXT: {
jchar *pStr = (jchar *)sqlite3_column_text16(pStmt, i);
int nStr = sqlite3_column_bytes16(pStmt, i) / sizeof(jchar);
jstring val = pEnv->NewString(pStr, nStr);
bOk =
pEnv->CallBooleanMethod(win, aMethod[CW_PUTSTRING].id, val, iRow, i);
pEnv->DeleteLocalRef(val);
break;
}
default: {
assert(sqlite3_column_type(pStmt, i) == SQLITE_BLOB);
const jbyte *p = (const jbyte *)sqlite3_column_blob(pStmt, i);
int n = sqlite3_column_bytes(pStmt, i);
jbyteArray val = pEnv->NewByteArray(n);
pEnv->SetByteArrayRegion(val, 0, n, p);
bOk = pEnv->CallBooleanMethod(win, aMethod[CW_PUTBLOB].id, val, iRow, i);
pEnv->DeleteLocalRef(val);
break;
}
}
if (bOk == 0) {
pEnv->CallVoidMethod(win, aMethod[CW_FREELASTROW].id);
}
}
return bOk;
}
static jboolean setWindowNumColumns(JNIEnv *pEnv, jobject win,
sqlite3_stmt *pStmt, CWMethod *aMethod) {
int nCol;
pEnv->CallVoidMethod(win, aMethod[CW_CLEAR].id);
nCol = sqlite3_column_count(pStmt);
return pEnv->CallBooleanMethod(win, aMethod[CW_SETNUMCOLUMNS].id, (jint)nCol);
}
/*
** This method has been rewritten for org.sqlite.database.*. The original
** android implementation used the C++ interface to populate a CursorWindow
** object. Since the NDK does not export this interface, we invoke the Java
** interface using standard JNI methods to do the same thing.
**
** This function executes the SQLite statement object passed as the 4th
** argument and copies one or more returned rows into the CursorWindow
** object passed as the 5th argument. The set of rows copied into the
** CursorWindow is always contiguous.
**
** The only row that *must* be copied into the CursorWindow is row
** iRowRequired. Ideally, all rows from iRowStart through to the end
** of the query are copied into the CursorWindow. If this is not possible
** (CursorWindow objects have a finite capacity), some compromise position
** is found (see comments embedded in the code below for details).
**
** The return value is a 64-bit integer calculated as follows:
**
** (iStart << 32) | nRow
**
** where iStart is the index of the first row copied into the CursorWindow.
** If the countAllRows argument is true, nRow is the total number of rows
** returned by the query. Otherwise, nRow is one greater than the index of
** the last row copied into the CursorWindow.
*/
static jlong nativeExecuteForCursorWindow(
JNIEnv *pEnv, jclass clazz,
jlong connectionPtr, /* Pointer to SQLiteConnection C++ object */
jlong statementPtr, /* Pointer to sqlite3_stmt object */
jobject win, /* The CursorWindow object to populate */
jint startPos, /* First row to add (advisory) */
jint iRowRequired, /* Required row */
jboolean countAllRows) {
// SQLiteConnection *pConnection =
// reinterpret_cast<SQLiteConnection *>(connectionPtr);
sqlite3_stmt *pStmt = reinterpret_cast<sqlite3_stmt *>(statementPtr);
CWMethod aMethod[] = {
{0, "clear", "()V"}, {0, "setNumColumns", "(I)Z"},
{0, "allocRow", "()Z"}, {0, "freeLastRow", "()V"},
{0, "putNull", "(II)Z"}, {0, "putLong", "(JII)Z"},
{0, "putDouble", "(DII)Z"}, {0, "putString", "(Ljava/lang/String;II)Z"},
{0, "putBlob", "([BII)Z"},
};
jclass cls; /* Class android.database.CursorWindow */
unsigned int i; /* Iterator variable */
// int nCol; /* Number of columns returned by pStmt */
int nRow;
jboolean bOk;
int iStart; /* First row copied to CursorWindow */
/* Locate all required CursorWindow methods. */
cls = pEnv->FindClass("android/database/CursorWindow");
for (i = 0; i < (sizeof(aMethod) / sizeof(struct CWMethod)); i++) {
aMethod[i].id = pEnv->GetMethodID(cls, aMethod[i].zName, aMethod[i].zSig);
if (aMethod[i].id == NULL) {
jniThrowExceptionFmt(pEnv, "java/lang/Exception",
"Failed to find method CursorWindow.%s()",
aMethod[i].zName);
return 0;
}
}
/* Set the number of columns in the window */
bOk = setWindowNumColumns(pEnv, win, pStmt, aMethod);
if (bOk == 0)
return 0;
nRow = 0;
iStart = startPos;
while (sqlite3_step(pStmt) == SQLITE_ROW) {
/* Only copy in rows that occur at or after row index iStart. */
if (nRow >= iStart && bOk) {
bOk = copyRowToWindow(pEnv, win, (nRow - iStart), pStmt, aMethod);
if (bOk == 0) {
/* The CursorWindow object ran out of memory. If row iRowRequired was
** not successfully added before this happened, clear the CursorWindow
** and try to add the current row again. */
if (nRow <= iRowRequired) {
bOk = setWindowNumColumns(pEnv, win, pStmt, aMethod);
if (bOk == 0) {
sqlite3_reset(pStmt);
return 0;
}
iStart = nRow;
bOk = copyRowToWindow(pEnv, win, (nRow - iStart), pStmt, aMethod);
}
/* If the CursorWindow is still full and the countAllRows flag is not
** set, break out of the loop here. If countAllRows is set, continue
** so as to set variable nRow correctly. */
if (bOk == 0 && countAllRows == 0)
break;
}
}
nRow++;
}
/* Finalize the statement. If this indicates an error occurred, throw an
** SQLiteException exception. */
int rc = sqlite3_reset(pStmt);
if (rc != SQLITE_OK) {
throw_sqlite3_exception(pEnv, sqlite3_db_handle(pStmt));
return 0;
}
jlong lRet = jlong(iStart) << 32 | jlong(nRow);
return lRet;
}
static jint nativeGetDbLookaside(JNIEnv *env, jobject clazz,
jlong connectionPtr) {
SQLiteConnection *connection =
reinterpret_cast<SQLiteConnection *>(connectionPtr);
int cur = -1;
int unused;
sqlite3_db_status(connection->db, SQLITE_DBSTATUS_LOOKASIDE_USED, &cur,
&unused, 0);
return cur;
}
static void nativeCancel(JNIEnv *env, jobject clazz, jlong connectionPtr) {
SQLiteConnection *connection =
reinterpret_cast<SQLiteConnection *>(connectionPtr);
connection->canceled = true;
}
static void nativeResetCancel(JNIEnv *env, jobject clazz, jlong connectionPtr,
jboolean cancelable) {
SQLiteConnection *connection =
reinterpret_cast<SQLiteConnection *>(connectionPtr);
connection->canceled = false;
if (cancelable) {
sqlite3_progress_handler(connection->db, 4, sqliteProgressHandlerCallback,
connection);
} else {
sqlite3_progress_handler(connection->db, 0, NULL, NULL);
}
}
static jboolean nativeHasCodec(JNIEnv *env, jobject clazz) {
#ifdef SQLITE_HAS_CODEC
return true;
#else
return false;
#endif
}
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"nativeOpen", "(Ljava/lang/String;ILjava/lang/String;ZZ)J",
(void *)nativeOpen},
{"nativeClose", "(J)V", (void *)nativeClose},
{"nativeRegisterCustomFunction",
"(JLorg/sqlite/database/sqlite/SQLiteCustomFunction;)V",
(void *)nativeRegisterCustomFunction},
{"nativeRegisterLocalizedCollators", "(JLjava/lang/String;)V",
(void *)nativeRegisterLocalizedCollators},
{"nativePrepareStatement", "(JLjava/lang/String;)J",
(void *)nativePrepareStatement},
{"nativeFinalizeStatement", "(JJ)V", (void *)nativeFinalizeStatement},
{"nativeGetParameterCount", "(JJ)I", (void *)nativeGetParameterCount},
{"nativeIsReadOnly", "(JJ)Z", (void *)nativeIsReadOnly},
{"nativeGetColumnCount", "(JJ)I", (void *)nativeGetColumnCount},
{"nativeGetColumnName", "(JJI)Ljava/lang/String;",
(void *)nativeGetColumnName},
{"nativeBindNull", "(JJI)V", (void *)nativeBindNull},
{"nativeBindLong", "(JJIJ)V", (void *)nativeBindLong},
{"nativeBindDouble", "(JJID)V", (void *)nativeBindDouble},
{"nativeBindString", "(JJILjava/lang/String;)V", (void *)nativeBindString},
{"nativeBindBlob", "(JJI[B)V", (void *)nativeBindBlob},
{"nativeResetStatementAndClearBindings", "(JJ)V",
(void *)nativeResetStatementAndClearBindings},
{"nativeExecute", "(JJ)V", (void *)nativeExecute},
{"nativeExecuteForLong", "(JJ)J", (void *)nativeExecuteForLong},
{"nativeExecuteForString", "(JJ)Ljava/lang/String;",
(void *)nativeExecuteForString},
{"nativeExecuteForBlobFileDescriptor", "(JJ)I",
(void *)nativeExecuteForBlobFileDescriptor},
{"nativeExecuteForChangedRowCount", "(JJ)I",
(void *)nativeExecuteForChangedRowCount},
{"nativeExecuteForLastInsertedRowId", "(JJ)J",
(void *)nativeExecuteForLastInsertedRowId},
{"nativeExecuteForCursorWindow", "(JJLandroid/database/CursorWindow;IIZ)J",
(void *)nativeExecuteForCursorWindow},
{"nativeGetDbLookaside", "(J)I", (void *)nativeGetDbLookaside},
{"nativeCancel", "(J)V", (void *)nativeCancel},
{"nativeResetCancel", "(JZ)V", (void *)nativeResetCancel},
{"nativeHasCodec", "()Z", (void *)nativeHasCodec},
};
#define FIND_CLASS(var, className) \
var = env->FindClass(className); \
LOG_FATAL_IF(!var, "Unable to find class " className);
#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \
var = env->GetMethodID(clazz, methodName, fieldDescriptor); \
LOG_FATAL_IF(!var, "Unable to find method" methodName);
#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
LOG_FATAL_IF(!var, "Unable to find field " fieldName);
int register_android_database_SQLiteConnection(JNIEnv *env) {
jclass clazz;
FIND_CLASS(clazz, "org/sqlite/database/sqlite/SQLiteCustomFunction");
GET_FIELD_ID(gSQLiteCustomFunctionClassInfo.name, clazz, "name",
"Ljava/lang/String;");
GET_FIELD_ID(gSQLiteCustomFunctionClassInfo.numArgs, clazz, "numArgs", "I");
GET_METHOD_ID(gSQLiteCustomFunctionClassInfo.dispatchCallback, clazz,
"dispatchCallback", "([Ljava/lang/String;)V");
FIND_CLASS(clazz, "java/lang/String");
gStringClassInfo.clazz = jclass(env->NewGlobalRef(clazz));
return jniRegisterNativeMethods(env,
"org/sqlite/database/sqlite/SQLiteConnection",
sMethods, NELEM(sMethods));
}
extern int register_android_database_SQLiteGlobal(JNIEnv *env);
extern int register_android_database_SQLiteDebug(JNIEnv *env);
} // namespace android
extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = 0;
android::gpJavaVM = vm;
vm->GetEnv((void **)&env, JNI_VERSION_1_4);
android::register_android_database_SQLiteConnection(env);
android::register_android_database_SQLiteDebug(env);
android::register_android_database_SQLiteGlobal(env);
return JNI_VERSION_1_4;
}