extern "C" NS_EXPORT jobject JNICALL Java_org_mozilla_gecko_sqlite_SQLiteBridge_sqliteCall(JNIEnv* jenv, jclass, jstring jDb, jstring jQuery, jobjectArray jParams, jlongArray jQueryRes) { JNI_Setup(jenv); int rc; jobject jCursor = NULL; const char* dbPath; sqlite3 *db; char* errorMsg; dbPath = jenv->GetStringUTFChars(jDb, NULL); rc = f_sqlite3_open(dbPath, &db); jenv->ReleaseStringUTFChars(jDb, dbPath); if (rc != SQLITE_OK) { asprintf(&errorMsg, "Can't open database: %s\n", f_sqlite3_errmsg(db)); LOG("Error in SQLiteBridge: %s\n", errorMsg); JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException", errorMsg); free(errorMsg); } else { jCursor = sqliteInternalCall(jenv, db, jQuery, jParams, jQueryRes); } f_sqlite3_close(db); return jCursor; }
extern "C" NS_EXPORT jobject MOZ_JNICALL Java_org_mozilla_gecko_sqlite_SQLiteBridge_sqliteCall(JNIEnv* jenv, jclass, jstring jDb, jstring jQuery, jobjectArray jParams, jlongArray jQueryRes) { JNI_Setup(jenv); int rc; jobject jCursor = nullptr; const char* dbPath; sqlite3 *db; dbPath = jenv->GetStringUTFChars(jDb, nullptr); rc = f_sqlite3_open(dbPath, &db); jenv->ReleaseStringUTFChars(jDb, dbPath); if (rc != SQLITE_OK) { throwSqliteException(jenv, "Can't open database: %s", f_sqlite3_errmsg(db)); f_sqlite3_close(db); // close db even if open failed return nullptr; } jCursor = sqliteInternalCall(jenv, db, jQuery, jParams, jQueryRes); f_sqlite3_close(db); return jCursor; }
extern "C" NS_EXPORT jlong MOZ_JNICALL Java_org_mozilla_gecko_sqlite_SQLiteBridge_openDatabase(JNIEnv* jenv, jclass, jstring jDb) { JNI_Setup(jenv); int rc; const char* dbPath; sqlite3 *db; dbPath = jenv->GetStringUTFChars(jDb, nullptr); rc = f_sqlite3_open(dbPath, &db); jenv->ReleaseStringUTFChars(jDb, dbPath); if (rc != SQLITE_OK) { throwSqliteException(jenv, "Can't open database: %s", f_sqlite3_errmsg(db)); f_sqlite3_close(db); // close db even if open failed return 0; } return (jlong)db; }
extern "C" NS_EXPORT jlong JNICALL Java_org_mozilla_gecko_sqlite_SQLiteBridge_openDatabase(JNIEnv* jenv, jclass, jstring jDb) { JNI_Setup(jenv); int rc; const char* dbPath; sqlite3 *db; char* errorMsg; dbPath = jenv->GetStringUTFChars(jDb, NULL); rc = f_sqlite3_open(dbPath, &db); jenv->ReleaseStringUTFChars(jDb, dbPath); if (rc != SQLITE_OK) { asprintf(&errorMsg, "Can't open database: %s\n", f_sqlite3_errmsg(db)); LOG("Error in SQLiteBridge: %s\n", errorMsg); JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException", errorMsg); free(errorMsg); } return (jlong)db; }
static jobject sqliteInternalCall(JNIEnv* jenv, sqlite3 *db, jstring jQuery, jobjectArray jParams, jlongArray jQueryRes) { JNI_Setup(jenv); jobject jCursor = NULL; char* errorMsg; jsize numPars = 0; const char *pzTail; sqlite3_stmt *ppStmt; int rc; const char* queryStr; queryStr = jenv->GetStringUTFChars(jQuery, NULL); rc = f_sqlite3_prepare_v2(db, queryStr, -1, &ppStmt, &pzTail); if (rc != SQLITE_OK || ppStmt == NULL) { asprintf(&errorMsg, "Can't prepare statement: %s\n", f_sqlite3_errmsg(db)); goto error_close; } jenv->ReleaseStringUTFChars(jQuery, queryStr); // Check if number of parameters matches if (jParams != NULL) { numPars = jenv->GetArrayLength(jParams); } int sqlNumPars; sqlNumPars = f_sqlite3_bind_parameter_count(ppStmt); if (numPars != sqlNumPars) { asprintf(&errorMsg, "Passed parameter count (%d) doesn't match SQL parameter count (%d)\n", numPars, sqlNumPars); goto error_close; } if (jParams != NULL) { // Bind parameters, if any if (numPars > 0) { for (int i = 0; i < numPars; i++) { jobject jObjectParam = jenv->GetObjectArrayElement(jParams, i); // IsInstanceOf or isAssignableFrom? String is final, so IsInstanceOf // should be OK. jboolean isString = jenv->IsInstanceOf(jObjectParam, stringClass); if (isString != JNI_TRUE) { asprintf(&errorMsg, "Parameter is not of String type"); goto error_close; } jstring jStringParam = (jstring)jObjectParam; const char* paramStr = jenv->GetStringUTFChars(jStringParam, NULL); // SQLite parameters index from 1. rc = f_sqlite3_bind_text(ppStmt, i + 1, paramStr, -1, SQLITE_TRANSIENT); jenv->ReleaseStringUTFChars(jStringParam, paramStr); if (rc != SQLITE_OK) { asprintf(&errorMsg, "Error binding query parameter"); goto error_close; } } } } // Execute the query and step through the results rc = f_sqlite3_step(ppStmt); if (rc != SQLITE_ROW && rc != SQLITE_DONE) { asprintf(&errorMsg, "Can't step statement: (%d) %s\n", rc, f_sqlite3_errmsg(db)); goto error_close; } // Get the column count and names int cols; cols = f_sqlite3_column_count(ppStmt); { // Allocate a String[cols] jobjectArray jStringArray = jenv->NewObjectArray(cols, stringClass, NULL); if (jStringArray == NULL) { asprintf(&errorMsg, "Can't allocate String[]\n"); goto error_close; } // Assign column names to the String[] for (int i = 0; i < cols; i++) { const char* colName = f_sqlite3_column_name(ppStmt, i); jstring jStr = jenv->NewStringUTF(colName); jenv->SetObjectArrayElement(jStringArray, i, jStr); } // Construct the MatrixCursor(String[]) with given column names jCursor = jenv->NewObject(cursorClass, jCursorConstructor, jStringArray); if (jCursor == NULL) { asprintf(&errorMsg, "Can't allocate MatrixBlobCursor\n"); goto error_close; } } // Return the id and number of changed rows in jQueryRes { jlong id = f_sqlite3_last_insert_rowid(db); jenv->SetLongArrayRegion(jQueryRes, 0, 1, &id); jlong changed = f_sqlite3_changes(db); jenv->SetLongArrayRegion(jQueryRes, 1, 1, &changed); } // For each row, add an Object[] to the passed ArrayList, // with that containing either String or ByteArray objects // containing the columns while (rc != SQLITE_DONE) { // Process row // Construct Object[] jobjectArray jRow = jenv->NewObjectArray(cols, objectClass, NULL); if (jRow == NULL) { asprintf(&errorMsg, "Can't allocate jRow Object[]\n"); goto error_close; } for (int i = 0; i < cols; i++) { int colType = f_sqlite3_column_type(ppStmt, i); if (colType == SQLITE_BLOB) { // Treat as blob const void* blob = f_sqlite3_column_blob(ppStmt, i); int colLen = f_sqlite3_column_bytes(ppStmt, i); // Construct ByteBuffer of correct size jobject jByteBuffer = jenv->CallStaticObjectMethod(byteBufferClass, jByteBufferAllocateDirect, colLen); if (jByteBuffer == NULL) { goto error_close; } // Get its backing array void* bufferArray = jenv->GetDirectBufferAddress(jByteBuffer); if (bufferArray == NULL) { asprintf(&errorMsg, "Failure calling GetDirectBufferAddress\n"); goto error_close; } memcpy(bufferArray, blob, colLen); jenv->SetObjectArrayElement(jRow, i, jByteBuffer); jenv->DeleteLocalRef(jByteBuffer); } else if (colType == SQLITE_NULL) { jenv->SetObjectArrayElement(jRow, i, NULL); } else { // Treat everything else as text const char* txt = (const char*)f_sqlite3_column_text(ppStmt, i); jstring jStr = jenv->NewStringUTF(txt); jenv->SetObjectArrayElement(jRow, i, jStr); jenv->DeleteLocalRef(jStr); } } // Append Object[] to Cursor jenv->CallVoidMethod(jCursor, jCursorAddRow, jRow); // Clean up jenv->DeleteLocalRef(jRow); // Get next row rc = f_sqlite3_step(ppStmt); // Real error? if (rc != SQLITE_ROW && rc != SQLITE_DONE) { asprintf(&errorMsg, "Can't re-step statement:(%d) %s\n", rc, f_sqlite3_errmsg(db)); goto error_close; } } rc = f_sqlite3_finalize(ppStmt); if (rc != SQLITE_OK) { asprintf(&errorMsg, "Can't finalize statement: %s\n", f_sqlite3_errmsg(db)); goto error_close; } return jCursor; error_close: LOG("Error in SQLiteBridge: %s\n", errorMsg); JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException", errorMsg); free(errorMsg); return jCursor; }
extern "C" NS_EXPORT void JNICALL Java_org_mozilla_gecko_sqlite_SQLiteBridge_sqliteCall(JNIEnv* jenv, jclass, jstring jDb, jstring jQuery, jobjectArray jParams, jobject jColumns, jobject jArrayList) { JNI_Setup(jenv); char* errorMsg; jsize numPars = 0; const char* queryStr; queryStr = jenv->GetStringUTFChars(jQuery, NULL); const char* dbPath; dbPath = jenv->GetStringUTFChars(jDb, NULL); const char *pzTail; sqlite3_stmt *ppStmt; sqlite3 *db; int rc; rc = f_sqlite3_open(dbPath, &db); jenv->ReleaseStringUTFChars(jDb, dbPath); if (rc != SQLITE_OK) { asprintf(&errorMsg, "Can't open database: %s\n", f_sqlite3_errmsg(db)); goto error_close; } rc = f_sqlite3_prepare_v2(db, queryStr, -1, &ppStmt, &pzTail); if (rc != SQLITE_OK || ppStmt == NULL) { asprintf(&errorMsg, "Can't prepare statement: %s\n", f_sqlite3_errmsg(db)); goto error_close; } jenv->ReleaseStringUTFChars(jQuery, queryStr); // Check if number of parameters matches if (jParams != NULL) { numPars = jenv->GetArrayLength(jParams); } int sqlNumPars; sqlNumPars = f_sqlite3_bind_parameter_count(ppStmt); if (numPars != sqlNumPars) { asprintf(&errorMsg, "Passed parameter count (%d) doesn't match SQL parameter count (%d)\n", numPars, sqlNumPars); goto error_close; } if (jParams != NULL) { // Bind parameters, if any if (numPars > 0) { for (int i = 0; i < numPars; i++) { jobject jObjectParam = jenv->GetObjectArrayElement(jParams, i); // IsInstanceOf or isAssignableFrom? String is final, so IsInstanceOf // should be OK. jboolean isString = jenv->IsInstanceOf(jObjectParam, stringClass); if (isString != JNI_TRUE) { asprintf(&errorMsg, "Parameter is not of String type"); goto error_close; } jstring jStringParam = (jstring)jObjectParam; const char* paramStr = jenv->GetStringUTFChars(jStringParam, NULL); // SQLite parameters index from 1. rc = f_sqlite3_bind_text(ppStmt, i + 1, paramStr, -1, SQLITE_TRANSIENT); jenv->ReleaseStringUTFChars(jStringParam, paramStr); if (rc != SQLITE_OK) { asprintf(&errorMsg, "Error binding query parameter"); goto error_close; } } } } // Execute the query and step through the results rc = f_sqlite3_step(ppStmt); if (rc != SQLITE_ROW && rc != SQLITE_DONE) { asprintf(&errorMsg, "Can't step statement: (%d) %s\n", rc, f_sqlite3_errmsg(db)); goto error_close; } // Get the column names int cols; cols = f_sqlite3_column_count(ppStmt); for (int i = 0; i < cols; i++) { const char* colName = f_sqlite3_column_name(ppStmt, i); jstring jStr = jenv->NewStringUTF(colName); jenv->CallBooleanMethod(jColumns, jArrayListAdd, jStr); jenv->DeleteLocalRef(jStr); } // if the statement doesn't return any results, instead return the id and number of changed rows if (rc == SQLITE_DONE) { jclass integerClass = jenv->FindClass("java/lang/Integer"); jmethodID intConstructor = jenv->GetMethodID(integerClass, "<init>", "(I)V"); jobjectArray jRow = jenv->NewObjectArray(2, objectClass, NULL); if (jRow == NULL) { asprintf(&errorMsg, "Can't allocate jRow Object[]\n"); goto error_close; } int id = f_sqlite3_last_insert_rowid(db); jobject jId = jenv->NewObject(integerClass, intConstructor, id); jenv->SetObjectArrayElement(jRow, 0, jId); jenv->DeleteLocalRef(jId); int changed = f_sqlite3_changes(db); jobject jChanged = jenv->NewObject(integerClass, intConstructor, changed); jenv->SetObjectArrayElement(jRow, 1, jChanged); jenv->DeleteLocalRef(jChanged); jenv->CallBooleanMethod(jArrayList, jArrayListAdd, jRow); jenv->DeleteLocalRef(jRow); } // For each row, add an Object[] to the passed ArrayList, // with that containing either String or ByteArray objects // containing the columns while (rc != SQLITE_DONE) { // Process row // Construct Object[] jobjectArray jRow = jenv->NewObjectArray(cols, objectClass, NULL); if (jRow == NULL) { asprintf(&errorMsg, "Can't allocate jRow Object[]\n"); goto error_close; } for (int i = 0; i < cols; i++) { int colType = f_sqlite3_column_type(ppStmt, i); if (colType == SQLITE_BLOB) { // Treat as blob const void* blob = f_sqlite3_column_blob(ppStmt, i); int colLen = f_sqlite3_column_bytes(ppStmt, i); // Construct ByteBuffer of correct size jobject jByteBuffer = jenv->CallStaticObjectMethod(byteBufferClass, jByteBufferAllocateDirect, colLen); if (jByteBuffer == NULL) { goto error_close; } // Get its backing array void* bufferArray = jenv->GetDirectBufferAddress(jByteBuffer); if (bufferArray == NULL) { asprintf(&errorMsg, "Failure calling GetDirectBufferAddress\n"); goto error_close; } memcpy(bufferArray, blob, colLen); jenv->SetObjectArrayElement(jRow, i, jByteBuffer); jenv->DeleteLocalRef(jByteBuffer); } else if (colType == SQLITE_NULL) { jenv->SetObjectArrayElement(jRow, i, jNull); } else { // Treat everything else as text const char* txt = (const char*)f_sqlite3_column_text(ppStmt, i); jstring jStr = jenv->NewStringUTF(txt); jenv->SetObjectArrayElement(jRow, i, jStr); jenv->DeleteLocalRef(jStr); } } // Append Object[] to ArrayList<Object[]> // JNI doesn't know about the generic, so use Object[] as Object jenv->CallBooleanMethod(jArrayList, jArrayListAdd, jRow); // Clean up jenv->DeleteLocalRef(jRow); // Get next row rc = f_sqlite3_step(ppStmt); // Real error? if (rc != SQLITE_ROW && rc != SQLITE_DONE) { asprintf(&errorMsg, "Can't re-step statement:(%d) %s\n", rc, f_sqlite3_errmsg(db)); goto error_close; } } rc = f_sqlite3_finalize(ppStmt); if (rc != SQLITE_OK) { asprintf(&errorMsg, "Can't finalize statement: %s\n", f_sqlite3_errmsg(db)); goto error_close; } f_sqlite3_close(db); return; error_close: f_sqlite3_close(db); LOG("Error in SQLiteBridge: %s\n", errorMsg); JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException", errorMsg); free(errorMsg); return; }