1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00

Expose the sqlite3_blob_...() APIs to JNI.

FossilOrigin-Name: 9a9945c405cfe9b6bf399655f498457da66b06c8b92014510ab9fb83fbb1c7d4
This commit is contained in:
stephan
2023-09-03 14:13:29 +00:00
parent 7dfacc9f96
commit 805e8c96b9
8 changed files with 281 additions and 33 deletions

View File

@ -336,6 +336,7 @@ struct S3JniNphOp {
static const struct {
const S3JniNphOp sqlite3;
const S3JniNphOp sqlite3_backup;
const S3JniNphOp sqlite3_blob;
const S3JniNphOp sqlite3_context;
const S3JniNphOp sqlite3_stmt;
const S3JniNphOp sqlite3_value;
@ -343,6 +344,7 @@ static const struct {
const S3JniNphOp OutputPointer_Int32;
const S3JniNphOp OutputPointer_Int64;
const S3JniNphOp OutputPointer_sqlite3;
const S3JniNphOp OutputPointer_sqlite3_blob;
const S3JniNphOp OutputPointer_sqlite3_stmt;
const S3JniNphOp OutputPointer_sqlite3_value;
const S3JniNphOp OutputPointer_String;
@ -363,26 +365,29 @@ static const struct {
#define RefO(INDEX, KLAZZ, SIG) MkRef(INDEX, KLAZZ, "value", SIG)
RefN(0, "sqlite3"),
RefN(1, "sqlite3_backup"),
RefN(2, "sqlite3_context"),
RefN(3, "sqlite3_stmt"),
RefN(4, "sqlite3_value"),
RefO(5, "OutputPointer$Bool", "Z"),
RefO(6, "OutputPointer$Int32", "I"),
RefO(7, "OutputPointer$Int64", "J"),
RefO(8, "OutputPointer$sqlite3",
RefN(2, "sqlite3_blob"),
RefN(3, "sqlite3_context"),
RefN(4, "sqlite3_stmt"),
RefN(5, "sqlite3_value"),
RefO(6, "OutputPointer$Bool", "Z"),
RefO(7, "OutputPointer$Int32", "I"),
RefO(8, "OutputPointer$Int64", "J"),
RefO(9, "OutputPointer$sqlite3",
"Lorg/sqlite/jni/sqlite3;"),
RefO(9, "OutputPointer$sqlite3_stmt",
RefO(10, "OutputPointer$sqlite3_blob",
"Lorg/sqlite/jni/sqlite3_blob;"),
RefO(11, "OutputPointer$sqlite3_stmt",
"Lorg/sqlite/jni/sqlite3_stmt;"),
RefO(10, "OutputPointer$sqlite3_value",
RefO(12, "OutputPointer$sqlite3_value",
"Lorg/sqlite/jni/sqlite3_value;"),
RefO(11, "OutputPointer$String", "Ljava/lang/String;"),
RefO(13, "OutputPointer$String", "Ljava/lang/String;"),
#ifdef SQLITE_ENABLE_FTS5
RefO(12, "OutputPointer$ByteArray", "[B"),
RefN(13, "Fts5Context"),
RefN(14, "Fts5ExtensionApi"),
RefN(15, "fts5_api"),
RefN(16, "fts5_tokenizer"),
RefN(17, "Fts5Tokenizer")
RefO(14, "OutputPointer$ByteArray", "[B"),
RefN(15, "Fts5Context"),
RefN(16, "Fts5ExtensionApi"),
RefN(17, "fts5_api"),
RefN(18, "fts5_tokenizer"),
RefN(19, "Fts5Tokenizer")
#endif
#undef MkRef
#undef RefN
@ -1429,6 +1434,7 @@ static void * NativePointerHolder__get(JNIEnv * env, jobject jNph,
#define PtrGet_T(T,OBJ) (T*)NativePointerHolder_get(OBJ, S3JniNph(T))
#define PtrGet_sqlite3(OBJ) PtrGet_T(sqlite3, OBJ)
#define PtrGet_sqlite3_backup(OBJ) PtrGet_T(sqlite3_backup, OBJ)
#define PtrGet_sqlite3_blob(OBJ) PtrGet_T(sqlite3_blob, OBJ)
#define PtrGet_sqlite3_context(OBJ) PtrGet_T(sqlite3_context, OBJ)
#define PtrGet_sqlite3_stmt(OBJ) PtrGet_T(sqlite3_stmt, OBJ)
#define PtrGet_sqlite3_value(OBJ) PtrGet_T(sqlite3_value, OBJ)
@ -1591,8 +1597,17 @@ static void OutputPointer_set_obj(JNIEnv * const env,
** v.
*/
static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut,
jobject jDb){
OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3), jOut, jDb);
jobject v){
OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3), jOut, v);
}
/*
** Sets the value property of the OutputPointer.sqlite3_blob jOut object to
** v.
*/
static void OutputPointer_set_sqlite3_blob(JNIEnv * const env, jobject const jOut,
jobject v){
OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_blob), jOut, v);
}
/*
@ -1600,8 +1615,8 @@ static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut,
** v.
*/
static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOut,
jobject jStmt){
OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_stmt), jOut, jStmt);
jobject v){
OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_stmt), jOut, v);
}
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
@ -1610,8 +1625,8 @@ static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOu
** v.
*/
static void OutputPointer_set_sqlite3_value(JNIEnv * const env, jobject const jOut,
jobject jValue){
OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_value), jOut, jValue);
jobject v){
OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_value), jOut, v);
}
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
@ -1704,6 +1719,9 @@ static inline jobject new_java_sqlite3(JNIEnv * const env, sqlite3 *sv){
static inline jobject new_java_sqlite3_backup(JNIEnv * const env, sqlite3_backup *sv){
return NativePointerHolder_new(env, S3JniNph(sqlite3_backup), sv);
}
static inline jobject new_java_sqlite3_blob(JNIEnv * const env, sqlite3_blob *sv){
return NativePointerHolder_new(env, S3JniNph(sqlite3_blob), sv);
}
static inline jobject new_java_sqlite3_context(JNIEnv * const env, sqlite3_context *sv){
return NativePointerHolder_new(env, S3JniNph(sqlite3_context), sv);
}
@ -2416,6 +2434,76 @@ S3JniApi(sqlite3_bind_zeroblob(),jint,1bind_1zeroblob64)(
return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_uint64)n);
}
S3JniApi(sqlite3_blob_bytes(),jint,1blob_1bytes)(
JniArgsEnvClass, jobject jBlob
){
return sqlite3_blob_bytes(PtrGet_sqlite3_blob(jBlob));
}
S3JniApi(sqlite3_blob_close(),jint,1blob_1close)(
JniArgsEnvClass, jobject jBlob
){
sqlite3_blob * const b = PtrGet_sqlite3_blob(jBlob);
jint const rc = b ? (jint)sqlite3_blob_close(b) : SQLITE_MISUSE;
if( b ){
NativePointerHolder_set(S3JniNph(sqlite3_blob), jBlob, 0);
}
return rc;
}
S3JniApi(sqlite3_blob_open(),jint,1blob_1open)(
JniArgsEnvClass, jobject jDb, jstring jDbName, jstring jTbl, jstring jCol,
jlong jRowId, jint flags, jobject jOut
){
sqlite3 * const db = PtrGet_sqlite3(jDb);
sqlite3_blob * pBlob = 0;
char * zDbName = 0, * zTableName = 0, * zColumnName = 0;
int rc;
if( !db || !jDbName || !jTbl || !jCol ) return SQLITE_MISUSE;
zDbName = s3jni_jstring_to_utf8(jDbName,0);
zTableName = zDbName ? s3jni_jstring_to_utf8(jTbl,0) : 0;
zColumnName = zTableName ? s3jni_jstring_to_utf8(jCol,0) : 0;
rc = zColumnName
? sqlite3_blob_open(db, zDbName, zTableName, zColumnName,
(sqlite3_int64)jRowId, (int)flags, &pBlob)
: SQLITE_NOMEM;
if( 0==rc ){
jobject rv = new_java_sqlite3_blob(env, pBlob);
if( rv ){
OutputPointer_set_sqlite3_blob(env, jOut, rv);
}else{
sqlite3_blob_close(pBlob);
rc = SQLITE_NOMEM;
}
}
sqlite3_free(zDbName);
sqlite3_free(zTableName);
sqlite3_free(zColumnName);
return rc;
}
S3JniApi(sqlite3_blob_reopen(),jint,1blob_1reopen)(
JniArgsEnvClass, jobject jBlob, jlong iNewRowId
){
return (jint)sqlite3_blob_reopen(PtrGet_sqlite3_blob(jBlob),
(sqlite3_int64)iNewRowId);
}
S3JniApi(sqlite3_blob_write(),jint,1blob_1write)(
JniArgsEnvClass, jobject jBlob, jbyteArray jBa, jint iOffset
){
sqlite3_blob * const b = PtrGet_sqlite3_blob(jBlob);
jbyte * const pBuf = b ? s3jni_jbyteArray_bytes(jBa) : 0;
const jsize nBa = pBuf ? (*env)->GetArrayLength(env, jBa) : 0;
int rc = SQLITE_MISUSE;
if(b && pBuf){
rc = sqlite3_blob_write( b, pBuf, nBa, (int)iOffset );
}
s3jni_jbyteArray_release(jBa, pBuf);
return (jint)rc;
}
/* Central C-to-Java busy handler proxy. */
static int s3jni_busy_handler(void* pState, int n){
S3JniDb * const ps = (S3JniDb *)pState;

View File

@ -947,6 +947,46 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1zeroblob
JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1zeroblob64
(JNIEnv *, jclass, jobject, jint, jlong);
/*
* Class: org_sqlite_jni_SQLite3Jni
* Method: sqlite3_blob_bytes
* Signature: (Lorg/sqlite/jni/sqlite3_blob;)I
*/
JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1blob_1bytes
(JNIEnv *, jclass, jobject);
/*
* Class: org_sqlite_jni_SQLite3Jni
* Method: sqlite3_blob_close
* Signature: (Lorg/sqlite/jni/sqlite3_blob;)I
*/
JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1blob_1close
(JNIEnv *, jclass, jobject);
/*
* Class: org_sqlite_jni_SQLite3Jni
* Method: sqlite3_blob_open
* Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JILorg/sqlite/jni/OutputPointer/sqlite3_blob;)I
*/
JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1blob_1open
(JNIEnv *, jclass, jobject, jstring, jstring, jstring, jlong, jint, jobject);
/*
* Class: org_sqlite_jni_SQLite3Jni
* Method: sqlite3_blob_reopen
* Signature: (Lorg/sqlite/jni/sqlite3_blob;J)I
*/
JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1blob_1reopen
(JNIEnv *, jclass, jobject, jlong);
/*
* Class: org_sqlite_jni_SQLite3Jni
* Method: sqlite3_blob_write
* Signature: (Lorg/sqlite/jni/sqlite3_blob;[BI)I
*/
JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1blob_1write
(JNIEnv *, jclass, jobject, jbyteArray, jint);
/*
* Class: org_sqlite_jni_SQLite3Jni
* Method: sqlite3_busy_handler

View File

@ -64,6 +64,27 @@ public final class OutputPointer {
}
}
/**
Output pointer for sqlite3_blob_open(). These
pointers can only be set by the JNI layer, not by client-level
code.
*/
public static final class sqlite3_blob {
private org.sqlite.jni.sqlite3_blob value;
/** Initializes with a null value. */
public sqlite3_blob(){value = null;}
/** Sets the current value to null. */
public void clear(){value = null;}
/** Returns the current value. */
public final org.sqlite.jni.sqlite3_blob get(){return value;}
/** Equivalent to calling get() then clear(). */
public final org.sqlite.jni.sqlite3_blob take(){
final org.sqlite.jni.sqlite3_blob v = value;
value = null;
return v;
}
}
/**
Output pointer for use with routines, such as sqlite3_prepare(),
which return a statement handle via an output pointer. These

View File

@ -342,6 +342,42 @@ public final class SQLite3Jni {
@NotNull sqlite3_stmt stmt, int ndx, long n
);
@Canonical
public static native int sqlite3_blob_bytes(@NotNull sqlite3_blob blob);
@Canonical
public static native int sqlite3_blob_close(@Nullable sqlite3_blob blob);
@Canonical
public static native int sqlite3_blob_open(
@NotNull sqlite3 db, @NotNull String dbName,
@NotNull String tableName, @NotNull String columnName,
long iRow, int flags, @NotNull OutputPointer.sqlite3_blob out
);
/**
Convenience overload.
*/
public static sqlite3_blob sqlite3_blob_open(
@NotNull sqlite3 db, @NotNull String dbName,
@NotNull String tableName, @NotNull String columnName,
long iRow, int flags ){
final OutputPointer.sqlite3_blob out = new OutputPointer.sqlite3_blob();
sqlite3_blob_open(db, dbName, tableName, columnName, iRow, flags, out);
return out.take();
};
@Canonical
public static native int sqlite3_blob_reopen(
@NotNull sqlite3_blob out, long newRowId
);
@Canonical
public static native int sqlite3_blob_write(
@NotNull sqlite3_blob out, @NotNull byte[] bytes,
int iOffset
);
/**
As for the C-level function of the same name, with a
BusyHandlerCallback instance in place of a callback
@ -1557,6 +1593,16 @@ public final class SQLite3Jni {
@NotNull sqlite3 db, @Nullable UpdateHookCallback hook
);
/*
Note that:
void * sqlite3_user_data(sqlite3_context*)
Is not relevant in the JNI binding, as its feature is replaced by
the ability to pass an object, including any relevant state, to
sqlite3_create_function().
*/
@Canonical
public static native byte[] sqlite3_value_blob(@NotNull sqlite3_value v);

View File

@ -1537,6 +1537,33 @@ public class Tester1 implements Runnable {
affirm( i!=0, "There's a very slight chance that 0 is actually correct." );
}
private void testBlobOpen(){
final sqlite3 db = createNewDb();
execSql(db, "CREATE TABLE T(a BLOB);"
+"INSERT INTO t(a) VALUES(cast('DEF' as text));");
final OutputPointer.sqlite3_blob pOut = new OutputPointer.sqlite3_blob();
int rc = sqlite3_blob_open(db, "main", "t", "a",
sqlite3_last_insert_rowid(db), 1, pOut);
affirm( 0==rc );
final sqlite3_blob b = pOut.take();
affirm( null!=b );
affirm( 0!=b.getNativePointer() );
affirm( 3==sqlite3_blob_bytes(b) );
rc = sqlite3_blob_write( b, new byte[] {100, 101, 102}, 0);
affirm( 0==rc );
rc = sqlite3_blob_close(b);
affirm( 0==rc );
affirm( 0==b.getNativePointer() );
sqlite3_stmt stmt = prepare(db,"SELECT length(a), a FROM t");
affirm( SQLITE_ROW == sqlite3_step(stmt) );
affirm( 3 == sqlite3_column_int(stmt,0) );
affirm( "def".equals(sqlite3_column_text16(stmt,1)) );
sqlite3_finalize(stmt);
sqlite3_close_v2(db);
}
/* Copy/paste/rename this to add new tests. */
private void _testTemplate(){
final sqlite3 db = createNewDb();

View File

@ -0,0 +1,25 @@
/*
** 2023-09-03
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
/**
A wrapper for passing C-level (sqlite3_blob*) instances around in
Java. These wrappers do not own their associated pointer, they
simply provide a type-safe way to communicate it between Java and C
via JNI.
*/
public final class sqlite3_blob extends NativePointerHolder<sqlite3_blob> {
// Only invoked from JNI.
private sqlite3_blob(){}
}