** Returns the ID of the "nativePointer" field from the given
** NativePointerHolder<T> class.
*/
-static jfieldID NativePointerHolder_field(JNIEnv * const env, S3NphRef const* pRef){
+static jfieldID NativePointerHolder_field(JNIEnv * const env,
+ S3NphRef const* pRef){
S3JniNphClass * const pNC = S3JniGlobal_nph(env, pRef);
if( !pNC->fidValue ){
S3JniMutex_Nph_enter;
if( !pNC->fidValue ){
pNC->fidValue = (*env)->GetFieldID(env, pNC->klazz,
pRef->zMember, pRef->zTypeSig);
- S3JniExceptionIsFatal("Code maintenance required: missing nativePointer field.");
+ S3JniExceptionIsFatal("Code maintenance required: missing "
+ "nativePointer field.");
}
S3JniMutex_Nph_leave;
}
** zClassName must be a static string so we can use its address as a
** cache key. This is a no-op if pObj is NULL.
*/
-static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, S3NphRef const* pRef){
+static void * NativePointerHolder_get(JNIEnv * env, jobject pObj,
+ S3NphRef const* pRef){
if( pObj ){
void * const rv = (void*)(*env)->GetLongField(
env, pObj, NativePointerHolder_field(env, pRef)
}
/*
-** Returns the S3JniDb object for the given db. At most, one of jDb or
-** pDb may be non-NULL.
+** Returns the S3JniDb object for the given org.sqlite.jni.sqlite3
+** object, or NULL if jDb is NULL, no pointer can be extracted
+** from it, or no matching entry can be found.
**
-** The 3rd argument should normally only be non-0 for routines which
-** are called from the C library and pass a native db handle instead of
-** a Java handle. In normal usage, the 2nd argument is a Java-side sqlite3
-** object, from which the db is fished out.
-**
-** If the lockMutex argument is true then the S3JniDb mutex is locked
-** before starting work, else the caller is required to have locked
-** it.
-**
-** Returns NULL if jDb and pDb are both NULL or if there is no
-** matching S3JniDb entry for pDb or the pointer fished out of jDb.
+** Requires locking the S3JniDb mutex.
*/
-static S3JniDb * S3JniDb_get(JNIEnv * const env, jobject jDb, sqlite3 *pDb){
+static S3JniDb * S3JniDb__from_java(JNIEnv * const env, jobject jDb){
S3JniDb * s = 0;
-
- if( 0==jDb && 0==pDb ) return 0;
- assert( jDb ? !pDb : !!pDb );
+ sqlite3 * pDb = 0;
S3JniMutex_S3JniDb_enter;
if( jDb ){
- assert(!pDb);
pDb = PtrGet_sqlite3(jDb);
}
s = SJG.perDb.aHead;
return s;
}
-#define S3JniDb_from_java(jObject) S3JniDb_get(env,(jObject),0)
-#define S3JniDb_from_c(sqlite3Ptr) S3JniDb_get(env,0,(sqlite3Ptr))
+/*
+** Returns the S3JniDb object for the sqlite3 object, or NULL if pDb
+** is NULL, or no matching entry
+** can be found.
+**
+** Requires locking the S3JniDb mutex.
+*/
+static S3JniDb * S3JniDb__from_c(JNIEnv * const env, sqlite3 *pDb){
+ S3JniDb * s = 0;
+ S3JniMutex_S3JniDb_enter;
+ s = SJG.perDb.aHead;
+ for( ; pDb && s; s = s->pNext){
+ if( s->pDb == pDb ){
+ break;
+ }
+ }
+ S3JniMutex_S3JniDb_leave;
+ return s;
+}
+
+#define S3JniDb_from_java(jObject) S3JniDb__from_java(env,(jObject))
+#define S3JniDb_from_c(sqlite3Ptr) S3JniDb__from_c(env,(sqlite3Ptr))
/*
** Unref any Java-side state in (S3JniAutoExtension*) AX and zero out
return (jint)sqlite3_bind_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val);
}
+/*
+** Bind a new global ref to Object `val` using sqlite3_bind_pointer().
+*/
+S3JniApi(sqlite3_bind_java_object(),jint,1bind_1java_1object)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx, jobject val
+){
+ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+ int rc = 0;
+
+ if(pStmt){
+ jobject const rv = val ? S3JniRefGlobal(val) : 0;
+ if( rv ){
+ rc = sqlite3_bind_pointer(pStmt, ndx, rv, ResultJavaValuePtrStr,
+ ResultJavaValue_finalizer);
+ }else if(val){
+ rc = SQLITE_NOMEM;
+ }
+ }else{
+ rc = SQLITE_MISUSE;
+ }
+ return rc;
+}
+
S3JniApi(sqlite3_bind_null(),jint,1bind_1null)(
JniArgsEnvClass, jobject jpStmt, jint ndx
){
S3JniDb * const ps = S3JniDb_from_java(jDb);
assert(version == 1 || version == 2);
if( ps ){
- rc = 1==version ? (jint)sqlite3_close(ps->pDb) : (jint)sqlite3_close_v2(ps->pDb);
+ rc = 1==version
+ ? (jint)sqlite3_close(ps->pDb)
+ : (jint)sqlite3_close_v2(ps->pDb);
if( 0==rc ){
S3JniDb_set_aside(env, ps)
/* MUST come after close() because of ps->trace. */;
JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1int64
(JNIEnv *, jclass, jobject, jint, jlong);
+/*
+ * Class: org_sqlite_jni_SQLite3Jni
+ * Method: sqlite3_bind_java_object
+ * Signature: (Lorg/sqlite/jni/sqlite3_stmt;ILjava/lang/Object;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1java_1object
+ (JNIEnv *, jclass, jobject, jint, jobject);
+
/*
* Class: org_sqlite_jni_SQLite3Jni
* Method: sqlite3_bind_null
@NotNull sqlite3_stmt stmt, int ndx, long v
);
+ /**
+ Binds the given object at the given index.
+
+ @see #sqlite3_result_java_object
+ */
+ public static native int sqlite3_bind_java_object(
+ @NotNull sqlite3_stmt cx, int ndx, @Nullable Object o
+ );
+
public static native int sqlite3_bind_null(
@NotNull sqlite3_stmt stmt, int ndx
);
Works like the C-level sqlite3_bind_text() but assumes
SQLITE_TRANSIENT for the final C API parameter.
- Results are undefined if data is not null and
+ <p>Results are undefined if data is not null and
maxBytes>=data.length. If maxBytes is negative then results are
undefined if data is not null and does not contain a NUL byte.
*/
@NotNull sqlite3_stmt stmt, int ndx
);
- /**
- Column counterpart of sqlite3_value_java_object().
- */
- public static Object sqlite3_column_java_object(
- @NotNull sqlite3_stmt stmt, int ndx
- ){
- Object rv = null;
- sqlite3_value v = sqlite3_column_value(stmt, ndx);
- if(null!=v){
- v = sqlite3_value_dup(v) /* we need a "protected" value */;
- if(null!=v){
- rv = sqlite3_value_java_object(v);
- sqlite3_value_free(v);
- }
- }
- return rv;
- }
-
- /**
- Column counterpart of sqlite3_value_java_casted().
- */
- @SuppressWarnings("unchecked")
- public static <T> T sqlite3_column_java_casted(
- @NotNull sqlite3_stmt stmt, int ndx, @NotNull Class<T> type
- ){
- final Object o = sqlite3_column_java_object(stmt, ndx);
- return type.isInstance(o) ? (T)o : null;
- }
-
public static native String sqlite3_column_origin_name(
@NotNull sqlite3_stmt stmt, int ndx
);
heed. Passing the object to sqlite3_close() or sqlite3_close_v2()
will clear that pointer mapping.
- Recall that even if opening fails, the output pointer might be
+ <p>Recall that even if opening fails, the output pointer might be
non-null. Any error message about the failure will be in that
object and it is up to the caller to sqlite3_close() that
db handle.
object might not have been successfully opened: use sqlite3_errcode() to
check whether it is in an error state.
- Ownership of the returned value is passed to the caller, who must eventually
+ <p>Ownership of the returned value is passed to the caller, who must eventually
pass it to sqlite3_close() or sqlite3_close_v2().
*/
public static sqlite3 sqlite3_open(@Nullable String filename){
retain functionally equivalent semantics and (B) overloading
allows us to install several convenience forms.
- All of them which take their SQL in the form of a byte[] require
+ <p>All of them which take their SQL in the form of a byte[] require
that it be in UTF-8 encoding unless explicitly noted otherwise.
- The forms which take a "tail" output pointer return (via that
+ <p>The forms which take a "tail" output pointer return (via that
output object) the index into their SQL byte array at which the
end of the first SQL statement processed by the call was
found. That's fundamentally how the C APIs work but making use of
/**
Binds the SQL result to the given object, or
{@link #sqlite3_result_null} if {@code o} is null. Use
- {@link #sqlite3_value_java_object(sqlite3_value) sqlite3_value_java_object()} or
- {@link #sqlite3_column_java_object(sqlite3_stmt,int) sqlite3_column_java_object()} to
+ {@link #sqlite3_value_java_object(sqlite3_value) sqlite3_value_java_object()} to
fetch it.
- This is implemented in terms of sqlite3_result_pointer(), but
- that function is not exposed to JNI because its 3rd argument must
- be a constant string (the library does not copy it), which we
- cannot implement cross-language here unless, in the JNI layer, we
- allocate such strings and store them somewhere for long-term use
- (leaking them more likely than not). Even then, passing around a
- pointer via Java like that has little practical use.
+ <p>This is implemented in terms of C's sqlite3_result_pointer(),
+ but that function is not exposed to JNI because its 3rd argument
+ must be a constant string (the library does not copy it), and
+ those semantics are cumbersome to bridge cross-language. Java
+ doesn't need that argument for type safety, in any case: the
+ object can, after extraction on the other end of the API, be
+ inspected with {@code instanceof}.
+
+ <p>Note that there is no sqlite3_column_java_object(), as the
+ C-level API has no sqlite3_column_pointer() to proxy.
- Note that there is no sqlite3_bind_java_object() counterpart.
+ @see #sqlite3_value_java_object
+ @see #sqlite3_bind_java_object
*/
public static native void sqlite3_result_java_object(
@NotNull sqlite3_context cx, @NotNull Object o
/**
Binds the given text using C's sqlite3_result_blob64() unless:
- - @param blob is null ==> sqlite3_result_null()
+ <ul>
+
+ <li>@param blob is null: translates to sqlite3_result_null()</li>
- - @param blob is too large ==> sqlite3_result_error_toobig()
+ <li>@param blob is too large: translates to
+ sqlite3_result_error_toobig()</li>
- If @param maxLen is larger than blob.length, it is truncated to that
- value. If it is negative, results are undefined.
+ </ul>
+
+ If @param maxLen is larger than blob.length, it is truncated to
+ that value. If it is negative, results are undefined.
*/
private static native void sqlite3_result_blob64(
@NotNull sqlite3_context cx, @Nullable byte[] blob, long maxLen
/**
Binds the given text using C's sqlite3_result_text64() unless:
- - text is null: translates to a call to sqlite3_result_null()
+ <ul>
+
+ <li>text is null: translates to a call to sqlite3_result_null()</li>
+
+ <li>text is too large: translates to a call to
+ {@link #sqlite3_result_error_toobig}</li>
- - text is too large: translates to a call to
- sqlite3_result_error_toobig()
+ <li>The @param encoding argument has an invalid value: translates to
+ {@link sqlite3_result_error_code} with code SQLITE_FORMAT.</li>
- - The @param encoding argument has an invalid value: translates to
- sqlite3_result_error_code() with code SQLITE_FORMAT.
+ </ul>
If maxLength (in bytes, not characters) is larger than
text.length, it is silently truncated to text.length. If it is
- negative, results are undefined. If text is null, the following
+ negative, results are undefined. If text is null, the subsequent
arguments are ignored.
*/
private static native void sqlite3_result_text64(
function is elided here because the roles of that functions' 3rd and 4th
arguments are encapsulated in the final argument to this function.
- Unlike the C API, which is documented as always returning 0, this
+ <p>Unlike the C API, which is documented as always returning 0, this
implementation returns non-0 if initialization of the tracer
mapping state fails.
*/
public static native long sqlite3_value_int64(@NotNull sqlite3_value v);
/**
- If the given value was set using sqlite3_result_java_value() then
- this function returns that object, else it returns null.
+ If the given value was set using {@link
+ #sqlite3_result_java_object} then this function returns that
+ object, else it returns null.
- It is up to the caller to inspect the object to determine its
+ <p>It is up to the caller to inspect the object to determine its
type, and cast it if necessary.
*/
public static native Object sqlite3_value_java_object(
private void testUdfJavaObject(){
final sqlite3 db = createNewDb();
final ValueHolder<sqlite3> testResult = new ValueHolder<>(db);
+ final ValueHolder<Integer> boundObj = new ValueHolder<>(42);
final SQLFunction func = new ScalarFunction(){
public void xFunc(sqlite3_context cx, sqlite3_value args[]){
sqlite3_result_java_object(cx, testResult.value);
+ affirm( sqlite3_value_java_object(args[0]) == boundObj );
}
};
int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func);
affirm(0 == rc);
- final sqlite3_stmt stmt = prepare(db, "select myfunc()");
+ sqlite3_stmt stmt = prepare(db, "select myfunc(?)");
affirm( 0 != stmt.getNativePointer() );
affirm( testResult.value == db );
+ rc = sqlite3_bind_java_object(stmt, 1, boundObj);
+ affirm( 0==rc );
int n = 0;
if( SQLITE_ROW == sqlite3_step(stmt) ){
final sqlite3_value v = sqlite3_column_value(stmt, 0);
affirm( 8 == val.value );
}
- @ManualTest /* because we only want to run this test manually */
+ @ManualTest /* we really only want to run this test manually. */
private void testSleep(){
out("Sleeping briefly... ");
sqlite3_sleep(600);
-C Correct\sthe\ssignature\smismatch\sbetween\sJNI\ssqlite3_column/value_text16()\sand\sadd\srelated\stests.
-D 2023-08-27T11:28:57.220
+C Remove\sJNI\ssqlite3_column_java_object(),\sas\sthe\sprotection\srules\sof\ssqlite3_values\smakes\sit\simpossible\sto\simplement\ssafely.\sAdd\sJNI\ssqlite3_bind_java_object().
+D 2023-08-27T13:43:45.514
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
F ext/jni/GNUmakefile 527f7c72360ba081c9ad120a9a00834973dac0115c6272fad94963651ed15bab
F ext/jni/README.md 1332b1fa27918bd5d9ca2d0d4f3ac3a6ab86b9e3699dc5bfe32904a027f3d2a9
F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa
-F ext/jni/src/c/sqlite3-jni.c fd9f52e536528ffaa4a98a8eebd0e15e438b3289e8f00947abcf011d5c8f9afb
-F ext/jni/src/c/sqlite3-jni.h c035d576158137e620da870eef685e6d96ade54565817fe3988fd209514eace1
+F ext/jni/src/c/sqlite3-jni.c 0078fdd79d31db6184a961bdf79d4c9d1138e78f6705bce7e35e17531365fbdf
+F ext/jni/src/c/sqlite3-jni.h 9c57a6e7efd466c4f96b190cea995353ff8897ed38fefb279b42913f352f73a6
F ext/jni/src/org/sqlite/jni/AggregateFunction.java 0a5a74bea5ee12a99407e9432d0ca393525af912c2b0ca55c7ee5dbd019c00ef
F ext/jni/src/org/sqlite/jni/AuthorizerCallback.java c374bb76409cce7a0bdba94877706b59ac6127fa5d9e6af3e8058c99ce99c030
F ext/jni/src/org/sqlite/jni/AutoExtensionCallback.java 4290d8b0937b07d466b50e6ca4136cec037f3ce658277af0d0c2d371e5f4b459
F ext/jni/src/org/sqlite/jni/RollbackHookCallback.java be7f7a26d1102fb514d835e11198d51302af8053d97188bfb2e34c2133208568
F ext/jni/src/org/sqlite/jni/SQLFunction.java d060f302b2cc4cf7a4f5a6b2d36458a2e6fc9648374b5d09c36a43665af41207
F ext/jni/src/org/sqlite/jni/SQLite3CallbackProxy.java 13c4ea6f35871261eba63fa4117715515e0beecbdebfb879ec5b1f340ed36904
-F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 2f45ac5e5fcfc03b8be6d3385a6a5a11fff40ba29735d6fde00b686d878017fe
+F ext/jni/src/org/sqlite/jni/SQLite3Jni.java b22b02b51eab5bab2e9068757c94c0978a7c2ccaf4c2ffa93b3b958c2f9e7dc9
F ext/jni/src/org/sqlite/jni/ScalarFunction.java 21301a947e49f0dd9c682dfe2cc8a6518226c837253dd791cd512f847eeca52c
-F ext/jni/src/org/sqlite/jni/Tester1.java ec5622933b896679a1297db6ed70e0619149913c0043a063c3723ee4645f1c8a
+F ext/jni/src/org/sqlite/jni/Tester1.java ff13dc4babfa7a3f8bf91862c12a7f837db611bb66fcd82f7f8728c3e297a188
F ext/jni/src/org/sqlite/jni/TesterFts5.java 6f135c60e24c89e8eecb9fe61dde0f3bb2906de668ca6c9186bcf34bdaf94629
F ext/jni/src/org/sqlite/jni/TraceV2Callback.java 25a45e800b0c57f506c237d111bcfd09da584e936fee395d4bd802100ebeff8c
F ext/jni/src/org/sqlite/jni/UpdateHookCallback.java f5eadfa44462c050658230884b41477274f34306accd85c8201a7afbc00d2429
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 3f9f7a9cb08b0687ad206605a5109306762df9ae8bdeab2d8d60bf9373c9ad32
-R e2ef8e56417f07ac504c27650de35a3d
+P 77f6e70f17c0cb6d031f983c458c9ec2e88d92b4716397533a029af39da2d128
+R 6ff41c97b17d551a7d85c6dd457042a1
U stephan
-Z 3bfd1e616266221f5a840c6c97fab6d0
+Z 98f8ce6b232c9712186c8841f85ec5e1
# Remove this line to create a well-formed Fossil manifest.
-77f6e70f17c0cb6d031f983c458c9ec2e88d92b4716397533a029af39da2d128
\ No newline at end of file
+29bd4a23a4afd96b2cc06d2b91a4f30c0bbf2347af0b0d18f8d4cf8aafa63160
\ No newline at end of file