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

Add JNI sqlite3_prepare_multi(), based on feedback.

FossilOrigin-Name: fa1c1534724b03debc83ae35c2fadab83faf4b4e62b91981fed103888de41396
This commit is contained in:
stephan
2023-09-13 17:11:32 +00:00
parent fef1c11f92
commit 181063d477
6 changed files with 209 additions and 12 deletions

View File

@ -0,0 +1,75 @@
/*
** 2023-09-13
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
/**
Callback for use with {@link SQLite3Jni#sqlite3_prepare_multi}.
*/
public interface PrepareMultiCallback extends CallbackProxy {
/**
Gets passed a which it may handle in arbitrary
ways, transfering ownership of it to this function.
sqlite3_prepare_multi() will _not_ finalize st - it is up
to the call() implementation how st is handled.
Must return 0 on success or an SQLITE_... code on error.
See the {@link Finalize} class for a wrapper which finalizes the
statement after calling a proxy PrepareMultiCallback.
*/
int call(sqlite3_stmt st);
/**
A PrepareMultiCallback impl which wraps a separate impl and finalizes
any sqlite3_stmt passed to its callback.
*/
public static final class Finalize implements PrepareMultiCallback {
private PrepareMultiCallback p;
public Finalize( PrepareMultiCallback p ){
this.p = p;
}
/**
Calls the call() method of the proxied callback and either returns its
result or propagates an exception. Either way, it passes its argument to
sqlite3_finalize().
*/
@Override public int call(sqlite3_stmt st){
try {
return this.p.call(st);
}finally{
SQLite3Jni.sqlite3_finalize(st);
}
}
}
/**
A PrepareMultiCallback impl which steps entirely through a result set,
ignoring all non-error results.
*/
public static final class StepAll implements PrepareMultiCallback {
public StepAll(){}
/**
Calls sqlite3_step() on st until it returns something other than
SQLITE_ROW. If the final result is SQLITE_DONE then 0 is returned,
else the result of the final step is returned.
*/
@Override public int call(sqlite3_stmt st){
int rc = SQLite3Jni.SQLITE_DONE;
while( SQLite3Jni.SQLITE_ROW == (rc = SQLite3Jni.sqlite3_step(st)) ){}
return SQLite3Jni.SQLITE_DONE==rc ? 0 : rc;
}
}
}

View File

@ -19,6 +19,7 @@ import java.lang.annotation.Target;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import org.sqlite.jni.annotation.*;
import java.util.Arrays;
/**
This class contains the entire C-style sqlite3 JNI API binding,
@ -907,7 +908,6 @@ public final class SQLite3Jni {
sqlite3_prepare(db, sql, out);
return out.take();
}
/**
@see #sqlite3_prepare
*/
@ -1010,6 +1010,103 @@ public final class SQLite3Jni {
return out.take();
}
/**
A convenience wrapper around sqlite3_prepare_v3() which accepts
an arbitrary amount of input provided as a UTF-8-encoded byte
array. It loops over the input bytes looking for
statements. Each one it finds is passed to p.call(), passing
ownership of it to that function. If p.call() returns 0, looping
continues, else the loop stops.
If p.call() throws, the exception is propagated.
How each statement is handled, including whether it is finalized
or not, is up to the callback object. e.g. the callback might
collect them for later use. If it does not collect them then it
must finalize them. See PrepareMultiCallback.Finalize for a
simple proxy which does that.
*/
public static int sqlite3_prepare_multi(
@NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
int preFlags,
@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){
sqlite3_stmt stmt = null;
if(pos > 0){
sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
sqlChunk.length);
}
if( 0==sqlChunk.length ) break;
rc = sqlite3_prepare_v3(db, sqlChunk, preFlags, outStmt, oTail);
if( 0!=rc ) break;
pos = oTail.value;
stmt = outStmt.take();
if( null == stmt ){
// empty statement was parsed.
continue;
}
rc = p.call(stmt);
}
return rc;
}
/**
Convenience overload which accepts its SQL as a String and uses
no statement-preparation flags.
*/
public static int sqlite3_prepare_multi(
@NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
@NotNull PrepareMultiCallback p){
return sqlite3_prepare_multi(db, sqlUtf8, 0, p);
}
/**
Convenience overload which accepts its SQL as a String.
*/
public static int sqlite3_prepare_multi(
@NotNull sqlite3 db, @NotNull String sql, int prepFlags,
@NotNull PrepareMultiCallback p){
return sqlite3_prepare_multi(
db, sql.getBytes(StandardCharsets.UTF_8), prepFlags, p
);
}
/**
Convenience overload which accepts its SQL as a String and uses
no statement-preparation flags.
*/
public static int sqlite3_prepare_multi(
@NotNull sqlite3 db, @NotNull String sql,
@NotNull PrepareMultiCallback p){
return sqlite3_prepare_multi(db, sql, 0, p);
}
/**
Convenience overload which accepts its SQL as a String
array. They will be concatenated together as-is, with no
separator, and passed on to one of the other overloads.
*/
public static int sqlite3_prepare_multi(
@NotNull sqlite3 db, @NotNull String[] sql, int prepFlags,
@NotNull PrepareMultiCallback p){
return sqlite3_prepare_multi(db, String.join("",sql), prepFlags, p);
}
/**
Convenience overload which uses no statement-preparation flags.
*/
public static int sqlite3_prepare_multi(
@NotNull sqlite3 db, @NotNull String[] sql,
@NotNull PrepareMultiCallback p){
return sqlite3_prepare_multi(db, sql, 0, p);
}
/**
If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
acts as a proxy for C's sqlite3_preupdate_blobwrite(), else it returns

View File

@ -1594,11 +1594,34 @@ public class Tester1 implements Runnable {
sqlite3_close_v2(db);
}
private void testPrepareMulti(){
final sqlite3 db = createNewDb();
final String[] sql = {
"create table t(a);",
"insert into t(a) values(1),(2),(3);",
"select a from t;"
};
final List<sqlite3_stmt> liStmt = new ArrayList<sqlite3_stmt>();
final PrepareMultiCallback proxy = new PrepareMultiCallback.StepAll();
PrepareMultiCallback m = new PrepareMultiCallback() {
@Override public int call(sqlite3_stmt st){
liStmt.add(st);
return proxy.call(st);
}
};
int rc = sqlite3_prepare_multi(db, sql, m);
affirm( 0==rc );
affirm( liStmt.size() == 3 );
for( sqlite3_stmt st : liStmt ){
sqlite3_finalize(st);
}
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);
}