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

Expose sqlite3_stmt_explain(), sqlite3_stmt_isexplain(), and sqlite3_stmt_readonly() to JNI.

FossilOrigin-Name: 208b786afe16eafaf0ce791f319a5e05f733a7b71ce1c542e1b83481b013ec38
This commit is contained in:
stephan
2023-09-01 06:50:17 +00:00
parent 0d3f0a9c11
commit a4b47b034c
7 changed files with 143 additions and 49 deletions

View File

@ -113,14 +113,6 @@
# define SQLITE_THREADSAFE 1
#endif
/*
** 2023-08-25: initial attempts at running with SQLITE_THREADSAFE=0
** lead to as-yet-uninvestigated bad reference errors from JNI.
*/
#if 0 && SQLITE_THREADSAFE==0
# error "This code currently requires SQLITE_THREADSAFE!=0."
#endif
/**********************************************************************/
/* SQLITE_USE_... */
#ifndef SQLITE_USE_URI
@ -132,9 +124,11 @@
** Which sqlite3.c we're using needs to be configurable to enable
** building against a custom copy, e.g. the SEE variant. We have to
** include sqlite3.c, as opposed to sqlite3.h, in order to get access
** to SQLITE_MAX_... and friends. This increases the rebuild time
** considerably but we need this in order to keep the exported values
** of SQLITE_MAX_... and SQLITE_LIMIT_... in sync with the C build.
** to some interal details like SQLITE_MAX_... and friends. This
** increases the rebuild time considerably but we need this in order
** to access some internal functionality and keep the to-Java-exported
** values of SQLITE_MAX_... and SQLITE_LIMIT_... in sync with the C
** build.
*/
#ifndef SQLITE_C
# define SQLITE_C sqlite3.c
@ -452,7 +446,8 @@ struct S3JniDb {
#endif
S3JniDb * pNext /* Next entry in SJG.perDb.aFree */;
};
#define S3JniDb_clientdata_key "S3JniDb"
static const char * const S3JniDb_clientdata_key = "S3JniDb";
#define S3JniDb_from_clientdata(pDb) \
(pDb ? sqlite3_get_clientdata(pDb, S3JniDb_clientdata_key) : 0)
@ -1463,36 +1458,23 @@ static S3JniDb * S3JniDb_alloc(JNIEnv * const env, jobject jDb){
** 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.
**
** Requires locking the S3JniDb mutex.
*/
static S3JniDb * S3JniDb__from_java(JNIEnv * const env, jobject jDb){
S3JniDb * s = 0;
sqlite3 * pDb = 0;
S3JniDb_mutex_enter;
if( jDb ) pDb = PtrGet_sqlite3(jDb);
s = S3JniDb_from_clientdata(pDb);
S3JniDb_mutex_leave;
return s;
}
#define S3JniDb_from_java(jObject) S3JniDb__from_java(env,(jObject))
static S3JniDb * S3JniDb__from_java_unlocked(JNIEnv * const env, jobject jDb){
sqlite3 * pDb = 0;
S3JniDb_mutex_assertLocker;
if( jDb ) pDb = PtrGet_sqlite3(jDb);
return S3JniDb_from_clientdata(pDb);
}
#define S3JniDb_from_java_unlocked(JDB) S3JniDb__from_java_unlocked(env, (JDB))
/*
** S3JniDb finalizer for use with sqlite3_set_clientdata().
*/
static void S3JniDb_xDestroy(void *p){
S3JniDeclLocal_env;
S3JniDb * const ps = p;
assert( !ps->pNext );
assert( !ps->pNext && "Else ps is already in the free-list.");
S3JniDb_set_aside(ps);
}
@ -2012,6 +1994,11 @@ static void udf_xInverse(sqlite3_context* cx, int argc,
JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jobject pStmt, jint n){ \
return (jint)CName(PtrGet_sqlite3_stmt(pStmt), (int)n); \
}
/** Create a trivial JNI wrapper for (boolish-int CName(sqlite3_stmt*)). */
#define WRAP_BOOL_STMT(JniNameSuffix,CName) \
JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jobject pStmt){ \
return CName(PtrGet_sqlite3_stmt(pStmt)) ? JNI_TRUE : JNI_FALSE; \
}
/** Create a trivial JNI wrapper for (jstring CName(sqlite3_stmt*,int)). */
#define WRAP_STR_STMT_INT(JniNameSuffix,CName) \
JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass, jobject pStmt, jint ndx){ \
@ -2061,6 +2048,9 @@ WRAP_INT_DB(1preupdate_1depth, sqlite3_preupdate_depth)
WRAP_INT_INT(1release_1memory, sqlite3_release_memory)
WRAP_INT_INT(1sleep, sqlite3_sleep)
WRAP_MUTF8_VOID(1sourceid, sqlite3_sourceid)
WRAP_INT_STMT_INT(1stmt_1explain, sqlite3_stmt_explain)
WRAP_INT_STMT(1stmt_1isexplain, sqlite3_stmt_isexplain)
WRAP_BOOL_STMT(1stmt_1readonly, sqlite3_stmt_readonly)
WRAP_INT_VOID(1threadsafe, sqlite3_threadsafe)
WRAP_INT_DB(1total_1changes, sqlite3_total_changes)
WRAP_INT64_DB(1total_1changes64, sqlite3_total_changes64)
@ -2513,7 +2503,7 @@ S3JniApi(sqlite3_collation_needed(),jint,1collation_1needed)(
int rc = 0;
S3JniDb_mutex_enter;
ps = S3JniDb_from_java_unlocked(jDb);
ps = S3JniDb_from_java(jDb);
if( !ps ){
S3JniDb_mutex_leave;
return SQLITE_MISUSE;
@ -2655,7 +2645,7 @@ static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,
S3JniHook * pHook;
S3JniDb_mutex_enter;
ps = S3JniDb_from_java_unlocked(jDb);
ps = S3JniDb_from_java(jDb);
if( !ps ){
s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
S3JniDb_mutex_leave;
@ -2878,7 +2868,7 @@ S3JniApi(sqlite3_create_collation() sqlite3_create_collation_v2(),
S3JniDb * ps;
S3JniDb_mutex_enter;
ps = S3JniDb_from_java_unlocked(jDb);
ps = S3JniDb_from_java(jDb);
if( !ps ){
rc = SQLITE_MISUSE;
}else{

View File

@ -1643,6 +1643,30 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1status64
JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1step
(JNIEnv *, jclass, jobject);
/*
* Class: org_sqlite_jni_SQLite3Jni
* Method: sqlite3_stmt_explain
* Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I
*/
JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1stmt_1explain
(JNIEnv *, jclass, jobject, jint);
/*
* Class: org_sqlite_jni_SQLite3Jni
* Method: sqlite3_stmt_isexplain
* Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I
*/
JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1stmt_1isexplain
(JNIEnv *, jclass, jobject);
/*
* Class: org_sqlite_jni_SQLite3Jni
* Method: sqlite3_stmt_readonly
* Signature: (Lorg/sqlite/jni/sqlite3_stmt;)Z
*/
JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1stmt_1readonly
(JNIEnv *, jclass, jobject);
/*
* Class: org_sqlite_jni_SQLite3Jni
* Method: sqlite3_strglob

View File

@ -1325,6 +1325,17 @@ public final class SQLite3Jni {
@Canonical
public static native int sqlite3_step(@NotNull sqlite3_stmt stmt);
@Canonical
public static native int sqlite3_stmt_explain(
@NotNull sqlite3_stmt stmt, int op
);
@Canonical
public static native int sqlite3_stmt_isexplain(@NotNull sqlite3_stmt stmt);
@Canonical
public static native boolean sqlite3_stmt_readonly(@NotNull sqlite3_stmt stmt);
/**
Internal impl of the public sqlite3_strglob() method. Neither
argument may be NULL and both MUST be NUL-terminated UTF-8.

View File

@ -192,7 +192,7 @@ public class Tester1 implements Runnable {
static sqlite3_stmt prepare(sqlite3 db, boolean throwOnError, String sql){
final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
int rc = sqlite3_prepare(db, sql, outStmt);
int rc = sqlite3_prepare_v2(db, sql, outStmt);
if( throwOnError ){
affirm( 0 == rc );
}
@ -203,9 +203,11 @@ public class Tester1 implements Runnable {
}
return rv;
}
static sqlite3_stmt prepare(sqlite3 db, String sql){
return prepare(db, true, sql);
}
private void showCompileOption(){
int i = 0;
String optName;
@ -260,6 +262,7 @@ public class Tester1 implements Runnable {
affirm(0 == rc);
sqlite3_stmt stmt = outStmt.take();
affirm(0 != stmt.getNativePointer());
affirm( !sqlite3_stmt_readonly(stmt) );
affirm( db == sqlite3_db_handle(stmt) );
rc = sqlite3_step(stmt);
if( SQLITE_DONE != rc ){
@ -360,6 +363,7 @@ public class Tester1 implements Runnable {
affirm(sqlite3_changes64(db) > changes64);
affirm(sqlite3_total_changes64(db) > changesT64);
stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
affirm( sqlite3_stmt_readonly(stmt) );
int total2 = 0;
while( SQLITE_ROW == sqlite3_step(stmt) ){
total2 += sqlite3_column_int(stmt, 0);
@ -1362,7 +1366,7 @@ public class Tester1 implements Runnable {
private void testColumnMetadata(){
sqlite3 db = createNewDb();
final sqlite3 db = createNewDb();
execSql(db, new String[] {
"CREATE TABLE t(a duck primary key not null collate noCase); ",
"INSERT INTO t(a) VALUES(1),(2),(3);"
@ -1397,7 +1401,7 @@ public class Tester1 implements Runnable {
}
private void testTxnState(){
sqlite3 db = createNewDb();
final sqlite3 db = createNewDb();
affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
execSql(db, "BEGIN;");
affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
@ -1410,6 +1414,32 @@ public class Tester1 implements Runnable {
sqlite3_close_v2(db);
}
private void testExplain(){
final sqlite3 db = createNewDb();
sqlite3_stmt stmt = prepare(db,"SELECT 1");
affirm( 0 == sqlite3_stmt_isexplain(stmt) );
int rc = sqlite3_stmt_explain(stmt, 1);
affirm( 1 == sqlite3_stmt_isexplain(stmt) );
rc = sqlite3_stmt_explain(stmt, 2);
affirm( 2 == sqlite3_stmt_isexplain(stmt) );
sqlite3_finalize(stmt);
sqlite3_close_v2(db);
}
/* Copy/paste/rename this to add new tests. */
private void _testTemplate(){
final sqlite3 db = createNewDb();
sqlite3_stmt stmt = prepare(db,"SELECT 1");
sqlite3_finalize(stmt);
sqlite3_close_v2(db);
}
@ManualTest /* we really only want to run this test manually. */
private void testSleep(){
out("Sleeping briefly... ");

View File

@ -1,19 +1,58 @@
/**
This package houses a JNI binding to the SQLite3 C API.
<p>The docs are in progress.
<p>The primary interfaces are in {@link
org.sqlite.jni.SQLite3Jni}.</p>
<p>The primary interfaces are in {@link org.sqlite.jni.SQLite3Jni}.
<h1>API Goals and Requirements</h1>
<ul>
<li>A 1-to-1(-ish) mapping of the C API to Java via JNI, insofar
as cross-language semantics allow for. A closely-related goal is
that <a href='https://sqlite.org/c3ref/intro.html'>the C
documentation</a> should be usable as-is, insofar as possible,
for most of the JNI binding. As a rule, undocumented symbols
behave as documented for their C API counterpart, and only
semantic differences are documented here.</li>
<li>Support Java as far back as version 8 (2014).</li>
<li>Environment-independent. Should work everywhere both Java and
SQLite3 do.</li>
<li>No 3rd-party dependencies beyond the JDK. That includes no
build-level dependencies for specific IDEs and toolchains. We
welcome the addition of build files for arbitrary environments
insofar as they neither interfere with each other nor become a
maintenance burden for the sqlite developers.</li>
</ul>
<h2>Non-Goals</h2>
<ul>
<li>Creation of high-level OO wrapper APIs. Clients are free to
create them off of the C-style API.</li>
<li>Support for mixed-mode operation, where client code accesses
SQLite both via the Java-side API and the C API via their own
native code. In such cases, proxy functionalities (primarily
callback handler wrappers of all sorts) may fail because the
C-side use of the SQLite APIs will bypass those proxies.</li>
</ul>
<h1>State of this API</h1>
<p>As of version 3.43, this software is in "tech preview" form. We
tentatively plan to stamp it as stable with the 3.44 release.
tentatively plan to stamp it as stable with the 3.44 release.</p>
<h1>Threading Considerations</h1>
<p>This API is, if built with SQLITE_THREADSAFE set to 1 or 2,
thread-safe, insofar as the C API guarantees, with some addenda:
thread-safe, insofar as the C API guarantees, with some addenda:</p>
<ul>
@ -39,11 +78,11 @@
<p>Any number of threads may, of course, create and use any number
of database handles they wish. Care only needs to be taken when
those handles or their associated resources cross threads, or...
those handles or their associated resources cross threads, or...</p>
<p>When built with SQLITE_THREADSAFE=0 then no threading guarantees
are provided and multi-threaded use of the library will provoke
undefined behavior.
undefined behavior.</p>
*/
package org.sqlite.jni;