Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use UTF-8 functions of SQLite to avoid unexpected data type conversions #84

Merged
merged 1 commit into from
Jul 6, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Use UTF-8 functions of SQLite (instead of UTF-16 functions) to avoid
unexpected data type conversions.

Don't use modified UTF-8, see #61
Convert Java Strings to "unmodified" UTF-8 and vice versa.
Improve out-of-memory handling.

Fixes #78
mkauf committed Jul 4, 2016
commit b8db61dd718dd760354d64c9dc073d6b2eb5445e
445 changes: 265 additions & 180 deletions src/main/java/org/sqlite/core/NativeDB.c
Original file line number Diff line number Diff line change
@@ -70,23 +70,109 @@ static void throwex_msg(JNIEnv *env, const char *str)
(*env)->NewStringUTF(env, str));
}

static void throwex_errorcode_and_msg(JNIEnv *env, int errorCode, const char *str)
static void throwex_outofmemory(JNIEnv *env)
{
static jmethodID mth_throwexmsg = 0;
throwex_msg(env, "Out of memory");
}

if (!mth_throwexmsg) mth_throwexmsg = (*env)->GetStaticMethodID(
env, dbclass, "throwex", "(ILjava/lang/String;)V");
static jbyteArray stringToUtf8ByteArray(JNIEnv *env, jstring str)
{
static jmethodID mth_stringToUtf8ByteArray = 0;

(*env)->CallStaticVoidMethod(env, dbclass, mth_throwexmsg, (jint) errorCode,
(*env)->NewStringUTF(env, str));
jobject result;

if (!mth_stringToUtf8ByteArray) mth_stringToUtf8ByteArray = (*env)->GetStaticMethodID(
env, dbclass, "stringToUtf8ByteArray", "(Ljava/lang/String;)[B");

result = (*env)->CallStaticObjectMethod(env, dbclass, mth_stringToUtf8ByteArray, str);

return (jbyteArray) result;
}

static void throwex_outofmemory(JNIEnv *env)
static jstring utf8ByteArrayToString(JNIEnv *env, jbyteArray utf8bytes)
{
throwex_msg(env, "Out of memory");
static jmethodID mth_utf8ByteArrayToString = 0;

jobject result;

if (!mth_utf8ByteArrayToString) mth_utf8ByteArrayToString = (*env)->GetStaticMethodID(
env, dbclass, "utf8ByteArrayToString", "([B)Ljava/lang/String;");

result = (*env)->CallStaticObjectMethod(env, dbclass, mth_utf8ByteArrayToString, utf8bytes);

return (jstring) result;
}

static jstring utf8BytesToString(JNIEnv *env, const char* bytes, int nbytes)
{
jstring result;
jbyteArray utf8bytes;

if (!bytes)
{
return NULL;
}

utf8bytes = (*env)->NewByteArray(env, (jsize) nbytes);
if (!utf8bytes)
{
throwex_outofmemory(env);
return NULL;
}

(*env)->SetByteArrayRegion(env, utf8bytes, (jsize) 0, (jsize) nbytes, (const jbyte*) bytes);

result = utf8ByteArrayToString(env, utf8bytes);

(*env)->DeleteLocalRef(env, utf8bytes);

return result;
}

static void stringToUtf8Bytes(JNIEnv *env, jstring str, char** bytes, int* nbytes)
{
jbyteArray utf8bytes;
jsize utf8bytes_length;
char* buf;

*bytes = NULL;
if (nbytes) *nbytes = 0;

if (!str)
{
return;
}

utf8bytes = stringToUtf8ByteArray(env, str);
if (!utf8bytes)
{
return;
}

utf8bytes_length = (*env)->GetArrayLength(env, (jarray) utf8bytes);

buf = (char*) malloc(utf8bytes_length + 1);
if (!buf)
{
throwex_outofmemory(env);
return;
}

(*env)->GetByteArrayRegion(env, utf8bytes, 0, utf8bytes_length, (jbyte*)buf);

buf[utf8bytes_length] = '\0';

*bytes = buf;
if (nbytes) *nbytes = (int) utf8bytes_length;
}

static void freeUtf8Bytes(char* bytes)
{
if (bytes)
{
free(bytes);
}
}

static sqlite3 * gethandle(JNIEnv *env, jobject this)
{
@@ -104,14 +190,6 @@ static void sethandle(JNIEnv *env, jobject this, sqlite3 * ref)
(*env)->SetLongField(env, this, pointer, fromref(ref));
}

/* Returns number of 16-bit blocks in UTF-16 string, not including null. */
static jsize jstrlen(const jchar *str)
{
const jchar *s;
for (s = str; *s; s++);
return (jsize)(s - str);
}


// User Defined Function SUPPORT ////////////////////////////////////

@@ -151,9 +229,9 @@ static sqlite3_value * tovalue(JNIEnv *env, jobject function, jint arg)
/* called if an exception occured processing xFunc */
static void xFunc_error(sqlite3_context *context, JNIEnv *env)
{
const jchar *msgstr = 0;
jstring msg = 0;
jint msglength = 0;
char *msg_bytes;
int msg_nbytes;

jclass exclass = 0;
static jmethodID exp_msg = 0;
@@ -170,13 +248,11 @@ static void xFunc_error(sqlite3_context *context, JNIEnv *env)
msg = (jstring)(*env)->CallObjectMethod(env, ex, exp_msg);
if (!msg) { sqlite3_result_error(context, "unknown error", 13); return; }

msglength = (*env)->GetStringLength(env, msg);
msgstr = (*env)->GetStringCritical(env, msg, 0);
if (!msgstr) { sqlite3_result_error_nomem(context); return; }
stringToUtf8Bytes(env, msg, &msg_bytes, &msg_nbytes);
if (!msg_bytes) { sqlite3_result_error_nomem(context); return; }

sqlite3_result_error16(context, msgstr, msglength * sizeof(jchar));

(*env)->ReleaseStringCritical(env, msg, msgstr);
sqlite3_result_error(context, msg_bytes, msg_nbytes);
freeUtf8Bytes(msg_bytes);
}

/* used to call xFunc, xStep and xFinal */
@@ -307,9 +383,9 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
if (!aclass) return JNI_ERR;
aclass = (*env)->NewGlobalRef(env, aclass);

pclass = (*env)->FindClass(env, "org/sqlite/core/DB$ProgressObserver");
if(!pclass) return JNI_ERR;
pclass = (*env)->NewGlobalRef(env, pclass);
pclass = (*env)->FindClass(env, "org/sqlite/core/DB$ProgressObserver");
if(!pclass) return JNI_ERR;
pclass = (*env)->NewGlobalRef(env, pclass);

return JNI_VERSION_1_2;
}
@@ -327,7 +403,7 @@ JNIEXPORT jint JNICALL Java_org_sqlite_core_NativeDB_shared_1cache(
JNIEXPORT jint JNICALL Java_org_sqlite_core_NativeDB_enable_1load_1extension(
JNIEnv *env, jobject this, jboolean enable)
{
return sqlite3_enable_load_extension(gethandle(env, this), enable ? 1 : 0);
return sqlite3_enable_load_extension(gethandle(env, this), enable ? 1 : 0);
}


@@ -336,19 +412,21 @@ JNIEXPORT void JNICALL Java_org_sqlite_core_NativeDB__1open(
{
int ret;
sqlite3 *db = gethandle(env, this);
const char *str;
char *file_bytes;

if (db) {
throwex_msg(env, "DB already open");
sqlite3_close(db);
return;
}

str = (*env)->GetStringUTFChars(env, file, 0);
ret = sqlite3_open_v2(str, &db, flags, NULL);
(*env)->ReleaseStringUTFChars(env, file, str);
stringToUtf8Bytes(env, file, &file_bytes, NULL);
if (!file_bytes) return;

ret = sqlite3_open_v2(file_bytes, &db, flags, NULL);
freeUtf8Bytes(file_bytes);

if (ret) {
if (ret != SQLITE_OK) {
throwex_errorcode(env, this, ret);
sqlite3_close(db);
return;
@@ -364,7 +442,9 @@ JNIEXPORT void JNICALL Java_org_sqlite_core_NativeDB__1close(
JNIEnv *env, jobject this)
{
if (sqlite3_close(gethandle(env, this)) != SQLITE_OK)
{
throwex(env, this);
}
sethandle(env, this, 0);
}

@@ -384,13 +464,15 @@ JNIEXPORT jlong JNICALL Java_org_sqlite_core_NativeDB_prepare(
{
sqlite3* db = gethandle(env, this);
sqlite3_stmt* stmt;
char* sql_bytes;
int sql_nbytes;
int status;

jsize sqllength = (*env)->GetStringLength(env, sql);
const jchar *sqlstr = (*env)->GetStringCritical(env, sql, 0);
if (!sqlstr) { throwex_outofmemory(env); return fromref(0); }
stringToUtf8Bytes(env, sql, &sql_bytes, &sql_nbytes);
if (!sql_bytes) return fromref(0);

int status = sqlite3_prepare16_v2(db, sqlstr, sqllength * sizeof(jchar), &stmt, 0);
(*env)->ReleaseStringCritical(env, sql, sqlstr);
status = sqlite3_prepare_v2(db, sql_bytes, sql_nbytes, &stmt, 0);
freeUtf8Bytes(sql_bytes);

if (status != SQLITE_OK) {
throwex_errorcode(env, this, status);
@@ -399,92 +481,42 @@ JNIEXPORT jlong JNICALL Java_org_sqlite_core_NativeDB_prepare(
return fromref(stmt);
}


JNIEXPORT jint JNICALL Java_org_sqlite_core_NativeDB__1exec(
JNIEnv *env, jobject this, jstring sql)
{
sqlite3* db = gethandle(env, this);
sqlite3_stmt* stmt = 0;
jsize sqllength;
const jchar *sqlstr;
const jchar *sqlstrend;
const jchar *sqlstrstmt;
const jchar *leftover; // Tail of unprocessed SQL
int status = SQLITE_OK;
char* sql_bytes;
int status;

if (!db)
{
throwex_errorcode(env, this, SQLITE_MISUSE);
return SQLITE_MISUSE;
}

sqllength = (*env)->GetStringLength(env, sql);

// Do not use GetStringCritical() here, because SQLite may call
// Java methods while evaluating the SQL query
sqlstr = (*env)->GetStringChars(env, sql, 0);
if (!sqlstr) { throwex_outofmemory(env); return 0; }

sqlstrstmt = sqlstr;
sqlstrend = sqlstr + sqllength;

while (status == SQLITE_OK && sqlstrstmt && sqlstrstmt < sqlstrend)
stringToUtf8Bytes(env, sql, &sql_bytes, NULL);
if (!sql_bytes)
{
status = sqlite3_prepare16_v2(db, sqlstrstmt, (sqlstrend - sqlstrstmt) * sizeof(jchar),
&stmt, (const void**)&leftover);
if (status != SQLITE_OK)
{
continue;
}

if (!stmt)
{
// this happens for a comment or white-space
sqlstrstmt = leftover;
continue;
}

while (1)
{
status = sqlite3_step(stmt);

if (status != SQLITE_ROW)
{
status = sqlite3_finalize(stmt);
stmt = 0;
sqlstrstmt = leftover;

while ( sqlstrstmt && sqlstrstmt < sqlstrend
&& (*sqlstrstmt == ' ' || *sqlstrstmt == '\t' || *sqlstrstmt == '\n'
|| *sqlstrstmt == '\v' || *sqlstrstmt == '\f' || *sqlstrstmt == '\r' ))
{
sqlstrstmt++;
}
break;
}
}
return SQLITE_ERROR;
}

(*env)->ReleaseStringChars(env, sql, sqlstr);
status = sqlite3_exec(db, sql_bytes, 0, 0, NULL);
freeUtf8Bytes(sql_bytes);

if (stmt)
{
sqlite3_finalize(stmt);
}

if (status != SQLITE_OK)
{
if (status != SQLITE_OK) {
throwex_errorcode(env, this, status);
}

return status;
}



JNIEXPORT jstring JNICALL Java_org_sqlite_core_NativeDB_errmsg(JNIEnv *env, jobject this)
{
const jchar *str = (const jchar*) sqlite3_errmsg16(gethandle(env, this));
return str ? (*env)->NewString(env, str, jstrlen(str)) : NULL;
const char *str = (const char*) sqlite3_errmsg(gethandle(env, this));
if (!str) return NULL;
return utf8BytesToString(env, str, strlen(str));
}

JNIEXPORT jstring JNICALL Java_org_sqlite_core_NativeDB_libversion(
@@ -550,59 +582,78 @@ JNIEXPORT jint JNICALL Java_org_sqlite_core_NativeDB_column_1type(
JNIEXPORT jstring JNICALL Java_org_sqlite_core_NativeDB_column_1decltype(
JNIEnv *env, jobject this, jlong stmt, jint col)
{
const jchar *str = (const jchar*) sqlite3_column_decltype16(toref(stmt), col);
return str ? (*env)->NewString(env, str, jstrlen(str)) : NULL;
const char *str = (const char*) sqlite3_column_decltype(toref(stmt), col);
if (!str) return NULL;
return utf8BytesToString(env, str, strlen(str));
}

JNIEXPORT jstring JNICALL Java_org_sqlite_core_NativeDB_column_1table_1name(
JNIEnv *env, jobject this, jlong stmt, jint col)
{
const void *str = sqlite3_column_table_name16(toref(stmt), col);
return str ? (*env)->NewString(env, str, jstrlen(str)) : NULL;
const char *str = sqlite3_column_table_name(toref(stmt), col);
if (!str) return NULL;
return utf8BytesToString(env, str, strlen(str));
}

JNIEXPORT jstring JNICALL Java_org_sqlite_core_NativeDB_column_1name(
JNIEnv *env, jobject this, jlong stmt, jint col)
{
const void *str = sqlite3_column_name16(toref(stmt), col);
return str ? (*env)->NewString(env, str, jstrlen(str)) : NULL;
const char *str = sqlite3_column_name(toref(stmt), col);
if (!str) return NULL;
return utf8BytesToString(env, str, strlen(str));
}

JNIEXPORT jstring JNICALL Java_org_sqlite_core_NativeDB_column_1text(
JNIEnv *env, jobject this, jlong stmt, jint col)
{
const jchar *str = 0;
jint strlength = 0;
const char *bytes;
int nbytes;

str = (const jchar*) sqlite3_column_text16(toref(stmt), col);
strlength = sqlite3_column_bytes16(toref(stmt), col) / sizeof(jchar);
return str ? (*env)->NewString(env, str, strlength) : NULL;
bytes = (const char*) sqlite3_column_text(toref(stmt), col);
nbytes = sqlite3_column_bytes(toref(stmt), col);

if (!bytes && sqlite3_errcode(gethandle(env, this)) == SQLITE_NOMEM)
{
throwex_outofmemory(env);
return NULL;
}

return utf8BytesToString(env, bytes, nbytes);
}

JNIEXPORT jbyteArray JNICALL Java_org_sqlite_core_NativeDB_column_1blob(
JNIEnv *env, jobject this, jlong stmt, jint col)
{
int type;
int length;
jbyteArray jBlob;
jbyte *a;
const void *blob;

// The value returned by sqlite3_column_type() is only meaningful if no type conversions have occurred
int type = sqlite3_column_type(toref(stmt), col);
const void *blob = sqlite3_column_blob(toref(stmt), col);
jsize length = sqlite3_column_bytes(toref(stmt), col);
type = sqlite3_column_type(toref(stmt), col);
blob = sqlite3_column_blob(toref(stmt), col);
if (!blob && sqlite3_errcode(gethandle(env, this)) == SQLITE_NOMEM)
{
throwex_outofmemory(env);
return NULL;
}
if (!blob) {
if (type == SQLITE_NULL) {
return NULL;
} else if (length == 0) {
}
else {
// The return value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer.
return (*env)->NewByteArray(env, 0);
jBlob = (*env)->NewByteArray(env, 0);
if (!jBlob) { throwex_outofmemory(env); return 0; }
return jBlob;
}
}

length = sqlite3_column_bytes(toref(stmt), col);
jBlob = (*env)->NewByteArray(env, length);
if (!jBlob) { throwex_outofmemory(env); return 0; }

a = (*env)->GetPrimitiveArrayCritical(env, jBlob, 0);
memcpy(a, blob, length);
(*env)->ReleasePrimitiveArrayCritical(env, jBlob, a, 0);
(*env)->SetByteArrayRegion(env, jBlob, (jsize) 0, (jsize) length, (const jbyte*) blob);

return jBlob;
}
@@ -652,11 +703,16 @@ JNIEXPORT jint JNICALL Java_org_sqlite_core_NativeDB_bind_1double(
JNIEXPORT jint JNICALL Java_org_sqlite_core_NativeDB_bind_1text(
JNIEnv *env, jobject this, jlong stmt, jint pos, jstring v)
{
jsize vlength = (*env)->GetStringLength(env, v);
const jchar *vstr = (*env)->GetStringCritical(env, v, 0);
if (!vstr) { throwex_outofmemory(env); return 0; }
int rc = sqlite3_bind_text16(toref(stmt), pos, vstr, vlength * sizeof(jchar), SQLITE_TRANSIENT);
(*env)->ReleaseStringCritical(env, v, vstr);
int rc;
char* v_bytes;
int v_nbytes;

stringToUtf8Bytes(env, v, &v_bytes, &v_nbytes);
if (!v_bytes) return SQLITE_ERROR;

rc = sqlite3_bind_text(toref(stmt), pos, v_bytes, v_nbytes, SQLITE_TRANSIENT);
freeUtf8Bytes(v_bytes);

return rc;
}

@@ -682,16 +738,20 @@ JNIEXPORT void JNICALL Java_org_sqlite_core_NativeDB_result_1null(
JNIEXPORT void JNICALL Java_org_sqlite_core_NativeDB_result_1text(
JNIEnv *env, jobject this, jlong context, jstring value)
{
const jchar *valuestr;
jsize valuelength;
char* value_bytes;
int value_nbytes;

if (value == NULL) { sqlite3_result_null(toref(context)); return; }

valuelength = (*env)->GetStringLength(env, value);
valuestr = (*env)->GetStringCritical(env, value, 0);
if (!valuestr) { throwex_outofmemory(env); return; }
sqlite3_result_text16(toref(context), valuestr, valuelength * sizeof(jchar), SQLITE_TRANSIENT);
(*env)->ReleaseStringCritical(env, value, valuestr);
stringToUtf8Bytes(env, value, &value_bytes, &value_nbytes);
if (!value_bytes)
{
sqlite3_result_error_nomem(toref(context));
return;
}

sqlite3_result_text(toref(context), value_bytes, value_nbytes, SQLITE_TRANSIENT);
freeUtf8Bytes(value_bytes);
}

JNIEXPORT void JNICALL Java_org_sqlite_core_NativeDB_result_1blob(
@@ -727,28 +787,26 @@ JNIEXPORT void JNICALL Java_org_sqlite_core_NativeDB_result_1int(
sqlite3_result_int(toref(context), value);
}




JNIEXPORT jstring JNICALL Java_org_sqlite_core_NativeDB_value_1text(
JNIEnv *env, jobject this, jobject f, jint arg)
{
const void *str = 0;
jint strlength = 0;
const char* bytes;
int nbytes;

sqlite3_value *value = tovalue(env, f, arg);
if (!value) return NULL;

str = sqlite3_value_text16(value);
strlength = sqlite3_value_bytes16(value) / sizeof(jchar);
return str ? (*env)->NewString(env, str, strlength) : NULL;
bytes = (const char*) sqlite3_value_text(value);
nbytes = sqlite3_value_bytes(value);

return utf8BytesToString(env, bytes, nbytes);
}

JNIEXPORT jbyteArray JNICALL Java_org_sqlite_core_NativeDB_value_1blob(
JNIEnv *env, jobject this, jobject f, jint arg)
{
jsize length;
int length;
jbyteArray jBlob;
jbyte *a;
const void *blob;
sqlite3_value *value = tovalue(env, f, arg);
if (!value) return NULL;
@@ -760,9 +818,7 @@ JNIEXPORT jbyteArray JNICALL Java_org_sqlite_core_NativeDB_value_1blob(
jBlob = (*env)->NewByteArray(env, length);
if (!jBlob) { throwex_outofmemory(env); return 0; }

a = (*env)->GetPrimitiveArrayCritical(env, jBlob, 0);
memcpy(a, blob, length);
(*env)->ReleasePrimitiveArrayCritical(env, jBlob, a, 0);
(*env)->SetByteArrayRegion(env, jBlob, (jsize) 0, (jsize) length, (const jbyte*) blob);

return jBlob;
}
@@ -799,7 +855,7 @@ JNIEXPORT jint JNICALL Java_org_sqlite_core_NativeDB_create_1function(
JNIEnv *env, jobject this, jstring name, jobject func)
{
jint ret = 0;
const char *strname = 0;
char *name_bytes;
int isAgg = 0;

static jfieldID udfdatalist = 0;
@@ -818,21 +874,20 @@ JNIEXPORT jint JNICALL Java_org_sqlite_core_NativeDB_create_1function(
udf->next = toref((*env)->GetLongField(env, this, udfdatalist));
(*env)->SetLongField(env, this, udfdatalist, fromref(udf));

strname = (*env)->GetStringUTFChars(env, name, 0);
if (!strname) { throwex_outofmemory(env); return 0; }
stringToUtf8Bytes(env, name, &name_bytes, NULL);
if (!name_bytes) { throwex_outofmemory(env); return 0; }

ret = sqlite3_create_function(
gethandle(env, this),
strname, // function name
name_bytes, // function name
-1, // number of args
SQLITE_UTF16, // preferred chars
udf,
isAgg ? 0 :&xFunc,
isAgg ? &xStep : 0,
isAgg ? &xFinal : 0
);

(*env)->ReleaseStringUTFChars(env, name, strname);
freeUtf8Bytes(name_bytes);

return ret;
}
@@ -841,12 +896,15 @@ JNIEXPORT jint JNICALL Java_org_sqlite_core_NativeDB_destroy_1function(
JNIEnv *env, jobject this, jstring name)
{
jint ret = 0;
const char* strname = (*env)->GetStringUTFChars(env, name, 0);
char* name_bytes;

stringToUtf8Bytes(env, name, &name_bytes, NULL);
if (!name_bytes) { throwex_outofmemory(env); return 0; }

ret = sqlite3_create_function(
gethandle(env, this), strname, -1, SQLITE_UTF16, 0, 0, 0, 0
gethandle(env, this), name_bytes, -1, SQLITE_UTF16, 0, 0, 0, 0
);
(*env)->ReleaseStringUTFChars(env, name, strname);
freeUtf8Bytes(name_bytes);

return ret;
}
@@ -938,10 +996,10 @@ void reportProgress(JNIEnv* env, jobject func, int remaining, int pageCount) {
if (!mth) {
mth = (*env)->GetMethodID(env, pclass, "progress", "(II)V");
}

if(!func)
return;

(*env)->CallVoidMethod(env, func, mth, remaining, pageCount);
}

@@ -969,41 +1027,55 @@ void reportProgress(JNIEnv* env, jobject func, int remaining, int pageCount) {
JNIEXPORT jint JNICALL Java_org_sqlite_core_NativeDB_backup(
JNIEnv *env, jobject this,
jstring zDBName,
jstring zFilename, /* Name of file to back up to */
jobject observer /* Progress function to invoke */
jstring zFilename, /* Name of file to back up to */
jobject observer /* Progress function to invoke */
)
{
#if SQLITE_VERSION_NUMBER >= 3006011
int rc; /* Function return code */
sqlite3* pDb; /* Database to back up */
sqlite3* pFile; /* Database connection opened on zFilename */
sqlite3_backup *pBackup; /* Backup handle used to copy data */
const char *dFileName;
const char *dDBName;
char *dFileName;
char *dDBName;

pDb = gethandle(env, this);

dFileName = (*env)->GetStringUTFChars(env, zFilename, 0);
dDBName = (*env)->GetStringUTFChars(env, zDBName, 0);

stringToUtf8Bytes(env, zFilename, &dFileName, NULL);
if (!dFileName)
{
return SQLITE_NOMEM;
}

stringToUtf8Bytes(env, zDBName, &dDBName, NULL);
if (!dDBName)
{
freeUtf8Bytes(dFileName);
return SQLITE_NOMEM;
}

/* Open the database file identified by dFileName. */
rc = sqlite3_open(dFileName, &pFile);
if( rc==SQLITE_OK ){

/* Open the sqlite3_backup object used to accomplish the transfer */
pBackup = sqlite3_backup_init(pFile, "main", pDb, dDBName);
if( pBackup ){
while((rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
while((rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}

/* Release resources allocated by backup_init(). */
(void)sqlite3_backup_finish(pBackup);
}
rc = sqlite3_errcode(pFile);
}

/* Close the database connection opened on database file zFilename
** and return the result of this function. */
(void)sqlite3_close(pFile);

freeUtf8Bytes(dDBName);
freeUtf8Bytes(dFileName);

return rc;
#else
return SQLITE_INTERNAL;
@@ -1013,23 +1085,33 @@ JNIEXPORT jint JNICALL Java_org_sqlite_core_NativeDB_backup(
JNIEXPORT jint JNICALL Java_org_sqlite_core_NativeDB_restore(
JNIEnv *env, jobject this,
jstring zDBName,
jstring zFilename, /* Name of file to back up to */
jobject observer /* Progress function to invoke */
jstring zFilename, /* Name of file to back up to */
jobject observer /* Progress function to invoke */
)
{
#if SQLITE_VERSION_NUMBER >= 3006011
int rc; /* Function return code */
sqlite3* pDb; /* Database to back up */
sqlite3* pFile; /* Database connection opened on zFilename */
sqlite3_backup *pBackup; /* Backup handle used to copy data */
const char *dFileName;
const char *dDBName;
char *dFileName;
char *dDBName;
int nTimeout = 0;

pDb = gethandle(env, this);

dFileName = (*env)->GetStringUTFChars(env, zFilename, 0);
dDBName = (*env)->GetStringUTFChars(env, zDBName, 0);
stringToUtf8Bytes(env, zFilename, &dFileName, NULL);
if (!dFileName)
{
return SQLITE_NOMEM;
}

stringToUtf8Bytes(env, zDBName, &dDBName, NULL);
if (!dDBName)
{
freeUtf8Bytes(dFileName);
return SQLITE_NOMEM;
}

/* Open the database file identified by dFileName. */
rc = sqlite3_open(dFileName, &pFile);
@@ -1038,25 +1120,28 @@ JNIEXPORT jint JNICALL Java_org_sqlite_core_NativeDB_restore(
/* Open the sqlite3_backup object used to accomplish the transfer */
pBackup = sqlite3_backup_init(pDb, dDBName, pFile, "main");
if( pBackup ){
while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK
|| rc==SQLITE_BUSY ){
if( rc==SQLITE_BUSY ){
if( nTimeout++ >= 3 ) break;
sqlite3_sleep(100);
}
}
while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK
|| rc==SQLITE_BUSY ){
if( rc==SQLITE_BUSY ){
if( nTimeout++ >= 3 ) break;
sqlite3_sleep(100);
}
}
/* Release resources allocated by backup_init(). */
(void)sqlite3_backup_finish(pBackup);
}
rc = sqlite3_errcode(pFile);
}

/* Close the database connection opened on database file zFilename
** and return the result of this function. */
(void)sqlite3_close(pFile);

freeUtf8Bytes(dDBName);
freeUtf8Bytes(dFileName);

return rc;
#else
return SQLITE_INTERNAL;
#endif
}

19 changes: 19 additions & 0 deletions src/main/java/org/sqlite/core/NativeDB.java
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@

package org.sqlite.core;

import java.io.UnsupportedEncodingException;
import java.sql.SQLException;

import org.sqlite.Function;
@@ -396,4 +397,22 @@ public native synchronized int restore(String dbName, String sourceFileName, Pro
static void throwex(String msg) throws SQLException {
throw new SQLException(msg);
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These string util methods should move to org.sqlite.util.StringUtils.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. Well that complicates the native implementation, doesn't it.

static byte[] stringToUtf8ByteArray(String str) {
try {
return str.getBytes("UTF-8");
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 is not supported", e);
}
}

static String utf8ByteArrayToString(byte[] utf8bytes) {
try {
return new String(utf8bytes, "UTF-8");
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 is not supported", e);
}
}
}
25 changes: 25 additions & 0 deletions src/test/java/org/sqlite/StatementTest.java
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@

import static org.junit.Assert.*;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.sql.BatchUpdateException;
import java.sql.Connection;
@@ -384,6 +385,30 @@ public void blobTest() throws SQLException {
stat.executeUpdate("CREATE TABLE Foo (KeyId INTEGER, Stuff BLOB)");
}

@Test
public void bytesTest() throws SQLException, UnsupportedEncodingException {
stat.executeUpdate("CREATE TABLE blobs (Blob BLOB)");
PreparedStatement prep = conn.prepareStatement("insert into blobs values(?)");

String str = "This is a test";
byte[] strBytes = str.getBytes("UTF-8");

prep.setBytes(1, strBytes);
prep.executeUpdate();

ResultSet rs = stat.executeQuery("select * from blobs");
assertTrue(rs.next());

byte[] resultBytes = rs.getBytes(1);
assertArrayEquals(strBytes, resultBytes);

String resultStr = rs.getString(1);
assertEquals(str, resultStr);

byte[] resultBytesAfterConversionToString = rs.getBytes(1);
assertArrayEquals(strBytes, resultBytesAfterConversionToString);
}

@Test
public void dateTimeTest() throws SQLException {
Date day = new Date(new java.util.Date().getTime());