Bind the trace API to the JNI wrapper1 API and add a way to map the native-level db/stmt types to their high-level counterparts (required for translating callbacks such as tracers).

FossilOrigin-Name: 702910e0d1cfc897a269b4fb36b255165958edf529ac9553ebc5155e404d4cd3
This commit is contained in:
stephan 2023-11-04 12:53:00 +00:00
parent 5189ef98d3
commit aad6808efc
5 changed files with 180 additions and 21 deletions

View File

@ -46,7 +46,7 @@ public class Tester1 implements Runnable {
//! True to shuffle the order of the tests.
private static boolean shuffle = false;
//! True to dump the list of to-run tests to stdout.
private static boolean listRunTests = false;
private static int listRunTests = 0;
//! True to squelch all out() and outln() output.
private static boolean quietMode = false;
//! Total number of runTests() calls.
@ -1701,7 +1701,7 @@ public class Tester1 implements Runnable {
mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) );
java.util.Collections.shuffle(mlist);
}
if( listRunTests ){
if( (!fromThread && listRunTests>0) || listRunTests>1 ){
synchronized(this.getClass()){
if( !fromThread ){
out("Initial test"," list: ");
@ -1763,8 +1763,11 @@ public class Tester1 implements Runnable {
-naps: sleep small random intervals between tests in order to add
some chaos for cross-thread contention.
-list-tests: outputs the list of tests being run, minus some
which are hard-coded. This is noisy in multi-threaded mode.
which are hard-coded. In multi-threaded mode, use this twice to
to emit the list run by each thread (which may differ from the initial
list, in particular if -shuffle is used).
-fail: forces an exception to be thrown during the test run. Use
with -shuffle to make its appearance unpredictable.
@ -1793,7 +1796,7 @@ public class Tester1 implements Runnable {
}else if(arg.equals("shuffle")){
shuffle = true;
}else if(arg.equals("list-tests")){
listRunTests = true;
++listRunTests;
}else if(arg.equals("fail")){
forceFail = true;
}else if(arg.equals("sqllog")){

View File

@ -61,11 +61,31 @@ public final class Sqlite implements AutoCloseable {
public static final int PREPARE_NORMALIZE = CApi.SQLITE_PREPARE_NORMALIZE;
public static final int PREPARE_NO_VTAB = CApi.SQLITE_PREPARE_NO_VTAB;
public static final int TRACE_STMT = CApi.SQLITE_TRACE_STMT;
public static final int TRACE_PROFILE = CApi.SQLITE_TRACE_PROFILE;
public static final int TRACE_ROW = CApi.SQLITE_TRACE_ROW;
public static final int TRACE_CLOSE = CApi.SQLITE_TRACE_CLOSE;
public static final int TRACE_ALL = TRACE_STMT | TRACE_PROFILE | TRACE_ROW | TRACE_CLOSE;
//! Used only by the open() factory functions.
private Sqlite(sqlite3 db){
this.db = db;
}
/** Maps org.sqlite.jni.capi.sqlite3 to Sqlite instances. */
private static final java.util.Map<org.sqlite.jni.capi.sqlite3, Sqlite> nativeToWrapper
= new java.util.HashMap<>();
/**
Returns the Sqlite object associated with the given sqlite3
object, or null if there is no such mapping.
*/
static Sqlite fromNative(sqlite3 low){
synchronized(nativeToWrapper){
return nativeToWrapper.get(low);
}
}
/**
Returns a newly-opened db connection or throws SqliteException if
opening fails. All arguments are as documented for
@ -84,7 +104,11 @@ public final class Sqlite implements AutoCloseable {
n.close();
throw ex;
}
return new Sqlite(n);
Sqlite rv = new Sqlite(n);
synchronized(nativeToWrapper){
nativeToWrapper.put(n, rv);
}
return rv;
}
public static Sqlite open(String filename, int flags){
@ -124,6 +148,9 @@ public final class Sqlite implements AutoCloseable {
@Override public void close(){
if(null!=this.db){
synchronized(nativeToWrapper){
nativeToWrapper.remove(this.db);
}
this.db.close();
this.db = null;
}
@ -467,11 +494,71 @@ public final class Sqlite implements AutoCloseable {
return rv;
}
public interface TraceCallback {
/**
Called by sqlite3 for various tracing operations, as per
sqlite3_trace_v2(). Note that this interface elides the 2nd
argument to the native trace callback, as that role is better
filled by instance-local state.
<p>These callbacks may throw, in which case their exceptions are
converted to C-level error information.
<p>The 2nd argument to this function, if non-null, will be a an
Sqlite or Sqlite.Stmt object, depending on the first argument
(see below).
<p>The final argument to this function is the "X" argument
documented for sqlite3_trace() and sqlite3_trace_v2(). Its type
depends on value of the first argument:
<p>- SQLITE_TRACE_STMT: pNative is a Sqlite.Stmt. pX is a String
containing the prepared SQL.
<p>- SQLITE_TRACE_PROFILE: pNative is a sqlite3_stmt. pX is a Long
holding an approximate number of nanoseconds the statement took
to run.
<p>- SQLITE_TRACE_ROW: pNative is a sqlite3_stmt. pX is null.
<p>- SQLITE_TRACE_CLOSE: pNative is a sqlite3. pX is null.
*/
void call(int traceFlag, Object pNative, Object pX);
}
/**
Analog to sqlite3_trace_v2(). traceMask must be a mask of the
TRACE_... constants. Pass a null callback to remove tracing.
Throws on error.
*/
public void trace(int traceMask, TraceCallback callback){
final Sqlite self = this;
final org.sqlite.jni.capi.TraceV2Callback tc =
(null==callback) ? null : new org.sqlite.jni.capi.TraceV2Callback(){
@SuppressWarnings("unchecked")
@Override public int call(int flag, Object pNative, Object pX){
switch(flag){
case TRACE_ROW:
case TRACE_PROFILE:
case TRACE_STMT:
callback.call(flag, Sqlite.Stmt.fromNative((sqlite3_stmt)pNative), pX);
break;
case TRACE_CLOSE:
callback.call(flag, self, pX);
break;
}
return 0;
}
};
checkRc( CApi.sqlite3_trace_v2(thisDb(), traceMask, tc) );
};
/**
Corresponds to the sqlite3_stmt class. Use Sqlite.prepare() to
create new instances.
*/
public final class Stmt implements AutoCloseable {
public static final class Stmt implements AutoCloseable {
private Sqlite _db = null;
private sqlite3_stmt stmt = null;
/**
@ -489,12 +576,29 @@ public final class Sqlite implements AutoCloseable {
this._db = db;
this.stmt = stmt;
this.resultColCount = CApi.sqlite3_column_count(stmt);
synchronized(nativeToWrapper){
nativeToWrapper.put(this.stmt, this);
}
}
sqlite3_stmt nativeHandle(){
return stmt;
}
/** Maps org.sqlite.jni.capi.sqlite3_stmt to Stmt instances. */
private static final java.util.Map<org.sqlite.jni.capi.sqlite3_stmt, Stmt> nativeToWrapper
= new java.util.HashMap<>();
/**
Returns the Stmt object associated with the given sqlite3_stmt
object, or null if there is no such mapping.
*/
static Stmt fromNative(sqlite3_stmt low){
synchronized(nativeToWrapper){
return nativeToWrapper.get(low);
}
}
/**
If this statement is still opened, its low-level handle is
returned, eelse an IllegalArgumentException is thrown.
@ -527,6 +631,9 @@ public final class Sqlite implements AutoCloseable {
public int finalizeStmt(){
int rc = 0;
if( null!=stmt ){
synchronized(nativeToWrapper){
nativeToWrapper.remove(this.stmt);
}
sqlite3_finalize(stmt);
stmt = null;
_db = null;

View File

@ -46,7 +46,7 @@ public class Tester2 implements Runnable {
//! True to shuffle the order of the tests.
private static boolean shuffle = false;
//! True to dump the list of to-run tests to stdout.
private static boolean listRunTests = false;
private static int listRunTests = 0;
//! True to squelch all out() and outln() output.
private static boolean quietMode = false;
//! Total number of runTests() calls.
@ -444,6 +444,54 @@ public class Tester2 implements Runnable {
db.close();
}
private void testTrace(){
final Sqlite db = openDb();
final ValueHolder<Integer> counter = new ValueHolder<>(0);
/* Ensure that characters outside of the UTF BMP survive the trip
from Java to sqlite3 and back to Java. (At no small efficiency
penalty.) */
final String nonBmpChar = "😃";
db.trace(
Sqlite.TRACE_ALL,
new Sqlite.TraceCallback(){
@Override public void call(int traceFlag, Object pNative, Object x){
++counter.value;
//outln("TRACE "+traceFlag+" pNative = "+pNative.getClass().getName());
switch(traceFlag){
case Sqlite.TRACE_STMT:
affirm(pNative instanceof Sqlite.Stmt);
//outln("TRACE_STMT sql = "+x);
affirm(x instanceof String);
affirm( ((String)x).indexOf(nonBmpChar) > 0 );
break;
case Sqlite.TRACE_PROFILE:
affirm(pNative instanceof Sqlite.Stmt);
affirm(x instanceof Long);
//outln("TRACE_PROFILE time = "+x);
break;
case Sqlite.TRACE_ROW:
affirm(pNative instanceof Sqlite.Stmt);
affirm(null == x);
//outln("TRACE_ROW = "+sqlite3_column_text16((sqlite3_stmt)pNative, 0));
break;
case Sqlite.TRACE_CLOSE:
affirm(pNative instanceof Sqlite);
affirm(null == x);
break;
default:
affirm(false /*cannot happen*/);
break;
}
}
});
execSql(db, "SELECT coalesce(null,null,'"+nonBmpChar+"'); "+
"SELECT 'w"+nonBmpChar+"orld'");
affirm( 6 == counter.value );
db.close();
affirm( 7 == counter.value );
}
private void runTests(boolean fromThread) throws Exception {
List<java.lang.reflect.Method> mlist = testMethods;
affirm( null!=mlist );
@ -451,7 +499,7 @@ public class Tester2 implements Runnable {
mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) );
java.util.Collections.shuffle(mlist);
}
if( listRunTests ){
if( (!fromThread && listRunTests>0) || listRunTests>1 ){
synchronized(this.getClass()){
if( !fromThread ){
out("Initial test"," list: ");
@ -514,7 +562,9 @@ public class Tester2 implements Runnable {
some chaos for cross-thread contention.
-list-tests: outputs the list of tests being run, minus some
which are hard-coded. This is noisy in multi-threaded mode.
which are hard-coded. In multi-threaded mode, use this twice to
to emit the list run by each thread (which may differ from the initial
list, in particular if -shuffle is used).
-fail: forces an exception to be thrown during the test run. Use
with -shuffle to make its appearance unpredictable.
@ -543,7 +593,7 @@ public class Tester2 implements Runnable {
}else if(arg.equals("shuffle")){
shuffle = true;
}else if(arg.equals("list-tests")){
listRunTests = true;
++listRunTests;
}else if(arg.equals("fail")){
forceFail = true;
}else if(arg.equals("sqllog")){

View File

@ -1,5 +1,5 @@
C Back\sout\sthe\sALWAYS\sinserted\slate\syesterday.\s\sThe\sfuzzer\sdiscovered\sa\ncounter-example.
D 2023-11-03T18:45:26.322
C Bind\sthe\strace\sAPI\sto\sthe\sJNI\swrapper1\sAPI\sand\sadd\sa\sway\sto\smap\sthe\snative-level\sdb/stmt\stypes\sto\stheir\shigh-level\scounterparts\s(required\sfor\stranslating\scallbacks\ssuch\sas\stracers).
D 2023-11-04T12:53:00.737
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -269,7 +269,7 @@ F ext/jni/src/org/sqlite/jni/capi/SQLFunction.java 0d1e9afc9ff8a2adb94a155b72385
F ext/jni/src/org/sqlite/jni/capi/SQLTester.java 09bee15aa0eedac68d767ae21d9a6a62a31ade59182a3ccbf036d6463d9e30b1
F ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java 93b9700fca4c68075ccab12fe0fbbc76c91cafc9f368e835b9bd7cd7732c8615
F ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java addf120e0e76e5be1ff2260daa7ce305ff9b5fafd64153a7a28e9d8f000a815f
F ext/jni/src/org/sqlite/jni/capi/Tester1.java b6b2f3354ba68956a6bcd1c586b8eb25a0bd66eed2b58b340405e1129da15de9
F ext/jni/src/org/sqlite/jni/capi/Tester1.java fba87e2c39ba186bb7add972d9e84b7f817f656452cf4f317679575bd5a738e7
F ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java 0a25e117a0daae3394a77f24713e36d7b44c67d6e6d30e9e1d56a63442eef723
F ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java 2766b8526bbffc4f1045f70e79f1bc1b1efe1c3e95ca06cdb8a7391032dda3b4
F ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 22d365746a78c5cd7ae10c39444eb7bbf1a819aad4bb7eb77b1edc47773a3950
@ -296,9 +296,9 @@ F ext/jni/src/org/sqlite/jni/test-script-interpreter.md f9f25126127045d051e918fe
F ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java bbe60ac7fd8718edb215a23dc901771bcedb1df3b46d9cf6caff6f419828587f
F ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java 43c43adfb7866098aadaaca1620028a6ec82d5193149970019b1cce9eb59fb03
F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 0b01b9058ef6737c85b505c6aa2490fb1dc1d974fb39d88a93269fed09553f9f
F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java 1c95f5e0f872aeb9cdd174cbb2e254d158df1f8b2fee9f0e6ec82c348602a7bd
F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java 12a9323a74e38e7c6229dc73c5b62bf50088a65310100f383469308549381907
F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java aa85b4b05fae240b14f3d332f9524a2f80c619fb03856be72b4adda866b63b72
F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 9ab7e38e6741842f8e3b74cd3ecb4953e2f1957f5229bd32663df7331245ce95
F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 83cfe6583c8df226eda985eed059f47efaefaca3951c618c286ffc8c63210ee8
F ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java 7b89a7391f771692c5b83b0a5b86266abe8d59f1c77d7a0eccc9b79f259d79af
F ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java 1a1afbafbd7406ff67e7d6405541c6347517c731de535a97d7a3df1d4db835b4
F ext/jni/src/tests/000-000-sanity.test c3427a0e0ac84d7cbe4c95fdc1cd4b61f9ddcf43443408f3000139478c4dc745
@ -2142,9 +2142,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 8f5e9c192ff2820d8cfb076ab28f30697d10c22710583d6c7fd7019c4a0ea795
Q -268b5984a4263bee245a9bb47ac927bde56cdf4af8795b851dada5622224076f
R 4994e654abc4ea7988499736bdf83ad0
U drh
Z 5983c90319f7844a8c9e3e4e981877ce
P 570635575cc5fbffe910ed992b58393e214117ef3b5370a66f115cd0ee202913
R cb275d62547ff275bdfb4b29d97a5241
U stephan
Z 2824e678eb263c5ade76d9ba9fb58525
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
570635575cc5fbffe910ed992b58393e214117ef3b5370a66f115cd0ee202913
702910e0d1cfc897a269b4fb36b255165958edf529ac9553ebc5155e404d4cd3