blob: da884f25f4bbc4ec92c24967dc4a176d1117e8d3 [file] [log] [blame]
#include <jni.h>
#include "sqlite3.h"
#include <sstream>
#include <stdlib.h>
/**
* Throws SQLiteException with the given error code and message.
*
* @return true if the exception was thrown, otherwise false.
*/
static bool throwSQLiteException(JNIEnv *env, int errorCode, const char *errorMsg) {
jclass exceptionClass = env->FindClass("androidx/sqlite/SQLiteException");
if (exceptionClass == nullptr) {
// If androidx's exception isn't found we are likely in Android's native where the
// actual exception is type aliased. Clear the ClassNotFoundException and instead find
// and throw Android's exception.
env->ExceptionClear();
exceptionClass = env->FindClass("android/database/SQLException");
}
std::stringstream message;
message << "Error code: " << errorCode;
if (errorMsg != nullptr) {
message << ", message: " << errorMsg;
}
int throwResult = env->ThrowNew(exceptionClass, message.str().c_str());
return throwResult == 0;
}
static bool throwIfNoRow(JNIEnv *env, sqlite3_stmt* stmt) {
int lastRc = sqlite3_errcode(sqlite3_db_handle(stmt));
if (lastRc != SQLITE_ROW) {
return throwSQLiteException(env, SQLITE_MISUSE, "no row");
}
return false;
}
static bool throwIfInvalidColumn(JNIEnv *env, sqlite3_stmt *stmt, int index) {
if (index < 0 || index >= sqlite3_column_count(stmt)) {
return throwSQLiteException(env, SQLITE_RANGE, "column index out of range");
}
return false;
}
extern "C" JNIEXPORT jlong JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteDriverKt_nativeOpen(
JNIEnv* env,
jclass clazz,
jstring name) {
const char *path = env->GetStringUTFChars(name, nullptr);
sqlite3 *db;
int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
int rc = sqlite3_open_v2(path, &db, openFlags, nullptr);
env->ReleaseStringUTFChars(name, path);
if (rc != SQLITE_OK) {
throwSQLiteException(env, rc, nullptr);
return 0;
}
return reinterpret_cast<jlong>(db);
}
extern "C" JNIEXPORT jlong JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteConnectionKt_nativePrepare(
JNIEnv* env,
jclass clazz,
jlong dbPointer,
jstring sqlString) {
sqlite3* db = reinterpret_cast<sqlite3*>(dbPointer);
sqlite3_stmt* stmt;
jsize sqlLength = env->GetStringLength(sqlString);
// Java / jstring represents a string in UTF-16 encoding.
const jchar* sql = env->GetStringCritical(sqlString, nullptr);
int rc = sqlite3_prepare16_v2(db, sql, sqlLength * sizeof(jchar), &stmt, nullptr);
env->ReleaseStringCritical(sqlString, sql);
if (rc != SQLITE_OK) {
throwSQLiteException(env, rc, sqlite3_errmsg(db));
return 0;
}
return reinterpret_cast<jlong>(stmt);
}
extern "C" JNIEXPORT void JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteConnectionKt_nativeClose(
JNIEnv* env,
jclass clazz,
jlong dbPointer) {
sqlite3 *db = reinterpret_cast<sqlite3*>(dbPointer);
sqlite3_close_v2(db);
}
extern "C" JNIEXPORT void JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeBindBlob(
JNIEnv* env,
jclass clazz,
jlong stmtPointer,
jint index,
jbyteArray value) {
sqlite3_stmt* stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
jsize valueLength = env->GetArrayLength(value);
jbyte* blob = static_cast<jbyte*>(env->GetPrimitiveArrayCritical(value, nullptr));
int rc = sqlite3_bind_blob(stmt, index, blob, valueLength, SQLITE_TRANSIENT);
env->ReleasePrimitiveArrayCritical(value, blob, JNI_ABORT);
if (rc != SQLITE_OK) {
throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt)));
}
}
extern "C" JNIEXPORT void JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeBindDouble(
JNIEnv* env,
jclass clazz,
jlong stmtPointer,
jint index,
jdouble value) {
sqlite3_stmt* stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
int rc = sqlite3_bind_double(stmt, index, value);
if (rc != SQLITE_OK) {
throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt)));
}
}
extern "C" JNIEXPORT void JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeBindLong(
JNIEnv* env,
jclass clazz,
jlong stmtPointer,
jint index,
jlong value) {
sqlite3_stmt* stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
int rc = sqlite3_bind_int64(stmt, index, value);
if (rc != SQLITE_OK) {
throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt)));
}
}
extern "C" JNIEXPORT void JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeBindText(
JNIEnv* env,
jclass clazz,
jlong stmtPointer,
jint index,
jstring value) {
sqlite3_stmt* stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
jsize valueLength = env->GetStringLength(value);
const jchar* text = env->GetStringCritical(value, NULL);
int rc = sqlite3_bind_text16(stmt, index, text, valueLength * sizeof(jchar), SQLITE_TRANSIENT);
env->ReleaseStringCritical(value, text);
if (rc != SQLITE_OK) {
throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt)));
}
}
extern "C" JNIEXPORT void JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeBindNull(
JNIEnv* env,
jclass clazz,
jlong stmtPointer,
jint index) {
sqlite3_stmt* stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
int rc = sqlite3_bind_null(stmt, index);
if (rc != SQLITE_OK) {
throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt)));
}
}
extern "C" JNIEXPORT jboolean JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeStep(
JNIEnv *env,
jclass clazz,
jlong stmtPointer) {
sqlite3_stmt *stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
int rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
return JNI_TRUE;
}
if (rc == SQLITE_DONE) {
return JNI_FALSE;
}
throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt)));
return JNI_FALSE;
}
extern "C" JNIEXPORT jbyteArray JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetBlob(
JNIEnv *env,
jclass clazz,
jlong stmtPointer,
jint index) {
sqlite3_stmt *stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
if (throwIfNoRow(env, stmt)) return nullptr;
if (throwIfInvalidColumn(env, stmt, index)) return nullptr;
const void *blob = sqlite3_column_blob(stmt, index);
int size = sqlite3_column_bytes(stmt, index);
// TODO(b/304297717): Use sqlite3_errcode() to check for out-of-memory
jbyteArray byteArray = env->NewByteArray(size);
if (size > 0) {
env->SetByteArrayRegion(byteArray, 0, size, static_cast<const jbyte*>(blob));
}
return byteArray;
}
extern "C" JNIEXPORT jdouble JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetDouble(
JNIEnv *env,
jclass clazz,
jlong stmtPointer,
jint index) {
sqlite3_stmt *stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
if (throwIfNoRow(env, stmt)) return 0.0;
if (throwIfInvalidColumn(env, stmt, index)) return 0.0;
return sqlite3_column_double(stmt, index);
}
extern "C" JNIEXPORT jlong JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetLong(
JNIEnv *env,
jclass clazz,
jlong stmtPointer,
jint index) {
sqlite3_stmt *stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
if (throwIfNoRow(env, stmt)) return 0;
if (throwIfInvalidColumn(env, stmt, index)) return 0;
return sqlite3_column_int64(stmt, index);
}
extern "C" JNIEXPORT jstring JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetText(
JNIEnv *env,
jclass clazz,
jlong stmtPointer,
jint index) {
sqlite3_stmt *stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
if (throwIfNoRow(env, stmt)) return nullptr;
if (throwIfInvalidColumn(env, stmt, index)) return nullptr;
// Java / jstring represents a string in UTF-16 encoding.
const jchar *text = static_cast<const jchar*>(sqlite3_column_text16(stmt, index));
if (text) {
int length = sqlite3_column_bytes16(stmt, index) / sizeof(jchar);
return env->NewString(text, length);
}
// TODO: Use sqlite3_errcode() to check for out-of-memory
return nullptr;
}
extern "C" JNIEXPORT jint JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetColumnCount(
JNIEnv *env,
jclass clazz,
jlong stmtPointer) {
sqlite3_stmt *stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
return sqlite3_column_count(stmt);
}
extern "C" JNIEXPORT jstring JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetColumnName(
JNIEnv *env,
jclass clazz,
jlong stmtPointer,
jint index) {
sqlite3_stmt *stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
if (throwIfInvalidColumn(env, stmt, index)) return nullptr;
const char *name = sqlite3_column_name(stmt, index);
if (name == NULL) {
// TODO: throw out-of-memory exception
}
return env->NewStringUTF(name);
}
extern "C" JNIEXPORT jint JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetColumnType(
JNIEnv *env,
jclass clazz,
jlong stmtPointer,
jint index) {
sqlite3_stmt *stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
if (throwIfNoRow(env, stmt)) return 0;
if (throwIfInvalidColumn(env, stmt, index)) return 0;
return sqlite3_column_type(stmt, index);
}
extern "C" JNIEXPORT void JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeReset(
JNIEnv* env,
jclass clazz,
jlong stmtPointer) {
sqlite3_stmt* stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
int rc = sqlite3_reset(stmt);
if (rc != SQLITE_OK) {
throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt)));
}
}
extern "C" JNIEXPORT void JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeClearBindings(
JNIEnv* env,
jclass clazz,
jlong stmtPointer) {
sqlite3_stmt* stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
int rc = sqlite3_clear_bindings(stmt);
if (rc != SQLITE_OK) {
throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt)));
}
}
extern "C" JNIEXPORT void JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeClose(
JNIEnv* env,
jclass clazz,
jlong stmtPointer) {
sqlite3_stmt* stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
sqlite3_finalize(stmt);
}