1 #include <jni.h>
2 #include "sqlite3.h"
3 #include <sstream>
4 #include <stdlib.h>
5 
6 /**
7  * Throws SQLiteException with the given error code and message.
8  *
9  * @return true if the exception was thrown, otherwise false.
10  */
throwSQLiteException(JNIEnv * env,int errorCode,const char * errorMsg)11 static bool throwSQLiteException(JNIEnv *env, int errorCode, const char *errorMsg) {
12     jclass exceptionClass = env->FindClass("androidx/sqlite/SQLiteException");
13     if (exceptionClass == nullptr) {
14         // If androidx's exception isn't found we are likely in Android's native where the
15         // actual exception is type aliased. Clear the ClassNotFoundException and instead find
16         // and throw Android's exception.
17         env->ExceptionClear();
18         exceptionClass = env->FindClass("android/database/SQLException");
19     }
20     std::stringstream message;
21     message << "Error code: " << errorCode;
22     if (errorMsg != nullptr) {
23         message << ", message: " <<  errorMsg;
24     }
25     int throwResult = env->ThrowNew(exceptionClass, message.str().c_str());
26     return throwResult == 0;
27 }
28 
throwIfNoRow(JNIEnv * env,sqlite3_stmt * stmt)29 static bool throwIfNoRow(JNIEnv *env, sqlite3_stmt* stmt) {
30     if (sqlite3_stmt_busy(stmt) == 0) {
31         return throwSQLiteException(env, SQLITE_MISUSE, "no row");
32     }
33     return false;
34 }
35 
throwIfInvalidColumn(JNIEnv * env,sqlite3_stmt * stmt,int index)36 static bool throwIfInvalidColumn(JNIEnv *env, sqlite3_stmt *stmt, int index) {
37     if (index < 0 || index >= sqlite3_column_count(stmt)) {
38         return throwSQLiteException(env, SQLITE_RANGE, "column index out of range");
39     }
40     return false;
41 }
42 
throwOutOfMemoryError(JNIEnv * env)43 static bool throwOutOfMemoryError(JNIEnv *env) {
44     jclass exceptionClass = env->FindClass("java/lang/OutOfMemoryError");
45     int throwResult = env->ThrowNew(exceptionClass, nullptr);
46     return throwResult == 0;
47 }
48 
throwIfOutOfMemory(JNIEnv * env,sqlite3_stmt * stmt)49 static bool throwIfOutOfMemory(JNIEnv *env, sqlite3_stmt *stmt) {
50     int lastRc = sqlite3_errcode(sqlite3_db_handle(stmt));
51     if (lastRc == SQLITE_NOMEM) {
52         return throwOutOfMemoryError(env);
53     }
54     return false;
55 }
56 
57 extern "C" JNIEXPORT jint JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteDriverKt_nativeThreadSafeMode(JNIEnv * env,jclass clazz)58 Java_androidx_sqlite_driver_bundled_BundledSQLiteDriverKt_nativeThreadSafeMode(
59         JNIEnv* env,
60         jclass clazz) {
61     return sqlite3_threadsafe();
62 }
63 
64 extern "C" JNIEXPORT jlong JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteDriverKt_nativeOpen(JNIEnv * env,jclass clazz,jstring name,int openFlags)65 Java_androidx_sqlite_driver_bundled_BundledSQLiteDriverKt_nativeOpen(
66         JNIEnv* env,
67         jclass clazz,
68         jstring name,
69         int openFlags) {
70     const char *path = env->GetStringUTFChars(name, nullptr);
71     sqlite3 *db;
72     int rc = sqlite3_open_v2(path, &db, openFlags, nullptr);
73     env->ReleaseStringUTFChars(name, path);
74     if (rc != SQLITE_OK) {
75         throwSQLiteException(env, rc, nullptr);
76         return 0;
77     }
78     return reinterpret_cast<jlong>(db);
79 }
80 
81 extern "C" JNIEXPORT jlong JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteConnectionKt_nativePrepare(JNIEnv * env,jclass clazz,jlong dbPointer,jstring sqlString)82 Java_androidx_sqlite_driver_bundled_BundledSQLiteConnectionKt_nativePrepare(
83         JNIEnv* env,
84         jclass clazz,
85         jlong dbPointer,
86         jstring sqlString) {
87     sqlite3* db = reinterpret_cast<sqlite3*>(dbPointer);
88     sqlite3_stmt* stmt;
89     jsize sqlLength = env->GetStringLength(sqlString);
90     // Java / jstring represents a string in UTF-16 encoding.
91     const jchar* sql = env->GetStringCritical(sqlString, nullptr);
92     int rc = sqlite3_prepare16_v2(db, sql, sqlLength * sizeof(jchar), &stmt, nullptr);
93     env->ReleaseStringCritical(sqlString, sql);
94     if (rc != SQLITE_OK) {
95         throwSQLiteException(env, rc, sqlite3_errmsg(db));
96         return 0;
97     }
98     return reinterpret_cast<jlong>(stmt);
99 }
100 
101 extern "C" JNIEXPORT void JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteConnectionKt_nativeClose(JNIEnv * env,jclass clazz,jlong dbPointer)102 Java_androidx_sqlite_driver_bundled_BundledSQLiteConnectionKt_nativeClose(
103         JNIEnv* env,
104         jclass clazz,
105         jlong dbPointer) {
106     sqlite3 *db = reinterpret_cast<sqlite3*>(dbPointer);
107     sqlite3_close_v2(db);
108 }
109 
110 extern "C" JNIEXPORT void JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeBindBlob(JNIEnv * env,jclass clazz,jlong stmtPointer,jint index,jbyteArray value)111 Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeBindBlob(
112         JNIEnv* env,
113         jclass clazz,
114         jlong stmtPointer,
115         jint index,
116         jbyteArray value) {
117     sqlite3_stmt* stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
118     jsize valueLength = env->GetArrayLength(value);
119     jbyte* blob = static_cast<jbyte*>(env->GetPrimitiveArrayCritical(value, nullptr));
120     int rc = sqlite3_bind_blob(stmt, index, blob, valueLength, SQLITE_TRANSIENT);
121     env->ReleasePrimitiveArrayCritical(value, blob, JNI_ABORT);
122     if (rc != SQLITE_OK) {
123         throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt)));
124     }
125 }
126 
127 extern "C" JNIEXPORT void JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeBindDouble(JNIEnv * env,jclass clazz,jlong stmtPointer,jint index,jdouble value)128 Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeBindDouble(
129         JNIEnv* env,
130         jclass clazz,
131         jlong stmtPointer,
132         jint index,
133         jdouble value) {
134     sqlite3_stmt* stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
135     int rc = sqlite3_bind_double(stmt, index, value);
136     if (rc != SQLITE_OK) {
137         throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt)));
138     }
139 }
140 
141 extern "C" JNIEXPORT void JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeBindLong(JNIEnv * env,jclass clazz,jlong stmtPointer,jint index,jlong value)142 Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeBindLong(
143         JNIEnv* env,
144         jclass clazz,
145         jlong stmtPointer,
146         jint index,
147         jlong value) {
148     sqlite3_stmt* stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
149     int rc = sqlite3_bind_int64(stmt, index, value);
150     if (rc != SQLITE_OK) {
151         throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt)));
152     }
153 }
154 
155 extern "C" JNIEXPORT void JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeBindText(JNIEnv * env,jclass clazz,jlong stmtPointer,jint index,jstring value)156 Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeBindText(
157         JNIEnv* env,
158         jclass clazz,
159         jlong stmtPointer,
160         jint index,
161         jstring value) {
162     sqlite3_stmt* stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
163     jsize valueLength = env->GetStringLength(value);
164     const jchar* text = env->GetStringCritical(value, NULL);
165     int rc = sqlite3_bind_text16(stmt, index, text, valueLength * sizeof(jchar), SQLITE_TRANSIENT);
166     env->ReleaseStringCritical(value, text);
167     if (rc != SQLITE_OK) {
168         throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt)));
169     }
170 }
171 
172 extern "C" JNIEXPORT void JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeBindNull(JNIEnv * env,jclass clazz,jlong stmtPointer,jint index)173 Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeBindNull(
174         JNIEnv* env,
175         jclass clazz,
176         jlong stmtPointer,
177         jint index) {
178     sqlite3_stmt* stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
179     int rc = sqlite3_bind_null(stmt, index);
180     if (rc != SQLITE_OK) {
181         throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt)));
182     }
183 }
184 
185 extern "C" JNIEXPORT jboolean JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeStep(JNIEnv * env,jclass clazz,jlong stmtPointer)186 Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeStep(
187         JNIEnv *env,
188         jclass clazz,
189         jlong stmtPointer) {
190     sqlite3_stmt *stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
191     int rc = sqlite3_step(stmt);
192     if (rc == SQLITE_ROW) {
193         return JNI_TRUE;
194     }
195     if (rc == SQLITE_DONE) {
196         return JNI_FALSE;
197     }
198     throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt)));
199     return JNI_FALSE;
200 }
201 
202 extern "C" JNIEXPORT jbyteArray JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetBlob(JNIEnv * env,jclass clazz,jlong stmtPointer,jint index)203 Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetBlob(
204         JNIEnv *env,
205         jclass clazz,
206         jlong stmtPointer,
207         jint index) {
208     sqlite3_stmt *stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
209     if (throwIfNoRow(env, stmt)) return nullptr;
210     if (throwIfInvalidColumn(env, stmt, index)) return nullptr;
211     const void *blob = sqlite3_column_blob(stmt, index);
212     if (blob == nullptr && throwIfOutOfMemory(env, stmt)) return nullptr;
213     int size = sqlite3_column_bytes(stmt, index);
214     if (size == 0 && throwIfOutOfMemory(env, stmt)) return nullptr;
215     jbyteArray byteArray = env->NewByteArray(size);
216     if (size > 0) {
217         env->SetByteArrayRegion(byteArray, 0, size, static_cast<const jbyte*>(blob));
218     }
219     return byteArray;
220 }
221 
222 extern "C" JNIEXPORT jdouble JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetDouble(JNIEnv * env,jclass clazz,jlong stmtPointer,jint index)223 Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetDouble(
224         JNIEnv *env,
225         jclass clazz,
226         jlong stmtPointer,
227         jint index) {
228     sqlite3_stmt *stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
229     if (throwIfNoRow(env, stmt)) return 0.0;
230     if (throwIfInvalidColumn(env, stmt, index)) return 0.0;
231     return sqlite3_column_double(stmt, index);
232 }
233 
234 extern "C" JNIEXPORT jlong JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetLong(JNIEnv * env,jclass clazz,jlong stmtPointer,jint index)235 Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetLong(
236         JNIEnv *env,
237         jclass clazz,
238         jlong stmtPointer,
239         jint index) {
240     sqlite3_stmt *stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
241     if (throwIfNoRow(env, stmt)) return 0;
242     if (throwIfInvalidColumn(env, stmt, index)) return 0;
243     return sqlite3_column_int64(stmt, index);
244 }
245 
246 extern "C" JNIEXPORT jstring JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetText(JNIEnv * env,jclass clazz,jlong stmtPointer,jint index)247 Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetText(
248         JNIEnv *env,
249         jclass clazz,
250         jlong stmtPointer,
251         jint index) {
252     sqlite3_stmt *stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
253     if (throwIfNoRow(env, stmt)) return nullptr;
254     if (throwIfInvalidColumn(env, stmt, index)) return nullptr;
255     // Java / jstring represents a string in UTF-16 encoding.
256     const jchar *text = static_cast<const jchar*>(sqlite3_column_text16(stmt, index));
257     if (text == nullptr && throwIfOutOfMemory(env, stmt)) return nullptr;
258     size_t length = sqlite3_column_bytes16(stmt, index) / sizeof(jchar);
259     if (length == 0 && throwIfOutOfMemory(env, stmt)) return nullptr;
260     return env->NewString(text, length);
261 }
262 
263 extern "C" JNIEXPORT jint JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetColumnCount(JNIEnv * env,jclass clazz,jlong stmtPointer)264 Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetColumnCount(
265         JNIEnv *env,
266         jclass clazz,
267         jlong stmtPointer) {
268     sqlite3_stmt *stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
269     return sqlite3_column_count(stmt);
270 }
271 
272 extern "C" JNIEXPORT jstring JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetColumnName(JNIEnv * env,jclass clazz,jlong stmtPointer,jint index)273 Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetColumnName(
274         JNIEnv *env,
275         jclass clazz,
276         jlong stmtPointer,
277         jint index) {
278     sqlite3_stmt *stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
279     if (throwIfInvalidColumn(env, stmt, index)) return nullptr;
280     const char *name = sqlite3_column_name(stmt, index);
281     if (name == nullptr) {
282         throwOutOfMemoryError(env);
283         return nullptr;
284     }
285     return env->NewStringUTF(name);
286 }
287 
288 extern "C" JNIEXPORT jint JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetColumnType(JNIEnv * env,jclass clazz,jlong stmtPointer,jint index)289 Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeGetColumnType(
290         JNIEnv *env,
291         jclass clazz,
292         jlong stmtPointer,
293         jint index) {
294     sqlite3_stmt *stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
295     if (throwIfNoRow(env, stmt)) return 0;
296     if (throwIfInvalidColumn(env, stmt, index)) return 0;
297     return sqlite3_column_type(stmt, index);
298 }
299 
300 extern "C" JNIEXPORT void JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeReset(JNIEnv * env,jclass clazz,jlong stmtPointer)301 Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeReset(
302         JNIEnv* env,
303         jclass clazz,
304         jlong stmtPointer) {
305     sqlite3_stmt* stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
306     int rc = sqlite3_reset(stmt);
307     if (rc != SQLITE_OK) {
308         throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt)));
309     }
310 }
311 
312 extern "C" JNIEXPORT void JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeClearBindings(JNIEnv * env,jclass clazz,jlong stmtPointer)313 Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeClearBindings(
314         JNIEnv* env,
315         jclass clazz,
316         jlong stmtPointer) {
317     sqlite3_stmt* stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
318     int rc = sqlite3_clear_bindings(stmt);
319     if (rc != SQLITE_OK) {
320         throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt)));
321     }
322 }
323 
324 extern "C" JNIEXPORT void JNICALL
Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeClose(JNIEnv * env,jclass clazz,jlong stmtPointer)325 Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeClose(
326         JNIEnv* env,
327         jclass clazz,
328         jlong stmtPointer) {
329     sqlite3_stmt* stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
330     sqlite3_finalize(stmt);
331 }
332