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

Add Sqlite.prepareMulti() to JNI wrapper1, for preparing multiple statements from a single input.

FossilOrigin-Name: e4670d68b52233ab376a1725983e148aaf2a2c3658a41f5768e37a0f1f87428a
This commit is contained in:
stephan
2023-11-07 15:56:39 +00:00
parent 6dc368e632
commit 488125d4ce
5 changed files with 166 additions and 24 deletions

View File

@ -1202,26 +1202,26 @@ public final class CApi {
*/
public static int sqlite3_prepare_multi(
@NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
int preFlags,
int prepFlags,
@NotNull PrepareMultiCallback p){
final OutputPointer.Int32 oTail = new OutputPointer.Int32();
int pos = 0, n = 1;
byte[] sqlChunk = sqlUtf8;
int rc = 0;
final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
while(0==rc && pos<sqlChunk.length){
while( 0==rc && pos<sqlChunk.length ){
sqlite3_stmt stmt = null;
if(pos > 0){
if( pos>0 ){
sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
sqlChunk.length);
}
if( 0==sqlChunk.length ) break;
rc = sqlite3_prepare_v3(db, sqlChunk, preFlags, outStmt, oTail);
rc = sqlite3_prepare_v3(db, sqlChunk, prepFlags, outStmt, oTail);
if( 0!=rc ) break;
pos = oTail.value;
stmt = outStmt.take();
if( null == stmt ){
// empty statement was parsed.
if( null==stmt ){
// empty statement (whitespace/comments)
continue;
}
rc = p.call(stmt);

View File

@ -510,19 +510,27 @@ public final class Sqlite implements AutoCloseable {
}
/**
Analog to sqlite3_prepare_v3(), this prepares the first SQL
statement from the given input string and returns it as a
Stmt. It throws an SqliteException if preparation fails or an
IllegalArgumentException if the input is empty (e.g. contains
only comments or whitespace).
The first argument must be SQL input in UTF-8 encoding.
prepFlags must be 0 or a bitmask of the PREPARE_... constants.
prepare() TODOs include:
For processing multiple statements from a single input, use
prepareMulti().
- overloads taking byte[] and ByteBuffer.
- multi-statement processing, like CApi.sqlite3_prepare_multi()
but using a callback specific to the higher-level Stmt class
rather than the sqlite3_stmt class.
Design note: though the C-level API succeeds with a null
statement object for empty inputs, that approach is cumbersome to
use in higher-level APIs because every prepared statement has to
be checked for null before using it.
*/
public Stmt prepare(String sql, int prepFlags){
public Stmt prepare(byte utf8Sql[], int prepFlags){
final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
final int rc = CApi.sqlite3_prepare_v3(thisDb(), sql, prepFlags, out);
final int rc = CApi.sqlite3_prepare_v3(thisDb(), utf8Sql, prepFlags, out);
checkRc(rc);
final sqlite3_stmt q = out.take();
if( null==q ){
@ -539,10 +547,113 @@ public final class Sqlite implements AutoCloseable {
return new Stmt(this, q);
}
/**
Equivalent to prepare(X, prepFlags), where X is
sql.getBytes(StandardCharsets.UTF_8).
*/
public Stmt prepare(String sql, int prepFlags){
return prepare( sql.getBytes(StandardCharsets.UTF_8), prepFlags );
}
/**
Equivalent to prepare(sql, 0).
*/
public Stmt prepare(String sql){
return prepare(sql, 0);
}
/**
Callback type for use with prepareMulti().
*/
public interface PrepareMulti {
/**
Gets passed a Stmt which it may handle in arbitrary ways.
Ownership of st is passed to this function. It must throw on
error.
*/
void call(Sqlite.Stmt st);
}
/**
A PrepareMulti implementation which calls another PrepareMulti
object and then finalizes its statement.
*/
public static class PrepareMultiFinalize implements PrepareMulti {
private final PrepareMulti pm;
/**
Proxies the given PrepareMulti via this object's call() method.
*/
public PrepareMultiFinalize(PrepareMulti proxy){
this.pm = proxy;
}
/**
Passes st to the call() method of the object this one proxies,
then finalizes st, propagating any exceptions from call() after
finalizing st.
*/
@Override public void call(Stmt st){
try{ pm.call(st); }
finally{ st.finalizeStmt(); }
}
}
/**
Equivalent to prepareMulti(sql,0,visitor).
*/
public void prepareMulti(String sql, PrepareMulti visitor){
prepareMulti( sql, 0, visitor );
}
/**
A variant of prepare() which can handle multiple SQL statements
in a single input string. For each statement in the given string,
the statement is passed to visitor.call() a single time, passing
ownership of the statement to that function. This function does
not step() or close() statements - those operations are left to
caller or the visitor function.
Unlike prepare(), this function does not fail if the input
contains only whitespace or SQL comments. In that case it is up
to the caller to arrange for that to be an error (if desired).
PrepareMultiFinalize offers a proxy which finalizes each
statement after it is passed to another client-defined visitor.
*/
public void prepareMulti(byte sqlUtf8[], int prepFlags, PrepareMulti visitor){
int pos = 0, n = 1;
byte[] sqlChunk = sqlUtf8;
final org.sqlite.jni.capi.OutputPointer.sqlite3_stmt outStmt =
new org.sqlite.jni.capi.OutputPointer.sqlite3_stmt();
final org.sqlite.jni.capi.OutputPointer.Int32 oTail =
new org.sqlite.jni.capi.OutputPointer.Int32();
while( pos < sqlChunk.length ){
sqlite3_stmt stmt = null;
if( pos>0 ){
sqlChunk = java.util.Arrays.copyOfRange(sqlChunk, pos, sqlChunk.length);
}
if( 0==sqlChunk.length ) break;
checkRc(
CApi.sqlite3_prepare_v3(db, sqlChunk, prepFlags, outStmt, oTail)
);
pos = oTail.value;
stmt = outStmt.take();
if( null==stmt ){
/* empty statement, e.g. only comments or whitespace, was parsed. */
continue;
}
visitor.call(new Stmt(this, stmt));
}
}
/**
Equivallent to prepareMulti(X,prepFlags,visitor), where X is
sql.getBytes(StandardCharsets.UTF_8).
*/
public void prepareMulti(String sql, int prepFlags, PrepareMulti visitor){
prepareMulti(sql.getBytes(StandardCharsets.UTF_8), prepFlags, visitor);
}
public void createFunction(String name, int nArg, int eTextRep, ScalarFunction f){
int rc = CApi.sqlite3_create_function(thisDb(), name, nArg, eTextRep,
new SqlFunction.ScalarAdapter(f));

View File

@ -125,7 +125,7 @@ public class Tester2 implements Runnable {
}
public static void execSql(Sqlite db, String[] sql){
public static void execSql(Sqlite db, String sql[]){
execSql(db, String.join("", sql));
}
@ -938,6 +938,37 @@ public class Tester2 implements Runnable {
db.close();
}
void testPrepareMulti(){
final ValueHolder<Integer> fCount = new ValueHolder<>(0);
final ValueHolder<Integer> mCount = new ValueHolder<>(0);
try (Sqlite db = openDb()) {
execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)");
db.createFunction("counter", -1, new ScalarFunction(){
@Override public void xFunc(SqlFunction.Arguments args){
++fCount.value;
args.resultNull();
}
public void xDestroy(){}
}
);
final Sqlite.PrepareMulti pm = new Sqlite.PrepareMultiFinalize(
new Sqlite.PrepareMulti() {
@Override public void call(Sqlite.Stmt q){
++mCount.value;
while(q.step()){}
}
}
);
final String sql = "select counter(*) from t;"+
"select counter(*) from t; /* comment */"+
"select counter(*) from t; -- comment\n"
;
db.prepareMulti(sql, pm);
}
affirm( 3 == mCount.value );
affirm( 9 == fCount.value );
}
private void runTests(boolean fromThread) throws Exception {
List<java.lang.reflect.Method> mlist = testMethods;
affirm( null!=mlist );

View File

@ -1,5 +1,5 @@
C Diverse\sminor\scleanups\sin\sthe\sJNI\spieces.
D 2023-11-07T13:44:29.266
C Add\sSqlite.prepareMulti()\sto\sJNI\swrapper1,\sfor\spreparing\smultiple\sstatements\sfrom\sa\ssingle\sinput.
D 2023-11-07T15:56:39.576
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -251,7 +251,7 @@ F ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java 0b72cdff61533b564d65b63
F ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java c045a5b47e02bb5f1af91973814a905f12048c428a3504fbc5266d1c1be3de5a
F ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java 74cc4998a73d6563542ecb90804a3c4f4e828cb4bd69e61226d1a51f4646e759
F ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java 7b8e19810c42b0ad21a04b5d8c804b32ee5905d137148703f16a75b612c380ca
F ext/jni/src/org/sqlite/jni/capi/CApi.java 27e3c73685e1068e51331cd083152e0a6357b02278cb67b7c103240dbc262a5d
F ext/jni/src/org/sqlite/jni/capi/CApi.java 170cfd6501f6a4e68073808f3046970a6dd73d2faf478cbc9bd23f159ff3a646
F ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 57e2d275dcebe690b1fc1f3d34eb96879b2d7039bce30b563aee547bf45d8a8b
F ext/jni/src/org/sqlite/jni/capi/CollationCallback.java e29bcfc540fdd343e2f5cca4d27235113f2886acb13380686756d5cabdfd065a
F ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java 5bfa226a8e7a92e804fd52d6e42b4c7b875fa7a94f8e2c330af8cc244a8920ab
@ -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 d5c108b02afd3c63c9e5e53f71f85273c1bfdc461ae526e0a0bb2b25e4df6483
F ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java 43c43adfb7866098aadaaca1620028a6ec82d5193149970019b1cce9eb59fb03
F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 2833afdb9af5c1949bb35f4c926a5351fba9d1cdf0996864caa7b47827a346c7
F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java 6a310fe422d0daf79f7841c9b341f64d843ca7e85ef31829530623a81ecd25fa
F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java 0ef62b43b1d6a9f044e106b56c9ea42bc7150b82ebeb79cff58f5be08cb9a435
F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 982538ddb4c0719ef87dfa664cd137b09890b546029a7477810bd64d4c47ee35
F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java a944fd0a9047a51a5074bffd707452d80cf06e715ebc78b541480ee98629fb93
F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 2ecf37746f2d475a133b530ddaf9ba8a6a65ce238db0805cb8e410ee760d4793
F ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java 7b89a7391f771692c5b83b0a5b86266abe8d59f1c77d7a0eccc9b79f259d79af
F ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java c7d1452f9ff26175b3c19bbf273116cc2846610af68e01756d755f037fe7319f
F ext/jni/src/tests/000-000-sanity.test c3427a0e0ac84d7cbe4c95fdc1cd4b61f9ddcf43443408f3000139478c4dc745
@ -2142,8 +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 fcee41b3d4d2558299ead28cc17f290b9ff1957a84c3feaa0a24872feeb22901
R 2e556ea528a12ae342b294a8936cf3da
P 35233dd900632b997b5e532170a3b2af0ca7f1dccb8407555b93f2b395b0f7b4
R e808fc8487495cabb8d32f4b1af8109a
U stephan
Z 63d82a638a6f714cab2831b5cda37824
Z 28ef3e488a5683b53a25a3e0e53067e7
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
35233dd900632b997b5e532170a3b2af0ca7f1dccb8407555b93f2b395b0f7b4
e4670d68b52233ab376a1725983e148aaf2a2c3658a41f5768e37a0f1f87428a