1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +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 );