blob: b6b788114d224ff915831106297f2dd8457cb55e [file] [log] [blame]
/*
* Copyright (C) 2023 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.
*/
#define LOG_TAG "SQLiteRawStatement"
#include <string.h>
#include <algorithm>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/scoped_primitive_array.h>
#include <nativehelper/scoped_string_chars.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
#include <android-base/stringprintf.h>
#include <core_jni_helpers.h>
#include <utils/Log.h>
#include <utils/Unicode.h>
#include <sqlite3.h>
#include <sqlite3_android.h>
#include "android_database_SQLiteCommon.h"
/**
* JNI functions supporting the android.database.sqlite.SQLiteRawStatement class.
*/
namespace android {
// Helper functions.
static sqlite3 *db(long statementPtr) {
return sqlite3_db_handle(reinterpret_cast<sqlite3_stmt*>(statementPtr));
}
static sqlite3_stmt* stmt(long statementPtr) {
return reinterpret_cast<sqlite3_stmt*>(statementPtr);
}
// This throws a SQLiteBindOrColumnIndexOutOfRangeException if the parameter index is out
// of bounds. The function exists to construct an error message that includes
// the bounds.
static void throwInvalidParameter(JNIEnv *env, jlong stmtPtr, jint index) {
if (sqlite3_extended_errcode(db(stmtPtr)) == SQLITE_RANGE) {
int count = sqlite3_bind_parameter_count(stmt(stmtPtr));
std::string message = android::base::StringPrintf(
"parameter index %d out of bounds [1,%d]", index, count);
char const * errmsg = sqlite3_errstr(SQLITE_RANGE);
throw_sqlite3_exception(env, SQLITE_RANGE, errmsg, message.c_str());
} else {
throw_sqlite3_exception(env, db(stmtPtr), nullptr);
}
}
// This throws a SQLiteBindOrColumnIndexOutOfRangeException if the column index is out
// of bounds.
static void throwIfInvalidColumn(JNIEnv *env, jlong stmtPtr, jint col) {
if (col < 0 || col >= sqlite3_data_count(stmt(stmtPtr))) {
int count = sqlite3_data_count(stmt(stmtPtr));
std::string message = android::base::StringPrintf(
"column index %d out of bounds [0,%d]", col, count - 1);
char const * errmsg = sqlite3_errstr(SQLITE_RANGE);
throw_sqlite3_exception(env, SQLITE_RANGE, errmsg, message.c_str());
}
}
static jint bindParameterCount(JNIEnv* env, jclass, jlong stmtPtr) {
return sqlite3_bind_parameter_count(stmt(stmtPtr));
}
// jname must be in standard UTF-8. This throws an NPE if jname is null.
static jint bindParameterIndex(JNIEnv *env, jclass, jlong stmtPtr, jstring jname) {
ScopedStringChars name(env, jname);
if (name.get() == nullptr) {
return 0;
}
size_t len16 = env->GetStringLength(jname);
size_t len8 = utf16_to_utf8_length(reinterpret_cast<const char16_t*>(name.get()), len16);
// The extra byte is for the terminating null.
char *utf8Name = new char[len8 + 1];
utf16_to_utf8(reinterpret_cast<const char16_t*>(name.get()), len16, utf8Name, len8 + 1);
int r = sqlite3_bind_parameter_index(stmt(stmtPtr), utf8Name);
delete [] utf8Name;
return r;
}
// The name returned from the database is UTF-8. If there is no matching name,
// null is returned.
static jstring bindParameterName(JNIEnv *env, jclass, jlong stmtPtr, jint param) {
char const *src = sqlite3_bind_parameter_name(stmt(stmtPtr), param);
if (src == nullptr) {
return NULL;
}
return env->NewStringUTF(src);
}
static jint columnCount(JNIEnv* env, jclass, jlong stmtPtr) {
return sqlite3_column_count(stmt(stmtPtr));
}
// Step the prepared statement. If the result is other than ROW, DONE, BUSY, or LOCKED, throw an
// exception if throwOnError is true. The advantage of throwing from the native latyer is that
// all the error codes and error strings are easily visible.
static jint step(JNIEnv* env, jclass, jlong stmtPtr, jboolean throwOnError) {
sqlite3_stmt* statement = stmt(stmtPtr);
int err = sqlite3_step(statement);
switch (err) {
case SQLITE_ROW:
case SQLITE_DONE:
case SQLITE_BUSY:
case SQLITE_LOCKED:
return err;
}
if (throwOnError) {
throw_sqlite3_exception(env, db(stmtPtr), "failure in step()");
}
return err;
}
static void reset(JNIEnv*, jclass, jlong stmtPtr, jboolean clear) {
if (clear) sqlite3_clear_bindings(stmt(stmtPtr));
// The return value is ignored.
sqlite3_reset(stmt(stmtPtr));
}
static void clearBindings(JNIEnv*, jclass, jlong stmtPtr) {
sqlite3_clear_bindings(stmt(stmtPtr));
}
// This binds null to the parameter if the incoming array is null.
static void bindBlob(JNIEnv* env, jclass obj, jlong stmtPtr, jint index, jbyteArray val,
jint offset, jint length) {
ScopedByteArrayRO value(env, val);
int err;
if (value.get() == nullptr) {
err = sqlite3_bind_null(stmt(stmtPtr), index);
} else {
err = sqlite3_bind_blob(stmt(stmtPtr), index, value.get() + offset,
length, SQLITE_TRANSIENT);
}
if (err != SQLITE_OK) {
throwInvalidParameter(env, stmtPtr, index);
}
}
static void bindDouble(JNIEnv* env, jclass, jlong stmtPtr, jint index, jdouble val) {
if (sqlite3_bind_double(stmt(stmtPtr), index, val) != SQLITE_OK) {
throwInvalidParameter(env, stmtPtr, index);
}
}
static void bindInt(JNIEnv* env, jclass, jlong stmtPtr, jint index, jint val) {
if (sqlite3_bind_int(stmt(stmtPtr), index, val) != SQLITE_OK) {
throwInvalidParameter(env, stmtPtr, index);
}
}
static void bindLong(JNIEnv* env, jclass, jlong stmtPtr, jint index, jlong val) {
if (sqlite3_bind_int64(stmt(stmtPtr), index, val) != SQLITE_OK) {
throwInvalidParameter(env, stmtPtr, index);
}
}
static void bindNull(JNIEnv* env, jclass, jlong stmtPtr, jint index) {
if (sqlite3_bind_null(stmt(stmtPtr), index) != SQLITE_OK) {
throwInvalidParameter(env, stmtPtr, index);
}
}
// This binds null to the parameter if the string is null.
static void bindText(JNIEnv* env, jclass, jlong stmtPtr, jint index, jstring val) {
ScopedStringChars value(env, val);
int err;
if (value.get() == nullptr) {
err = sqlite3_bind_null(stmt(stmtPtr), index);
} else {
jsize valueLength = env->GetStringLength(val);
err = sqlite3_bind_text16(stmt(stmtPtr), index, value.get(),
valueLength * sizeof(jchar), SQLITE_TRANSIENT);
}
if (err != SQLITE_OK) {
throwInvalidParameter(env, stmtPtr, index);
}
}
static jint columnType(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
throwIfInvalidColumn(env, stmtPtr, col);
return sqlite3_column_type(stmt(stmtPtr), col);
}
static jstring columnName(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
throwIfInvalidColumn(env, stmtPtr, col);
const jchar* name = static_cast<const jchar*>(sqlite3_column_name16(stmt(stmtPtr), col));
if (name == nullptr) {
throw_sqlite3_exception(env, db(stmtPtr), "error fetching columnName()");
return NULL;
}
size_t length = strlen16(reinterpret_cast<const char16_t*>(name));
return env->NewString(name, length);
}
static jint columnBytes(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
throwIfInvalidColumn(env, stmtPtr, col);
return sqlite3_column_bytes16(stmt(stmtPtr), col);
}
static jbyteArray columnBlob(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
throwIfInvalidColumn(env, stmtPtr, col);
const void* blob = sqlite3_column_blob(stmt(stmtPtr), col);
if (blob == nullptr) {
return NULL;
}
size_t size = sqlite3_column_bytes(stmt(stmtPtr), col);
jbyteArray result = env->NewByteArray(size);
if (result == nullptr) {
// An OutOfMemory exception will have been thrown.
return NULL;
}
env->SetByteArrayRegion(result, 0, size, reinterpret_cast<const jbyte*>(blob));
return result;
}
static int columnBuffer(JNIEnv* env, jclass, jlong stmtPtr, jint col,
jbyteArray buffer, jint offset, jint length, jint srcOffset) {
throwIfInvalidColumn(env, stmtPtr, col);
const void* blob = sqlite3_column_blob(stmt(stmtPtr), col);
if (blob == nullptr) {
return 0;
}
jsize bsize = sqlite3_column_bytes(stmt(stmtPtr), col);
if (bsize == 0 || bsize <= srcOffset) {
return 0;
}
jsize want = std::min(bsize - srcOffset, length);
env->SetByteArrayRegion(buffer, offset, want, reinterpret_cast<const jbyte*>(blob) + srcOffset);
return want;
}
static jdouble columnDouble(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
throwIfInvalidColumn(env, stmtPtr, col);
return sqlite3_column_double(stmt(stmtPtr), col);
}
static jint columnInt(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
throwIfInvalidColumn(env, stmtPtr, col);
return sqlite3_column_int(stmt(stmtPtr), col);
}
static jlong columnLong(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
throwIfInvalidColumn(env, stmtPtr, col);
return sqlite3_column_int64(stmt(stmtPtr), col);
}
static jstring columnText(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
throwIfInvalidColumn(env, stmtPtr, col);
const jchar* text = static_cast<const jchar*>(sqlite3_column_text16(stmt(stmtPtr), col));
if (text == nullptr) {
return NULL;
}
size_t length = sqlite3_column_bytes16(stmt(stmtPtr), col) / sizeof(jchar);
return env->NewString(text, length);
}
static const JNINativeMethod sStatementMethods[] =
{
// Metadata
{ "nativeBindParameterCount", "(J)I", (void*) bindParameterCount },
{ "nativeBindParameterIndex", "(JLjava/lang/String;)I", (void*) bindParameterIndex },
{ "nativeBindParameterName", "(JI)Ljava/lang/String;", (void*) bindParameterName },
// Operations on a statement
{ "nativeStep", "(JZ)I", (void*) step },
{ "nativeReset", "(JZ)V", (void*) reset },
{ "nativeClearBindings", "(J)V", (void*) clearBindings },
// Methods that bind values to parameters
{ "nativeBindBlob", "(JI[BII)V", (void*) bindBlob },
{ "nativeBindDouble", "(JID)V", (void*) bindDouble },
{ "nativeBindInt", "(JII)V", (void*) bindInt },
{ "nativeBindLong", "(JIJ)V", (void*) bindLong },
{ "nativeBindNull", "(JI)V", (void*) bindNull },
{ "nativeBindText", "(JILjava/lang/String;)V", (void*) bindText },
// Methods that return information about columns in a result row.
{ "nativeColumnCount", "(J)I", (void*) columnCount },
{ "nativeColumnType", "(JI)I", (void*) columnType },
{ "nativeColumnName", "(JI)Ljava/lang/String;", (void*) columnName },
{ "nativeColumnBytes", "(JI)I", (void*) columnBytes },
{ "nativeColumnBlob", "(JI)[B", (void*) columnBlob },
{ "nativeColumnBuffer", "(JI[BIII)I", (void*) columnBuffer },
{ "nativeColumnDouble", "(JI)D", (void*) columnDouble },
{ "nativeColumnInt", "(JI)I", (void*) columnInt },
{ "nativeColumnLong", "(JI)J", (void*) columnLong },
{ "nativeColumnText", "(JI)Ljava/lang/String;", (void*) columnText },
};
int register_android_database_SQLiteRawStatement(JNIEnv *env)
{
return RegisterMethodsOrDie(env, "android/database/sqlite/SQLiteRawStatement",
sStatementMethods, NELEM(sStatementMethods));
}
} // namespace android