public final class Stmt implements AutoCloseable {
private Sqlite _db = null;
private sqlite3_stmt stmt = null;
+ /**
+ We save the result column count in order to prevent having to
+ call into C to fetch that value every time we need to check
+ that value for the columnXyz() methods.
+ */
+ private final int resultColCount;
+
/** Only called by the prepare() factory functions. */
Stmt(Sqlite db, sqlite3_stmt stmt){
this._db = db;
this.stmt = stmt;
+ this.resultColCount = CApi.sqlite3_column_count(stmt);
}
sqlite3_stmt nativeHandle(){
return stmt;
}
+ /** Throws if n is out of range of this.resultColCount. Intended
+ to be used by the columnXyz() methods. */
+ private sqlite3_stmt checkColIndex(int n){
+ if(n<0 || n>=this.resultColCount){
+ throw new IllegalArgumentException("Column index "+n+" is out of range.");
+ }
+ return thisStmt();
+ }
+
/**
Corresponds to sqlite3_finalize(), but we cannot override the
name finalize() here because this one requires a different
*/
public int step(){
return checkRc(sqlite3_step(thisStmt()));
+ /*
+ Potential signature change TODO:
+
+ boolean step()
+
+ Returning true for SQLITE_ROW and false for anything else.
+ Those semantics have proven useful in the WASM/JS bindings.
+ */
}
public Sqlite db(){ return this._db; }
checkRc(CApi.sqlite3_bind_blob(thisStmt(), ndx, bytes));
}
+ public byte[] columnBlob(int ndx){
+ return CApi.sqlite3_column_blob( checkColIndex(ndx), ndx );
+ }
+ public byte[] columnText(int ndx){
+ return CApi.sqlite3_column_text( checkColIndex(ndx), ndx );
+ }
+ public String columnText16(int ndx){
+ return CApi.sqlite3_column_text16( checkColIndex(ndx), ndx );
+ }
+ public int columnBytes(int ndx){
+ return CApi.sqlite3_column_bytes( checkColIndex(ndx), ndx );
+ }
+ public int columnBytes16(int ndx){
+ return CApi.sqlite3_column_bytes16( checkColIndex(ndx), ndx );
+ }
+ public int columnInt(int ndx){
+ return CApi.sqlite3_column_int( checkColIndex(ndx), ndx );
+ }
+ public long columnInt64(int ndx){
+ return CApi.sqlite3_column_int64( checkColIndex(ndx), ndx );
+ }
+ public double columnDouble(int ndx){
+ return CApi.sqlite3_column_double( checkColIndex(ndx), ndx );
+ }
+ public int columnType(int ndx){
+ return CApi.sqlite3_column_type( checkColIndex(ndx), ndx );
+ }
+ public String columnDeclType(int ndx){
+ return CApi.sqlite3_column_decltype( checkColIndex(ndx), ndx );
+ }
+ public int columnCount(){
+ return resultColCount;
+ }
+ public int columnDataCount(){
+ return CApi.sqlite3_data_count( thisStmt() );
+ }
+ public String columnName(int ndx){
+ return CApi.sqlite3_column_name( checkColIndex(ndx), ndx );
+ }
+ public String columnDatabaseName(int ndx){
+ return CApi.sqlite3_column_database_name( checkColIndex(ndx), ndx );
+ }
+ public String columnOriginName(int ndx){
+ return CApi.sqlite3_column_origin_name( checkColIndex(ndx), ndx );
+ }
+ public String columnTableName(int ndx){
+ return CApi.sqlite3_column_table_name( checkColIndex(ndx), ndx );
+ }
} /* Stmt class */
}
affirm( 1==stmt.bindParameterCount() );
affirm( "?1".equals(stmt.bindParameterName(1)) );
affirm( null==stmt.bindParameterName(2) );
- stmt.bindInt(1, 1);
stmt.bindInt64(1, 1);
stmt.bindDouble(1, 1.1);
stmt.bindObject(1, db);
stmt.bindText16(1, "123");
stmt.bindZeroBlob(1, 8);
stmt.bindBlob(1, new byte[] {1,2,3,4});
+ stmt.bindInt(1, 17);
try{ stmt.bindInt(2,1); }
catch(Exception ex){ e = ex; }
affirm( null!=e );
+ e = null;
affirm( CApi.SQLITE_ROW == stmt.step() );
+ try{ stmt.columnInt(1); }
+ catch(Exception ex){ e = ex; }
+ affirm( null!=e );
+ e = null;
+ affirm( 17 == stmt.columnInt(0) );
+ affirm( 17L == stmt.columnInt64(0) );
+ affirm( 17.0 == stmt.columnDouble(0) );
+ affirm( "17".equals(stmt.columnText16(0)) );
affirm( CApi.SQLITE_DONE == stmt.step() );
stmt.reset();
affirm( CApi.SQLITE_ROW == stmt.step() );
}
void testUdfAggregate(){
- /* FIXME/TODO: once we've added the stmt bind/step/fetch
- capabilities, go back and extend these tests to correspond to
- the aggregate UDF tests in ext/wasm/tester1.c-pp.js. We first
- require the ability to bind/step/fetch, however. */
final ValueHolder<Integer> xDestroyCalled = new ValueHolder<>(0);
- final ValueHolder<Integer> vh = new ValueHolder<>(0);
+ Sqlite.Stmt q = null;
try (Sqlite db = openDb()) {
execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)");
final AggregateFunction f = new AggregateFunction<Integer>(){
final Integer v = this.takeAggregateState(args);
if( null==v ) args.resultNull();
else args.resultInt(v);
- vh.value = v;
}
public void xDestroy(){
++xDestroyCalled.value;
}
};
- db.createFunction("myagg", -1, f);
- execSql(db, "select myagg(a) from t");
- affirm( 6 == vh.value );
+ db.createFunction("summer", 1, f);
+ q = db.prepare(
+ "with cte(v) as ("+
+ "select 3 union all select 5 union all select 7"+
+ ") select summer(v), summer(v+1) from cte"
+ /* ------------------^^^^^^^^^^^ ensures that we're handling
+ sqlite3_aggregate_context() properly. */
+ );
+ affirm( CApi.SQLITE_ROW==q.step() );
+ affirm( 15==q.columnInt(0) );
+ q.finalizeStmt();
+ q = null;
affirm( 0 == xDestroyCalled.value );
+ db.createFunction("summerN", -1, f);
+
+ q = db.prepare("select summerN(1,8,9), summerN(2,3,4)");
+ affirm( CApi.SQLITE_ROW==q.step() );
+ affirm( 18==q.columnInt(0) );
+ affirm( 9==q.columnInt(1) );
+ q.finalizeStmt();
+ q = null;
+
+ }/*db*/
+ finally{
+ if( null!=q ) q.finalizeStmt();
}
- affirm( 1 == xDestroyCalled.value );
+ affirm( 2 == xDestroyCalled.value
+ /* because we've bound the same instance twice */ );
}
private void runTests(boolean fromThread) throws Exception {
-C Add\sAPI_ARMOR\sto\ssqlite3_clear_bindings().
-D 2023-10-22T13:09:37.276
+C JNI:\sadd\scolumn-get\sbindings\sto\sthe\swrapper1\sStmt\sclass\sand\sextend\sthe\sAggregateFunction\stests\sto\sensure\sthat\sthe\saggregate\scontext\sis\shonored.
+D 2023-10-22T13:54:26.716
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
F ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java 5ad99bd74c85f56bbef324d9ec29b4048f4620547c9a80093d8586c3557f9f9a
F ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java 43c43adfb7866098aadaaca1620028a6ec82d5193149970019b1cce9eb59fb03
F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 004394eeb944baa56e36cd7ae69ba6d4a52b52db3c49439db16e98270b861421
-F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java 25e4e65ba434d0e110e4adb6782e20cd374d83b7fe00ba5ca48a1dadd2fdd7dd
+F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java b0ee72b60b2a15d3f19571213f62e560134d6517eacece73eb1299b6d9980f14
F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 1386f7b753134fc12253ce2fbbc448ba8c970567fac01a3356cb672e14408d73
-F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 65437e09115bfef4445957db61fcf6dac9aad37ac00edb445c3de812e9750d6e
+F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 5ff33f3e31f5d88178e485ab334e8fb6ccf8ad1f6666136b4a613fec58888432
F ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java 7b89a7391f771692c5b83b0a5b86266abe8d59f1c77d7a0eccc9b79f259d79af
F ext/jni/src/tests/000-000-sanity.test c3427a0e0ac84d7cbe4c95fdc1cd4b61f9ddcf43443408f3000139478c4dc745
F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 54fce9bf04a7517cdc8e96fe2efec66f03b0d03983c3a45d0ae7e1f16aa5a6c9
-R 17c045c95304ed4e3d6135c84273c496
+P f3fb4d345bbf5ae4a35d8076043df601b1bf7dfd68760a416440139eb3e5eb9a
+R 4d4dd7480ffdece828c7f766b6d86d96
U stephan
-Z 1cc1c3ab8b0755073c3409e264c96da6
+Z e88e3b32c9f23fead743fdff8331a212
# Remove this line to create a well-formed Fossil manifest.
-f3fb4d345bbf5ae4a35d8076043df601b1bf7dfd68760a416440139eb3e5eb9a
\ No newline at end of file
+60a0e82db26270af9d0a5f55c6173e4fd0bdc90a885e838480ed75f8ef193287
\ No newline at end of file