aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/fts5/fts5_aux.c8
-rw-r--r--ext/fts5/fts5_hash.c6
-rw-r--r--ext/fts5/fts5_index.c10
-rw-r--r--ext/fts5/test/fts5prefix2.test30
-rw-r--r--ext/fts5/test/fts5tokenizer2.test89
-rw-r--r--ext/jni/GNUmakefile3
-rw-r--r--ext/jni/src/c/sqlite3-jni.c211
-rw-r--r--ext/jni/src/c/sqlite3-jni.h14
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java3
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/CApi.java211
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java5
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java7
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java3
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java3
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java5
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/Tester1.java117
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java3
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/ValueHolder.java5
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/sqlite3.java2
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java2
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java4
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java31
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java1293
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java4
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java590
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java4
-rw-r--r--ext/wasm/tester1.c-pp.js24
-rw-r--r--manifest96
-rw-r--r--manifest.uuid2
-rw-r--r--src/date.c6
-rw-r--r--src/json.c66
-rw-r--r--src/os_unix.c15
-rw-r--r--src/select.c2
-rw-r--r--src/shell.c.in8
-rw-r--r--src/sqlite.h.in14
-rw-r--r--src/sqliteInt.h10
-rw-r--r--src/vdbe.c2
-rw-r--r--src/vdbeaux.c2
-rw-r--r--src/vdbemem.c2
-rw-r--r--src/wal.c7
-rw-r--r--test/date.test5
-rw-r--r--test/releasetest_data.tcl845
-rw-r--r--test/snapshot_up.test2
-rw-r--r--test/testrunner_data.tcl1
-rw-r--r--test/wapp.tcl987
-rwxr-xr-xtest/wapptest.tcl909
-rw-r--r--tool/srctree-check.tcl15
47 files changed, 2462 insertions, 3221 deletions
diff --git a/ext/fts5/fts5_aux.c b/ext/fts5/fts5_aux.c
index fa58b9aac..b6919fc9c 100644
--- a/ext/fts5/fts5_aux.c
+++ b/ext/fts5/fts5_aux.c
@@ -211,6 +211,14 @@ static int fts5HighlightCb(
}
if( iPos==p->iRangeEnd ){
+ if( p->bOpen ){
+ if( p->iter.iStart>=0 && iPos>=p->iter.iStart ){
+ fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff);
+ p->iOff = iEndOff;
+ }
+ fts5HighlightAppend(&rc, p, p->zClose, -1);
+ p->bOpen = 0;
+ }
fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff);
p->iOff = iEndOff;
}
diff --git a/ext/fts5/fts5_hash.c b/ext/fts5/fts5_hash.c
index 7e50c3660..391791c7a 100644
--- a/ext/fts5/fts5_hash.c
+++ b/ext/fts5/fts5_hash.c
@@ -432,10 +432,8 @@ static Fts5HashEntry *fts5HashEntryMerge(
}
/*
-** Extract all tokens from hash table iHash and link them into a list
-** in sorted order. The hash table is cleared before returning. It is
-** the responsibility of the caller to free the elements of the returned
-** list.
+** Link all tokens from hash table iHash into a list in sorted order. The
+** tokens are not removed from the hash table.
*/
static int fts5HashEntrySort(
Fts5Hash *pHash,
diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c
index 4e6afb281..c7c02cf6f 100644
--- a/ext/fts5/fts5_index.c
+++ b/ext/fts5/fts5_index.c
@@ -2719,6 +2719,14 @@ static void fts5SegIterHashInit(
pLeaf->p = (u8*)pList;
}
}
+
+ /* The call to sqlite3Fts5HashScanInit() causes the hash table to
+ ** fill the size field of all existing position lists. This means they
+ ** can no longer be appended to. Since the only scenario in which they
+ ** can be appended to is if the previous operation on this table was
+ ** a DELETE, by clearing the Fts5Index.bDelete flag we can avoid this
+ ** possibility altogether. */
+ p->bDelete = 0;
}else{
p->rc = sqlite3Fts5HashQuery(p->pHash, sizeof(Fts5Data),
(const char*)pTerm, nTerm, (void**)&pLeaf, &nList
@@ -6204,7 +6212,7 @@ int sqlite3Fts5IndexBeginWrite(Fts5Index *p, int bDelete, i64 iRowid){
/* Flush the hash table to disk if required */
if( iRowid<p->iWriteRowid
|| (iRowid==p->iWriteRowid && p->bDelete==0)
- || (p->nPendingData > p->pConfig->nHashSize)
+ || (p->nPendingData > p->pConfig->nHashSize)
){
fts5IndexFlush(p);
}
diff --git a/ext/fts5/test/fts5prefix2.test b/ext/fts5/test/fts5prefix2.test
index bf16e81a7..29744c86b 100644
--- a/ext/fts5/test/fts5prefix2.test
+++ b/ext/fts5/test/fts5prefix2.test
@@ -52,6 +52,36 @@ do_execsql_test 2.1 {
SELECT * FROM t2('to*');
} {top to tommy}
+#-------------------------------------------------------------------------
+
+foreach {tn newrowid} {
+ 1 122
+ 2 123
+ 3 124
+} {
+ reset_db
+ do_execsql_test 3.$tn.0 {
+ CREATE VIRTUAL TABLE t12 USING fts5(x);
+ INSERT INTO t12(rowid, x) VALUES(123, 'wwww');
+ }
+ do_execsql_test 3.$tn.1 {
+ BEGIN;
+ DELETE FROM t12 WHERE rowid=123;
+ SELECT * FROM t12('wwww*');
+ INSERT INTO t12(rowid, x) VALUES($newrowid, 'wwww');
+ SELECT * FROM t12('wwww*');
+ END;
+ } {wwww}
+ do_execsql_test 3.$tn.2 {
+ INSERT INTO t12(t12) VALUES('integrity-check');
+ }
+ do_execsql_test 3.$tn.3 {
+ SELECT rowid FROM t12('wwww*');
+ } $newrowid
+}
+
+finish_test
+
finish_test
diff --git a/ext/fts5/test/fts5tokenizer2.test b/ext/fts5/test/fts5tokenizer2.test
new file mode 100644
index 000000000..bdabd5312
--- /dev/null
+++ b/ext/fts5/test/fts5tokenizer2.test
@@ -0,0 +1,89 @@
+# 2023 Nov 03
+#
+# 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.
+#
+#***********************************************************************
+#
+# Tests focusing on the built-in fts5 tokenizers.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5tokenizer2
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+sqlite3_fts5_create_tokenizer db tst get_tst_tokenizer
+proc get_tst_tokenizer {args} {
+ return "tst_tokenizer"
+}
+proc tst_tokenizer {flags txt} {
+ set token ""
+ set lTok [list]
+
+ foreach c [split $txt {}] {
+ if {$token==""} {
+ append token $c
+ } else {
+ set t1 [string is upper $token]
+ set t2 [string is upper $c]
+
+ if {$t1!=$t2} {
+ lappend lTok $token
+ set token ""
+ }
+ append token $c
+ }
+ }
+ if {$token!=""} { lappend lTok $token }
+
+ set iOff 0
+ foreach t $lTok {
+ set n [string length $t]
+ sqlite3_fts5_token $t $iOff [expr $iOff+$n]
+ incr iOff $n
+ }
+}
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(t, tokenize=tst);
+}
+
+do_execsql_test 1.1 {
+ INSERT INTO t1 VALUES('AAdontBBmess');
+}
+
+do_execsql_test 1.2 {
+ SELECT snippet(t1, 0, '>', '<', '...', 4) FROM t1('BB');
+} {AAdont>BB<mess}
+
+do_execsql_test 1.3 {
+ SELECT highlight(t1, 0, '>', '<') FROM t1('BB');
+} {AAdont>BB<mess}
+
+do_execsql_test 1.4 {
+ SELECT highlight(t1, 0, '>', '<') FROM t1('AA');
+} {>AA<dontBBmess}
+
+do_execsql_test 1.5 {
+ SELECT highlight(t1, 0, '>', '<') FROM t1('dont');
+} {AA>dont<BBmess}
+
+do_execsql_test 1.6 {
+ SELECT highlight(t1, 0, '>', '<') FROM t1('mess');
+} {AAdontBB>mess<}
+
+do_execsql_test 1.7 {
+ SELECT highlight(t1, 0, '>', '<') FROM t1('BB mess');
+} {AAdont>BBmess<}
+
+
+finish_test
diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile
index 155e4e7f6..61c816194 100644
--- a/ext/jni/GNUmakefile
+++ b/ext/jni/GNUmakefile
@@ -227,7 +227,8 @@ SQLITE_OPT += -DSQLITE_ENABLE_RTREE \
-DSQLITE_ENABLE_OFFSET_SQL_FUNC \
-DSQLITE_ENABLE_PREUPDATE_HOOK \
-DSQLITE_ENABLE_NORMALIZE \
- -DSQLITE_ENABLE_SQLLOG
+ -DSQLITE_ENABLE_SQLLOG \
+ -DSQLITE_ENABLE_COLUMN_METADATA
endif
ifeq (1,$(opt.debug))
diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c
index 6d54391e1..9384fb9d2 100644
--- a/ext/jni/src/c/sqlite3-jni.c
+++ b/ext/jni/src/c/sqlite3-jni.c
@@ -201,8 +201,8 @@
**
** https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers
*/
-#define JniArgsEnvObj JNIEnv * const env, jobject jSelf
-#define JniArgsEnvClass JNIEnv * const env, jclass jKlazz
+#define JniArgsEnvObj JNIEnv * env, jobject jSelf
+#define JniArgsEnvClass JNIEnv * env, jclass jKlazz
/*
** Helpers to account for -Xcheck:jni warnings about not having
** checked for exceptions.
@@ -651,6 +651,17 @@ struct S3JniGlobalType {
jmethodID ctorLong1 /* the Long(long) constructor */;
jmethodID ctorStringBA /* the String(byte[],Charset) constructor */;
jmethodID stringGetBytes /* the String.getBytes(Charset) method */;
+
+ /*
+ ByteBuffer may or may not be supported via JNI on any given
+ platform:
+
+ https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#nio_support
+
+ We only store a ref to the following if JNI support for
+ ByteBuffer is available (which we determine during static init).
+ */
+ jclass cByteBuffer /* global ref to java.nio.ByteBuffer */;
} g;
/*
** The list of Java-side auto-extensions
@@ -1474,7 +1485,7 @@ static void * NativePointerHolder__get(JNIEnv * env, jobject jNph,
#define PtrGet_sqlite3_stmt(OBJ) PtrGet_T(sqlite3_stmt, OBJ)
#define PtrGet_sqlite3_value(OBJ) PtrGet_T(sqlite3_value, OBJ)
/*
-** S3JniLongPtr_T(X,Y) expects X to be an unqualified sqlite3 struct
+** LongPtrGet_T(X,Y) expects X to be an unqualified sqlite3 struct
** type name and Y to be a native pointer to such an object in the
** form of a jlong value. The jlong is simply cast to (X*). This
** approach is, as of 2023-09-27, supplanting the former approach. We
@@ -1483,12 +1494,12 @@ static void * NativePointerHolder__get(JNIEnv * env, jobject jNph,
** intptr_t part here is necessary for compatibility with (at least)
** ARM32.
*/
-#define S3JniLongPtr_T(T,JLongAsPtr) (T*)((intptr_t)(JLongAsPtr))
-#define S3JniLongPtr_sqlite3(JLongAsPtr) S3JniLongPtr_T(sqlite3,JLongAsPtr)
-#define S3JniLongPtr_sqlite3_backup(JLongAsPtr) S3JniLongPtr_T(sqlite3_backup,JLongAsPtr)
-#define S3JniLongPtr_sqlite3_blob(JLongAsPtr) S3JniLongPtr_T(sqlite3_blob,JLongAsPtr)
-#define S3JniLongPtr_sqlite3_stmt(JLongAsPtr) S3JniLongPtr_T(sqlite3_stmt,JLongAsPtr)
-#define S3JniLongPtr_sqlite3_value(JLongAsPtr) S3JniLongPtr_T(sqlite3_value,JLongAsPtr)
+#define LongPtrGet_T(T,JLongAsPtr) (T*)((intptr_t)(JLongAsPtr))
+#define LongPtrGet_sqlite3(JLongAsPtr) LongPtrGet_T(sqlite3,JLongAsPtr)
+#define LongPtrGet_sqlite3_backup(JLongAsPtr) LongPtrGet_T(sqlite3_backup,JLongAsPtr)
+#define LongPtrGet_sqlite3_blob(JLongAsPtr) LongPtrGet_T(sqlite3_blob,JLongAsPtr)
+#define LongPtrGet_sqlite3_stmt(JLongAsPtr) LongPtrGet_T(sqlite3_stmt,JLongAsPtr)
+#define LongPtrGet_sqlite3_value(JLongAsPtr) LongPtrGet_T(sqlite3_value,JLongAsPtr)
/*
** Extracts the new S3JniDb instance from the free-list, or allocates
** one if needed, associates it with pDb, and returns. Returns NULL
@@ -1547,7 +1558,7 @@ static void S3JniDb_xDestroy(void *p){
#define S3JniDb_from_c(sqlite3Ptr) \
((sqlite3Ptr) ? S3JniDb_from_clientdata(sqlite3Ptr) : 0)
#define S3JniDb_from_jlong(sqlite3PtrAsLong) \
- S3JniDb_from_c(S3JniLongPtr_T(sqlite3,sqlite3PtrAsLong))
+ S3JniDb_from_c(LongPtrGet_T(sqlite3,sqlite3PtrAsLong))
/*
** Unref any Java-side state in (S3JniAutoExtension*) AX and zero out
@@ -2052,12 +2063,12 @@ static void udf_xInverse(sqlite3_context* cx, int argc,
/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*)). */
#define WRAP_INT_STMT(JniNameSuffix,CName) \
JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt){ \
- return (jint)CName(S3JniLongPtr_sqlite3_stmt(jpStmt)); \
+ return (jint)CName(LongPtrGet_sqlite3_stmt(jpStmt)); \
}
/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*,int)). */
#define WRAP_INT_STMT_INT(JniNameSuffix,CName) \
JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt, jint n){ \
- return (jint)CName(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)n); \
+ return (jint)CName(LongPtrGet_sqlite3_stmt(jpStmt), (int)n); \
}
/** Create a trivial JNI wrapper for (boolean CName(sqlite3_stmt*)). */
#define WRAP_BOOL_STMT(JniNameSuffix,CName) \
@@ -2068,41 +2079,41 @@ static void udf_xInverse(sqlite3_context* cx, int argc,
#define WRAP_STR_STMT_INT(JniNameSuffix,CName) \
JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt, jint ndx){ \
return s3jni_utf8_to_jstring( \
- CName(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx), \
+ CName(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx), \
-1); \
}
/** Create a trivial JNI wrapper for (boolean CName(sqlite3*)). */
#define WRAP_BOOL_DB(JniNameSuffix,CName) \
JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \
- return CName(S3JniLongPtr_sqlite3(jpDb)) ? JNI_TRUE : JNI_FALSE; \
+ return CName(LongPtrGet_sqlite3(jpDb)) ? JNI_TRUE : JNI_FALSE; \
}
/** Create a trivial JNI wrapper for (int CName(sqlite3*)). */
#define WRAP_INT_DB(JniNameSuffix,CName) \
JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \
- return (jint)CName(S3JniLongPtr_sqlite3(jpDb)); \
+ return (jint)CName(LongPtrGet_sqlite3(jpDb)); \
}
/** Create a trivial JNI wrapper for (int64 CName(sqlite3*)). */
#define WRAP_INT64_DB(JniNameSuffix,CName) \
JniDecl(jlong,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \
- return (jlong)CName(S3JniLongPtr_sqlite3(jpDb)); \
+ return (jlong)CName(LongPtrGet_sqlite3(jpDb)); \
}
/** Create a trivial JNI wrapper for (jstring CName(sqlite3*,int)). */
#define WRAP_STR_DB_INT(JniNameSuffix,CName) \
JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass, jlong jpDb, jint ndx){ \
return s3jni_utf8_to_jstring( \
- CName(S3JniLongPtr_sqlite3(jpDb), (int)ndx), \
+ CName(LongPtrGet_sqlite3(jpDb), (int)ndx), \
-1); \
}
/** Create a trivial JNI wrapper for (int CName(sqlite3_value*)). */
#define WRAP_INT_SVALUE(JniNameSuffix,CName,DfltOnNull) \
JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpSValue){ \
- sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSValue); \
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSValue); \
return (jint)(sv ? CName(sv): DfltOnNull); \
}
/** Create a trivial JNI wrapper for (boolean CName(sqlite3_value*)). */
#define WRAP_BOOL_SVALUE(JniNameSuffix,CName,DfltOnNull) \
JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jlong jpSValue){ \
- sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSValue); \
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSValue); \
return (jint)(sv ? CName(sv) : DfltOnNull) \
? JNI_TRUE : JNI_FALSE; \
}
@@ -2115,9 +2126,11 @@ WRAP_INT_STMT_INT(1column_1bytes16, sqlite3_column_bytes16)
WRAP_INT_STMT(1column_1count, sqlite3_column_count)
WRAP_STR_STMT_INT(1column_1decltype, sqlite3_column_decltype)
WRAP_STR_STMT_INT(1column_1name, sqlite3_column_name)
+#ifdef SQLITE_ENABLE_COLUMN_METADATA
WRAP_STR_STMT_INT(1column_1database_1name, sqlite3_column_database_name)
WRAP_STR_STMT_INT(1column_1origin_1name, sqlite3_column_origin_name)
WRAP_STR_STMT_INT(1column_1table_1name, sqlite3_column_table_name)
+#endif
WRAP_INT_STMT_INT(1column_1type, sqlite3_column_type)
WRAP_INT_STMT(1data_1count, sqlite3_data_count)
WRAP_STR_DB_INT(1db_1name, sqlite3_db_name)
@@ -2315,7 +2328,7 @@ S3JniApi(sqlite3_backup_finish(),jint,1backup_1finish)(
){
int rc = 0;
if( jpBack!=0 ){
- rc = sqlite3_backup_finish( S3JniLongPtr_sqlite3_backup(jpBack) );
+ rc = sqlite3_backup_finish( LongPtrGet_sqlite3_backup(jpBack) );
}
return rc;
}
@@ -2324,8 +2337,8 @@ S3JniApi(sqlite3_backup_init(),jobject,1backup_1init)(
JniArgsEnvClass, jlong jpDbDest, jstring jTDest,
jlong jpDbSrc, jstring jTSrc
){
- sqlite3 * const pDest = S3JniLongPtr_sqlite3(jpDbDest);
- sqlite3 * const pSrc = S3JniLongPtr_sqlite3(jpDbSrc);
+ sqlite3 * const pDest = LongPtrGet_sqlite3(jpDbDest);
+ sqlite3 * const pSrc = LongPtrGet_sqlite3(jpDbSrc);
char * const zDest = s3jni_jstring_to_utf8(jTDest, 0);
char * const zSrc = s3jni_jstring_to_utf8(jTSrc, 0);
jobject rv = 0;
@@ -2348,19 +2361,19 @@ S3JniApi(sqlite3_backup_init(),jobject,1backup_1init)(
S3JniApi(sqlite3_backup_pagecount(),jint,1backup_1pagecount)(
JniArgsEnvClass, jlong jpBack
){
- return sqlite3_backup_pagecount(S3JniLongPtr_sqlite3_backup(jpBack));
+ return sqlite3_backup_pagecount(LongPtrGet_sqlite3_backup(jpBack));
}
S3JniApi(sqlite3_backup_remaining(),jint,1backup_1remaining)(
JniArgsEnvClass, jlong jpBack
){
- return sqlite3_backup_remaining(S3JniLongPtr_sqlite3_backup(jpBack));
+ return sqlite3_backup_remaining(LongPtrGet_sqlite3_backup(jpBack));
}
S3JniApi(sqlite3_backup_step(),jint,1backup_1step)(
JniArgsEnvClass, jlong jpBack, jint nPage
){
- return sqlite3_backup_step(S3JniLongPtr_sqlite3_backup(jpBack), (int)nPage);
+ return sqlite3_backup_step(LongPtrGet_sqlite3_backup(jpBack), (int)nPage);
}
S3JniApi(sqlite3_bind_blob(),jint,1bind_1blob)(
@@ -2373,13 +2386,13 @@ S3JniApi(sqlite3_bind_blob(),jint,1bind_1blob)(
if( nMax>nBA ){
nMax = nBA;
}
- rc = sqlite3_bind_blob(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx,
+ rc = sqlite3_bind_blob(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx,
pBuf, (int)nMax, SQLITE_TRANSIENT);
s3jni_jbyteArray_release(baData, pBuf);
}else{
rc = baData
? SQLITE_NOMEM
- : sqlite3_bind_null( S3JniLongPtr_sqlite3_stmt(jpStmt), ndx );
+ : sqlite3_bind_null( LongPtrGet_sqlite3_stmt(jpStmt), ndx );
}
return (jint)rc;
}
@@ -2387,20 +2400,20 @@ S3JniApi(sqlite3_bind_blob(),jint,1bind_1blob)(
S3JniApi(sqlite3_bind_double(),jint,1bind_1double)(
JniArgsEnvClass, jlong jpStmt, jint ndx, jdouble val
){
- return (jint)sqlite3_bind_double(S3JniLongPtr_sqlite3_stmt(jpStmt),
+ return (jint)sqlite3_bind_double(LongPtrGet_sqlite3_stmt(jpStmt),
(int)ndx, (double)val);
}
S3JniApi(sqlite3_bind_int(),jint,1bind_1int)(
JniArgsEnvClass, jlong jpStmt, jint ndx, jint val
){
- return (jint)sqlite3_bind_int(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, (int)val);
+ return (jint)sqlite3_bind_int(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)val);
}
S3JniApi(sqlite3_bind_int64(),jint,1bind_1int64)(
JniArgsEnvClass, jlong jpStmt, jint ndx, jlong val
){
- return (jint)sqlite3_bind_int64(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val);
+ return (jint)sqlite3_bind_int64(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val);
}
/*
@@ -2409,7 +2422,7 @@ S3JniApi(sqlite3_bind_int64(),jint,1bind_1int64)(
S3JniApi(sqlite3_bind_java_object(),jint,1bind_1java_1object)(
JniArgsEnvClass, jlong jpStmt, jint ndx, jobject val
){
- sqlite3_stmt * const pStmt = S3JniLongPtr_sqlite3_stmt(jpStmt);
+ sqlite3_stmt * const pStmt = LongPtrGet_sqlite3_stmt(jpStmt);
int rc = SQLITE_MISUSE;
if(pStmt){
@@ -2429,13 +2442,13 @@ S3JniApi(sqlite3_bind_java_object(),jint,1bind_1java_1object)(
S3JniApi(sqlite3_bind_null(),jint,1bind_1null)(
JniArgsEnvClass, jlong jpStmt, jint ndx
){
- return (jint)sqlite3_bind_null(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx);
+ return (jint)sqlite3_bind_null(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx);
}
S3JniApi(sqlite3_bind_parameter_count(),jint,1bind_1parameter_1count)(
JniArgsEnvClass, jlong jpStmt
){
- return (jint)sqlite3_bind_parameter_count(S3JniLongPtr_sqlite3_stmt(jpStmt));
+ return (jint)sqlite3_bind_parameter_count(LongPtrGet_sqlite3_stmt(jpStmt));
}
S3JniApi(sqlite3_bind_parameter_index(),jint,1bind_1parameter_1index)(
@@ -2444,7 +2457,7 @@ S3JniApi(sqlite3_bind_parameter_index(),jint,1bind_1parameter_1index)(
int rc = 0;
jbyte * const pBuf = s3jni_jbyteArray_bytes(jName);
if( pBuf ){
- rc = sqlite3_bind_parameter_index(S3JniLongPtr_sqlite3_stmt(jpStmt),
+ rc = sqlite3_bind_parameter_index(LongPtrGet_sqlite3_stmt(jpStmt),
(const char *)pBuf);
s3jni_jbyteArray_release(jName, pBuf);
}
@@ -2455,7 +2468,7 @@ S3JniApi(sqlite3_bind_parameter_name(),jstring,1bind_1parameter_1name)(
JniArgsEnvClass, jlong jpStmt, jint ndx
){
const char *z =
- sqlite3_bind_parameter_name(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx);
+ sqlite3_bind_parameter_name(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx);
return z ? s3jni_utf8_to_jstring(z, -1) : 0;
}
@@ -2477,14 +2490,14 @@ static int s3jni__bind_text(int is16, JNIEnv *env, jlong jpStmt, jint ndx,
such cases, we do not expose the byte-limit arguments in the
public API. */
rc = is16
- ? sqlite3_bind_text16(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx,
+ ? sqlite3_bind_text16(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx,
pBuf, (int)nMax, SQLITE_TRANSIENT)
- : sqlite3_bind_text(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx,
+ : sqlite3_bind_text(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx,
(const char *)pBuf,
(int)nMax, SQLITE_TRANSIENT);
}else{
rc = baData
- ? sqlite3_bind_null(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx)
+ ? sqlite3_bind_null(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx)
: SQLITE_NOMEM;
}
s3jni_jbyteArray_release(baData, pBuf);
@@ -2508,9 +2521,9 @@ S3JniApi(sqlite3_bind_value(),jint,1bind_1value)(
JniArgsEnvClass, jlong jpStmt, jint ndx, jlong jpValue
){
int rc = 0;
- sqlite3_stmt * pStmt = S3JniLongPtr_sqlite3_stmt(jpStmt);
+ sqlite3_stmt * pStmt = LongPtrGet_sqlite3_stmt(jpStmt);
if( pStmt ){
- sqlite3_value *v = S3JniLongPtr_sqlite3_value(jpValue);
+ sqlite3_value *v = LongPtrGet_sqlite3_value(jpValue);
if( v ){
rc = sqlite3_bind_value(pStmt, (int)ndx, v);
}else{
@@ -2525,27 +2538,27 @@ S3JniApi(sqlite3_bind_value(),jint,1bind_1value)(
S3JniApi(sqlite3_bind_zeroblob(),jint,1bind_1zeroblob)(
JniArgsEnvClass, jlong jpStmt, jint ndx, jint n
){
- return (jint)sqlite3_bind_zeroblob(S3JniLongPtr_sqlite3_stmt(jpStmt),
+ return (jint)sqlite3_bind_zeroblob(LongPtrGet_sqlite3_stmt(jpStmt),
(int)ndx, (int)n);
}
S3JniApi(sqlite3_bind_zeroblob64(),jint,1bind_1zeroblob64)(
JniArgsEnvClass, jlong jpStmt, jint ndx, jlong n
){
- return (jint)sqlite3_bind_zeroblob64(S3JniLongPtr_sqlite3_stmt(jpStmt),
+ return (jint)sqlite3_bind_zeroblob64(LongPtrGet_sqlite3_stmt(jpStmt),
(int)ndx, (sqlite3_uint64)n);
}
S3JniApi(sqlite3_blob_bytes(),jint,1blob_1bytes)(
JniArgsEnvClass, jlong jpBlob
){
- return sqlite3_blob_bytes(S3JniLongPtr_sqlite3_blob(jpBlob));
+ return sqlite3_blob_bytes(LongPtrGet_sqlite3_blob(jpBlob));
}
S3JniApi(sqlite3_blob_close(),jint,1blob_1close)(
JniArgsEnvClass, jlong jpBlob
){
- sqlite3_blob * const b = S3JniLongPtr_sqlite3_blob(jpBlob);
+ sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob);
return b ? (jint)sqlite3_blob_close(b) : SQLITE_MISUSE;
}
@@ -2553,7 +2566,7 @@ S3JniApi(sqlite3_blob_open(),jint,1blob_1open)(
JniArgsEnvClass, jlong jpDb, jstring jDbName, jstring jTbl, jstring jCol,
jlong jRowId, jint flags, jobject jOut
){
- sqlite3 * const db = S3JniLongPtr_sqlite3(jpDb);
+ sqlite3 * const db = LongPtrGet_sqlite3(jpDb);
sqlite3_blob * pBlob = 0;
char * zDbName = 0, * zTableName = 0, * zColumnName = 0;
int rc;
@@ -2587,7 +2600,7 @@ S3JniApi(sqlite3_blob_read(),jint,1blob_1read)(
int rc = jTgt ? (pBa ? SQLITE_MISUSE : SQLITE_NOMEM) : SQLITE_MISUSE;
if( pBa ){
jsize const nTgt = (*env)->GetArrayLength(env, jTgt);
- rc = sqlite3_blob_read(S3JniLongPtr_sqlite3_blob(jpBlob), pBa,
+ rc = sqlite3_blob_read(LongPtrGet_sqlite3_blob(jpBlob), pBa,
(int)nTgt, (int)iOffset);
if( 0==rc ){
s3jni_jbyteArray_commit(jTgt, pBa);
@@ -2601,14 +2614,14 @@ S3JniApi(sqlite3_blob_read(),jint,1blob_1read)(
S3JniApi(sqlite3_blob_reopen(),jint,1blob_1reopen)(
JniArgsEnvClass, jlong jpBlob, jlong iNewRowId
){
- return (jint)sqlite3_blob_reopen(S3JniLongPtr_sqlite3_blob(jpBlob),
+ return (jint)sqlite3_blob_reopen(LongPtrGet_sqlite3_blob(jpBlob),
(sqlite3_int64)iNewRowId);
}
S3JniApi(sqlite3_blob_write(),jint,1blob_1write)(
JniArgsEnvClass, jlong jpBlob, jbyteArray jBa, jint iOffset
){
- sqlite3_blob * const b = S3JniLongPtr_sqlite3_blob(jpBlob);
+ sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob);
jbyte * const pBuf = b ? s3jni_jbyteArray_bytes(jBa) : 0;
const jsize nBA = pBuf ? (*env)->GetArrayLength(env, jBa) : 0;
int rc = SQLITE_MISUSE;
@@ -2817,7 +2830,7 @@ S3JniApi(sqlite3_collation_needed(),jint,1collation_1needed)(
}else{
jclass const klazz = (*env)->GetObjectClass(env, jHook);
jmethodID const xCallback = (*env)->GetMethodID(
- env, klazz, "call", "(Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)I"
+ env, klazz, "call", "(Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)V"
);
S3JniUnrefLocal(klazz);
S3JniIfThrew {
@@ -2870,7 +2883,7 @@ S3JniApi(sqlite3_column_int64(),jlong,1column_1int64)(
S3JniApi(sqlite3_column_java_object(),jobject,1column_1java_1object)(
JniArgsEnvClass, jlong jpStmt, jint ndx
){
- sqlite3_stmt * const stmt = S3JniLongPtr_sqlite3_stmt(jpStmt);
+ sqlite3_stmt * const stmt = LongPtrGet_sqlite3_stmt(jpStmt);
jobject rv = 0;
if( stmt ){
sqlite3 * const db = sqlite3_db_handle(stmt);
@@ -2942,7 +2955,10 @@ static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){
? (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback)
: (int)((*env)->CallVoidMethod(env, hook.jObj, hook.midCallback), 0);
S3JniIfThrew{
- rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR, "hook callback threw");
+ rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR,
+ isCommit
+ ? "Commit hook callback threw"
+ : "Rollback hook callback threw");
}
S3JniHook_localundup(hook);
}
@@ -3045,7 +3061,7 @@ S3JniApi(sqlite3_compileoption_used(),jboolean,1compileoption_1used)(
return rc;
}
-S3JniApi(sqlite3_complete(),int,1complete)(
+S3JniApi(sqlite3_complete(),jint,1complete)(
JniArgsEnvClass, jbyteArray jSql
){
jbyte * const pBuf = s3jni_jbyteArray_bytes(jSql);
@@ -3429,7 +3445,6 @@ S3JniApi(
}
break;
}
- case 0:
default:
rc = SQLITE_MISUSE;
}
@@ -3492,7 +3507,7 @@ S3JniApi(sqlite3_db_readonly(),jint,1db_1readonly)(
return (jint)rc;
}
-S3JniApi(sqlite3_db_release_memory(),int,1db_1release_1memory)(
+S3JniApi(sqlite3_db_release_memory(),jint,1db_1release_1memory)(
JniArgsEnvClass, jobject jDb
){
sqlite3 * const pDb = PtrGet_sqlite3(jDb);
@@ -3594,19 +3609,21 @@ S3JniApi(sqlite3_normalized_sql(),jstring,1normalized_1sql)(
#endif
}
-S3JniApi(sqlite3_extended_result_codes(),jboolean,1extended_1result_1codes)(
+S3JniApi(sqlite3_extended_result_codes(),jint,1extended_1result_1codes)(
JniArgsEnvClass, jobject jpDb, jboolean onoff
){
sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
- int const rc = pDb ? sqlite3_extended_result_codes(pDb, onoff ? 1 : 0) : 0;
- return rc ? JNI_TRUE : JNI_FALSE;
+ int const rc = pDb
+ ? sqlite3_extended_result_codes(pDb, onoff ? 1 : 0)
+ : SQLITE_MISUSE;
+ return rc;
}
S3JniApi(sqlite3_finalize(),jint,1finalize)(
JniArgsEnvClass, jlong jpStmt
){
return jpStmt
- ? sqlite3_finalize(S3JniLongPtr_sqlite3_stmt(jpStmt))
+ ? sqlite3_finalize(LongPtrGet_sqlite3_stmt(jpStmt))
: 0;
}
@@ -3655,6 +3672,11 @@ JniDecl(jboolean,1java_1uncache_1thread)(JniArgsEnvClass){
return rc ? JNI_TRUE : JNI_FALSE;
}
+JniDecl(jboolean,1jni_1supports_1nio)(JniArgsEnvClass){
+ return SJG.g.cByteBuffer ? JNI_TRUE : JNI_FALSE;
+}
+
+
S3JniApi(sqlite3_keyword_check(),jboolean,1keyword_1check)(
JniArgsEnvClass, jstring jWord
){
@@ -3835,7 +3857,7 @@ jint sqlite3_jni_prepare_v123( int prepVersion, JNIEnv * const env, jclass self,
sqlite3_stmt * pStmt = 0;
jobject jStmt = 0;
const char * zTail = 0;
- sqlite3 * const pDb = S3JniLongPtr_sqlite3(jpDb);
+ sqlite3 * const pDb = LongPtrGet_sqlite3(jpDb);
jbyte * const pBuf = pDb ? s3jni_jbyteArray_bytes(baSql) : 0;
int rc = SQLITE_ERROR;
@@ -3990,11 +4012,11 @@ static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb,
#if !defined(SQLITE_ENABLE_PREUPDATE_HOOK)
/* We need no-op impls for preupdate_{count,depth,blobwrite}() */
-S3JniApi(sqlite3_preupdate_blobwrite(),int,1preupdate_1blobwrite)(
+S3JniApi(sqlite3_preupdate_blobwrite(),jint,1preupdate_1blobwrite)(
JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; }
-S3JniApi(sqlite3_preupdate_count(),int,1preupdate_1count)(
+S3JniApi(sqlite3_preupdate_count(),jint,1preupdate_1count)(
JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; }
-S3JniApi(sqlite3_preupdate_depth(),int,1preupdate_1depth)(
+S3JniApi(sqlite3_preupdate_depth(),jint,1preupdate_1depth)(
JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; }
#endif /* !SQLITE_ENABLE_PREUPDATE_HOOK */
@@ -4089,7 +4111,7 @@ S3JniApi(sqlite3_preupdate_hook(),jobject,1preupdate_1hook)(
static int s3jni_preupdate_newold(JNIEnv * const env, int isNew, jlong jpDb,
jint iCol, jobject jOut){
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
- sqlite3 * const pDb = S3JniLongPtr_sqlite3(jpDb);
+ sqlite3 * const pDb = LongPtrGet_sqlite3(jpDb);
int rc = SQLITE_MISUSE;
if( pDb ){
sqlite3_value * pOut = 0;
@@ -4310,7 +4332,7 @@ S3JniApi(sqlite3_result_double(),void,1result_1double)(
}
S3JniApi(sqlite3_result_error(),void,1result_1error)(
- JniArgsEnvClass, jobject jpCx, jbyteArray baMsg, int eTextRep
+ JniArgsEnvClass, jobject jpCx, jbyteArray baMsg, jint eTextRep
){
const char * zUnspecified = "Unspecified error.";
jsize const baLen = (*env)->GetArrayLength(env, baMsg);
@@ -4570,20 +4592,8 @@ S3JniApi(sqlite3_shutdown(),jint,1shutdown)(
S3JniEnv_uncache( SJG.envCache.aHead->env );
}
} S3JniEnv_mutex_leave;
-#if 0
- /*
- ** Is automatically closing any still-open dbs a good idea? We will
- ** get rid of the perDb list once sqlite3 gets a per-db client
- ** state, at which point we won't have a central list of databases
- ** to close.
- */
- S3JniDb_mutex_enter;
- while( SJG.perDb.pHead ){
- s3jni_close_db(env, SJG.perDb.pHead->jDb, 2);
- }
- S3JniDb_mutex_leave;
-#endif
- /* Do not clear S3JniGlobal.jvm: it's legal to restart the lib. */
+ /* Do not clear S3JniGlobal.jvm or S3JniGlobal.g: it's legal to
+ ** restart the lib. */
return sqlite3_shutdown();
}
@@ -4670,7 +4680,7 @@ S3JniApi(sqlite3_step(),jint,1step)(
return pStmt ? (jint)sqlite3_step(pStmt) : (jint)SQLITE_MISUSE;
}
-S3JniApi(sqlite3_table_column_metadata(),int,1table_1column_1metadata)(
+S3JniApi(sqlite3_table_column_metadata(),jint,1table_1column_1metadata)(
JniArgsEnvClass, jobject jDb, jstring jDbName, jstring jTableName,
jstring jColumnName, jobject jDataType, jobject jCollSeq, jobject jNotNull,
jobject jPrimaryKey, jobject jAutoinc
@@ -4846,7 +4856,7 @@ S3JniApi(sqlite3_update_hook(),jobject,1update_1hook)(
S3JniApi(sqlite3_value_blob(),jbyteArray,1value_1blob)(
JniArgsEnvClass, jlong jpSVal
){
- sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
const jbyte * pBytes = sv ? sqlite3_value_blob(sv) : 0;
int const nLen = pBytes ? sqlite3_value_bytes(sv) : 0;
@@ -4856,17 +4866,17 @@ S3JniApi(sqlite3_value_blob(),jbyteArray,1value_1blob)(
: NULL;
}
-S3JniApi(sqlite3_value_bytes(),int,1value_1bytes)(
+S3JniApi(sqlite3_value_bytes(),jint,1value_1bytes)(
JniArgsEnvClass, jlong jpSVal
){
- sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
return sv ? sqlite3_value_bytes(sv) : 0;
}
-S3JniApi(sqlite3_value_bytes16(),int,1value_1bytes16)(
+S3JniApi(sqlite3_value_bytes16(),jint,1value_1bytes16)(
JniArgsEnvClass, jlong jpSVal
){
- sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
return sv ? sqlite3_value_bytes16(sv) : 0;
}
@@ -4874,7 +4884,7 @@ S3JniApi(sqlite3_value_bytes16(),int,1value_1bytes16)(
S3JniApi(sqlite3_value_double(),jdouble,1value_1double)(
JniArgsEnvClass, jlong jpSVal
){
- sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
return (jdouble) (sv ? sqlite3_value_double(sv) : 0.0);
}
@@ -4882,7 +4892,7 @@ S3JniApi(sqlite3_value_double(),jdouble,1value_1double)(
S3JniApi(sqlite3_value_dup(),jobject,1value_1dup)(
JniArgsEnvClass, jlong jpSVal
){
- sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
sqlite3_value * const sd = sv ? sqlite3_value_dup(sv) : 0;
jobject rv = sd ? new_java_sqlite3_value(env, sd) : 0;
if( sd && !rv ) {
@@ -4895,7 +4905,7 @@ S3JniApi(sqlite3_value_dup(),jobject,1value_1dup)(
S3JniApi(sqlite3_value_free(),void,1value_1free)(
JniArgsEnvClass, jlong jpSVal
){
- sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
if( sv ){
sqlite3_value_free(sv);
}
@@ -4904,21 +4914,21 @@ S3JniApi(sqlite3_value_free(),void,1value_1free)(
S3JniApi(sqlite3_value_int(),jint,1value_1int)(
JniArgsEnvClass, jlong jpSVal
){
- sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
return (jint) (sv ? sqlite3_value_int(sv) : 0);
}
S3JniApi(sqlite3_value_int64(),jlong,1value_1int64)(
JniArgsEnvClass, jlong jpSVal
){
- sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
return (jlong) (sv ? sqlite3_value_int64(sv) : 0LL);
}
S3JniApi(sqlite3_value_java_object(),jobject,1value_1java_1object)(
JniArgsEnvClass, jlong jpSVal
){
- sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
return sv
? sqlite3_value_pointer(sv, s3jni__value_jref_key)
: 0;
@@ -4927,7 +4937,7 @@ S3JniApi(sqlite3_value_java_object(),jobject,1value_1java_1object)(
S3JniApi(sqlite3_value_text(),jbyteArray,1value_1text)(
JniArgsEnvClass, jlong jpSVal
){
- sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
const unsigned char * const p = sv ? sqlite3_value_text(sv) : 0;
int const n = p ? sqlite3_value_bytes(sv) : 0;
return p ? s3jni_new_jbyteArray(p, n) : 0;
@@ -4938,7 +4948,7 @@ S3JniApi(sqlite3_value_text(),jbyteArray,1value_1text)(
S3JniApi(sqlite3_value_text(),jstring,1value_1text)(
JniArgsEnvClass, jlong jpSVal
){
- sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
const unsigned char * const p = sv ? sqlite3_value_text(sv) : 0;
int const n = p ? sqlite3_value_bytes(sv) : 0;
return p ? s3jni_utf8_to_jstring( (const char *)p, n) : 0;
@@ -4948,7 +4958,7 @@ S3JniApi(sqlite3_value_text(),jstring,1value_1text)(
S3JniApi(sqlite3_value_text16(),jstring,1value_1text16)(
JniArgsEnvClass, jlong jpSVal
){
- sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
const int n = sv ? sqlite3_value_bytes16(sv) : 0;
const void * const p = sv ? sqlite3_value_text16(sv) : 0;
return p ? s3jni_text16_to_jstring(env, p, n) : 0;
@@ -5530,7 +5540,7 @@ JniDeclFtsXA(jlong,xRowid)(JniArgsEnvObj,jobject jCtx){
return (jlong)ext->xRowid(PtrGet_Fts5Context(jCtx));
}
-JniDeclFtsXA(int,xSetAuxdata)(JniArgsEnvObj,jobject jCtx, jobject jAux){
+JniDeclFtsXA(jint,xSetAuxdata)(JniArgsEnvObj,jobject jCtx, jobject jAux){
Fts5ExtDecl;
int rc;
S3JniFts5AuxData * pAux;
@@ -5924,6 +5934,19 @@ Java_org_sqlite_jni_capi_CApi_init(JniArgsEnvClass){
s3jni_oom_fatal( SJG.metrics.mutex );
#endif
+ {
+ /* Test whether this JVM supports direct memory access via
+ ByteBuffer. */
+ unsigned char buf[16] = {0};
+ jobject bb = (*env)->NewDirectByteBuffer(env, buf, 16);
+ if( bb ){
+ SJG.g.cByteBuffer = (*env)->GetObjectClass(env, bb);
+ S3JniUnrefLocal(bb);
+ }else{
+ SJG.g.cByteBuffer = 0;
+ }
+ }
+
sqlite3_shutdown()
/* So that it becomes legal for Java-level code to call
** sqlite3_config(). */;
diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h
index e4393ddd8..f160b6453 100644
--- a/ext/jni/src/c/sqlite3-jni.h
+++ b/ext/jni/src/c/sqlite3-jni.h
@@ -427,8 +427,6 @@ extern "C" {
#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_EXRESCODE 33554432L
#undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_PERSISTENT
#define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_PERSISTENT 1L
-#undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NORMALIZE
-#define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NORMALIZE 2L
#undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NO_VTAB
#define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NO_VTAB 4L
#undef org_sqlite_jni_capi_CApi_SQLITE_OK
@@ -777,6 +775,14 @@ JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1java_1uncache_
/*
* Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_jni_supports_nio
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1supports_1nio
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_aggregate_context
* Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Z)J
*/
@@ -1402,9 +1408,9 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1errcode
/*
* Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_extended_result_codes
- * Signature: (Lorg/sqlite/jni/capi/sqlite3;Z)Z
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Z)I
*/
-JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1result_1codes
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1result_1codes
(JNIEnv *, jclass, jobject, jboolean);
/*
diff --git a/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java b/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java
index ce7c6fca6..298e3a590 100644
--- a/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java
+++ b/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java
@@ -20,7 +20,8 @@ import org.sqlite.jni.annotation.*;
public interface AuthorizerCallback extends CallbackProxy {
/**
Must function as described for the C-level
- sqlite3_set_authorizer() callback.
+ sqlite3_set_authorizer() callback. If it throws, the error is
+ converted to a db-level error and the exception is suppressed.
*/
int call(int opId, @Nullable String s1, @Nullable String s2,
@Nullable String s3, @Nullable String s4);
diff --git a/ext/jni/src/org/sqlite/jni/capi/CApi.java b/ext/jni/src/org/sqlite/jni/capi/CApi.java
index 6eeeb29a2..8e0cb8f4a 100644
--- a/ext/jni/src/org/sqlite/jni/capi/CApi.java
+++ b/ext/jni/src/org/sqlite/jni/capi/CApi.java
@@ -123,6 +123,12 @@ public final class CApi {
*/
public static native boolean sqlite3_java_uncache_thread();
+ /**
+ Returns true if this JVM has JNI-level support for direct memory
+ access using java.nio.ByteBuffer, else returns false.
+ */
+ public static native boolean sqlite3_jni_supports_nio();
+
//////////////////////////////////////////////////////////////////////
// Maintenance reminder: please keep the sqlite3_.... functions
// alphabetized. The SQLITE_... values. on the other hand, are
@@ -164,13 +170,13 @@ public final class CApi {
*/
public static native int sqlite3_auto_extension(@NotNull AutoExtensionCallback callback);
- static native int sqlite3_backup_finish(@NotNull long ptrToBackup);
+ private static native int sqlite3_backup_finish(@NotNull long ptrToBackup);
public static int sqlite3_backup_finish(@NotNull sqlite3_backup b){
- return sqlite3_backup_finish(b.clearNativePointer());
+ return null==b ? 0 : sqlite3_backup_finish(b.clearNativePointer());
}
- static native sqlite3_backup sqlite3_backup_init(
+ private static native sqlite3_backup sqlite3_backup_init(
@NotNull long ptrToDbDest, @NotNull String destTableName,
@NotNull long ptrToDbSrc, @NotNull String srcTableName
);
@@ -183,25 +189,25 @@ public final class CApi {
dbSrc.getNativePointer(), srcTableName );
}
- static native int sqlite3_backup_pagecount(@NotNull long ptrToBackup);
+ private static native int sqlite3_backup_pagecount(@NotNull long ptrToBackup);
public static int sqlite3_backup_pagecount(@NotNull sqlite3_backup b){
return sqlite3_backup_pagecount(b.getNativePointer());
}
- static native int sqlite3_backup_remaining(@NotNull long ptrToBackup);
+ private static native int sqlite3_backup_remaining(@NotNull long ptrToBackup);
public static int sqlite3_backup_remaining(@NotNull sqlite3_backup b){
return sqlite3_backup_remaining(b.getNativePointer());
}
- static native int sqlite3_backup_step(@NotNull long ptrToBackup, int nPage);
+ private static native int sqlite3_backup_step(@NotNull long ptrToBackup, int nPage);
public static int sqlite3_backup_step(@NotNull sqlite3_backup b, int nPage){
return sqlite3_backup_step(b.getNativePointer(), nPage);
}
- static native int sqlite3_bind_blob(
+ private static native int sqlite3_bind_blob(
@NotNull long ptrToStmt, int ndx, @Nullable byte[] data, int n
);
@@ -223,7 +229,7 @@ public final class CApi {
: sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, data.length);
}
- static native int sqlite3_bind_double(
+ private static native int sqlite3_bind_double(
@NotNull long ptrToStmt, int ndx, double v
);
@@ -233,7 +239,7 @@ public final class CApi {
return sqlite3_bind_double(stmt.getNativePointer(), ndx, v);
}
- static native int sqlite3_bind_int(
+ private static native int sqlite3_bind_int(
@NotNull long ptrToStmt, int ndx, int v
);
@@ -243,7 +249,7 @@ public final class CApi {
return sqlite3_bind_int(stmt.getNativePointer(), ndx, v);
}
- static native int sqlite3_bind_int64(
+ private static native int sqlite3_bind_int64(
@NotNull long ptrToStmt, int ndx, long v
);
@@ -251,7 +257,7 @@ public final class CApi {
return sqlite3_bind_int64( stmt.getNativePointer(), ndx, v );
}
- static native int sqlite3_bind_java_object(
+ private static native int sqlite3_bind_java_object(
@NotNull long ptrToStmt, int ndx, @Nullable Object o
);
@@ -267,13 +273,13 @@ public final class CApi {
return sqlite3_bind_java_object(stmt.getNativePointer(), ndx, o);
}
- static native int sqlite3_bind_null(@NotNull long ptrToStmt, int ndx);
+ private static native int sqlite3_bind_null(@NotNull long ptrToStmt, int ndx);
public static int sqlite3_bind_null(@NotNull sqlite3_stmt stmt, int ndx){
return sqlite3_bind_null(stmt.getNativePointer(), ndx);
}
- static native int sqlite3_bind_parameter_count(@NotNull long ptrToStmt);
+ private static native int sqlite3_bind_parameter_count(@NotNull long ptrToStmt);
public static int sqlite3_bind_parameter_count(@NotNull sqlite3_stmt stmt){
return sqlite3_bind_parameter_count(stmt.getNativePointer());
@@ -300,7 +306,7 @@ public final class CApi {
return null==utf8 ? 0 : sqlite3_bind_parameter_index(stmt.getNativePointer(), utf8);
}
- static native String sqlite3_bind_parameter_name(
+ private static native String sqlite3_bind_parameter_name(
@NotNull long ptrToStmt, int index
);
@@ -308,7 +314,7 @@ public final class CApi {
return sqlite3_bind_parameter_name(stmt.getNativePointer(), index);
}
- static native int sqlite3_bind_text(
+ private static native int sqlite3_bind_text(
@NotNull long ptrToStmt, int ndx, @Nullable byte[] utf8, int maxBytes
);
@@ -352,7 +358,7 @@ public final class CApi {
: sqlite3_bind_text(stmt.getNativePointer(), ndx, utf8, utf8.length);
}
- static native int sqlite3_bind_text16(
+ private static native int sqlite3_bind_text16(
@NotNull long ptrToStmt, int ndx, @Nullable byte[] data, int maxBytes
);
@@ -393,7 +399,7 @@ public final class CApi {
: sqlite3_bind_text16(stmt.getNativePointer(), ndx, data, data.length);
}
- static native int sqlite3_bind_value(@NotNull long ptrToStmt, int ndx, long ptrToValue);
+ private static native int sqlite3_bind_value(@NotNull long ptrToStmt, int ndx, long ptrToValue);
/**
Functions like the C-level sqlite3_bind_value(), or
@@ -404,13 +410,13 @@ public final class CApi {
null==val ? 0L : val.getNativePointer());
}
- static native int sqlite3_bind_zeroblob(@NotNull long ptrToStmt, int ndx, int n);
+ private static native int sqlite3_bind_zeroblob(@NotNull long ptrToStmt, int ndx, int n);
public static int sqlite3_bind_zeroblob(@NotNull sqlite3_stmt stmt, int ndx, int n){
return sqlite3_bind_zeroblob(stmt.getNativePointer(), ndx, n);
}
- static native int sqlite3_bind_zeroblob64(
+ private static native int sqlite3_bind_zeroblob64(
@NotNull long ptrToStmt, int ndx, long n
);
@@ -418,19 +424,19 @@ public final class CApi {
return sqlite3_bind_zeroblob64(stmt.getNativePointer(), ndx, n);
}
- static native int sqlite3_blob_bytes(@NotNull long ptrToBlob);
+ private static native int sqlite3_blob_bytes(@NotNull long ptrToBlob);
public static int sqlite3_blob_bytes(@NotNull sqlite3_blob blob){
return sqlite3_blob_bytes(blob.getNativePointer());
}
- static native int sqlite3_blob_close(@Nullable long ptrToBlob);
+ private static native int sqlite3_blob_close(@Nullable long ptrToBlob);
public static int sqlite3_blob_close(@Nullable sqlite3_blob blob){
- return sqlite3_blob_close(blob.clearNativePointer());
+ return null==blob ? 0 : sqlite3_blob_close(blob.clearNativePointer());
}
- static native int sqlite3_blob_open(
+ private static native int sqlite3_blob_open(
@NotNull long ptrToDb, @NotNull String dbName,
@NotNull String tableName, @NotNull String columnName,
long iRow, int flags, @NotNull OutputPointer.sqlite3_blob out
@@ -458,7 +464,7 @@ public final class CApi {
return out.take();
};
- static native int sqlite3_blob_read(
+ private static native int sqlite3_blob_read(
@NotNull long ptrToBlob, @NotNull byte[] target, int iOffset
);
@@ -468,7 +474,7 @@ public final class CApi {
return sqlite3_blob_read(b.getNativePointer(), target, iOffset);
}
- static native int sqlite3_blob_reopen(
+ private static native int sqlite3_blob_reopen(
@NotNull long ptrToBlob, long newRowId
);
@@ -476,7 +482,7 @@ public final class CApi {
return sqlite3_blob_reopen(b.getNativePointer(), newRowId);
}
- static native int sqlite3_blob_write(
+ private static native int sqlite3_blob_write(
@NotNull long ptrToBlob, @NotNull byte[] bytes, int iOffset
);
@@ -486,7 +492,7 @@ public final class CApi {
return sqlite3_blob_write(b.getNativePointer(), bytes, iOffset);
}
- static native int sqlite3_busy_handler(
+ private static native int sqlite3_busy_handler(
@NotNull long ptrToDb, @Nullable BusyHandlerCallback handler
);
@@ -501,7 +507,7 @@ public final class CApi {
return sqlite3_busy_handler(db.getNativePointer(), handler);
}
- static native int sqlite3_busy_timeout(@NotNull long ptrToDb, int ms);
+ private static native int sqlite3_busy_timeout(@NotNull long ptrToDb, int ms);
public static int sqlite3_busy_timeout(@NotNull sqlite3 db, int ms){
return sqlite3_busy_timeout(db.getNativePointer(), ms);
@@ -511,64 +517,59 @@ public final class CApi {
@NotNull AutoExtensionCallback ax
);
- static native int sqlite3_changes(@NotNull long ptrToDb);
+ private static native int sqlite3_changes(@NotNull long ptrToDb);
public static int sqlite3_changes(@NotNull sqlite3 db){
return sqlite3_changes(db.getNativePointer());
}
- static native long sqlite3_changes64(@NotNull long ptrToDb);
+ private static native long sqlite3_changes64(@NotNull long ptrToDb);
public static long sqlite3_changes64(@NotNull sqlite3 db){
return sqlite3_changes64(db.getNativePointer());
}
- static native int sqlite3_clear_bindings(@NotNull long ptrToStmt);
+ private static native int sqlite3_clear_bindings(@NotNull long ptrToStmt);
public static int sqlite3_clear_bindings(@NotNull sqlite3_stmt stmt){
return sqlite3_clear_bindings(stmt.getNativePointer());
}
- static native int sqlite3_close(@Nullable long ptrToDb);
+ private static native int sqlite3_close(@Nullable long ptrToDb);
public static int sqlite3_close(@Nullable sqlite3 db){
- int rc = 0;
- if( null!=db ){
- rc = sqlite3_close(db.getNativePointer());
- if( 0==rc ) db.clearNativePointer();
- }
- return rc;
+ return null==db ? 0 : sqlite3_close(db.clearNativePointer());
}
- static native int sqlite3_close_v2(@Nullable long ptrToDb);
+ private static native int sqlite3_close_v2(@Nullable long ptrToDb);
public static int sqlite3_close_v2(@Nullable sqlite3 db){
- return db==null ? 0 : sqlite3_close_v2(db.clearNativePointer());
+ return null==db ? 0 : sqlite3_close_v2(db.clearNativePointer());
}
public static native byte[] sqlite3_column_blob(
@NotNull sqlite3_stmt stmt, int ndx
);
- static native int sqlite3_column_bytes(@NotNull long ptrToStmt, int ndx);
+ private static native int sqlite3_column_bytes(@NotNull long ptrToStmt, int ndx);
public static int sqlite3_column_bytes(@NotNull sqlite3_stmt stmt, int ndx){
return sqlite3_column_bytes(stmt.getNativePointer(), ndx);
}
- static native int sqlite3_column_bytes16(@NotNull long ptrToStmt, int ndx);
+ private static native int sqlite3_column_bytes16(@NotNull long ptrToStmt, int ndx);
public static int sqlite3_column_bytes16(@NotNull sqlite3_stmt stmt, int ndx){
return sqlite3_column_bytes16(stmt.getNativePointer(), ndx);
}
- static native int sqlite3_column_count(@NotNull long ptrToStmt);
+ private static native int sqlite3_column_count(@NotNull long ptrToStmt);
public static int sqlite3_column_count(@NotNull sqlite3_stmt stmt){
return sqlite3_column_count(stmt.getNativePointer());
}
- static native String sqlite3_column_decltype(@NotNull long ptrToStmt, int ndx);
+ private static native String sqlite3_column_decltype(@NotNull long ptrToStmt, int ndx);
public static String sqlite3_column_decltype(@NotNull sqlite3_stmt stmt, int ndx){
return sqlite3_column_decltype(stmt.getNativePointer(), ndx);
@@ -586,7 +587,7 @@ public final class CApi {
@NotNull sqlite3_stmt stmt, int ndx
);
- static native Object sqlite3_column_java_object(
+ private static native Object sqlite3_column_java_object(
@NotNull long ptrToStmt, int ndx
);
@@ -616,26 +617,35 @@ public final class CApi {
return type.isInstance(o) ? (T)o : null;
}
- static native String sqlite3_column_name(@NotNull long ptrToStmt, int ndx);
+ private static native String sqlite3_column_name(@NotNull long ptrToStmt, int ndx);
public static String sqlite3_column_name(@NotNull sqlite3_stmt stmt, int ndx){
return sqlite3_column_name(stmt.getNativePointer(), ndx);
}
- static native String sqlite3_column_database_name(@NotNull long ptrToStmt, int ndx);
+ private static native String sqlite3_column_database_name(@NotNull long ptrToStmt, int ndx);
+ /**
+ Only available if built with SQLITE_ENABLE_COLUMN_METADATA.
+ */
public static String sqlite3_column_database_name(@NotNull sqlite3_stmt stmt, int ndx){
return sqlite3_column_database_name(stmt.getNativePointer(), ndx);
}
- static native String sqlite3_column_origin_name(@NotNull long ptrToStmt, int ndx);
+ private static native String sqlite3_column_origin_name(@NotNull long ptrToStmt, int ndx);
+ /**
+ Only available if built with SQLITE_ENABLE_COLUMN_METADATA.
+ */
public static String sqlite3_column_origin_name(@NotNull sqlite3_stmt stmt, int ndx){
return sqlite3_column_origin_name(stmt.getNativePointer(), ndx);
}
- static native String sqlite3_column_table_name(@NotNull long ptrToStmt, int ndx);
+ private static native String sqlite3_column_table_name(@NotNull long ptrToStmt, int ndx);
+ /**
+ Only available if built with SQLITE_ENABLE_COLUMN_METADATA.
+ */
public static String sqlite3_column_table_name(@NotNull sqlite3_stmt stmt, int ndx){
return sqlite3_column_table_name(stmt.getNativePointer(), ndx);
}
@@ -693,7 +703,7 @@ public final class CApi {
// return rv;
// }
- static native int sqlite3_column_type(@NotNull long ptrToStmt, int ndx);
+ private static native int sqlite3_column_type(@NotNull long ptrToStmt, int ndx);
public static int sqlite3_column_type(@NotNull sqlite3_stmt stmt, int ndx){
return sqlite3_column_type(stmt.getNativePointer(), ndx);
@@ -703,7 +713,7 @@ public final class CApi {
@NotNull sqlite3_stmt stmt, int ndx
);
- static native int sqlite3_collation_needed(
+ private static native int sqlite3_collation_needed(
@NotNull long ptrToDb, @Nullable CollationNeededCallback callback
);
@@ -717,7 +727,7 @@ public final class CApi {
return sqlite3_collation_needed(db.getNativePointer(), callback);
}
- static native CommitHookCallback sqlite3_commit_hook(
+ private static native CommitHookCallback sqlite3_commit_hook(
@NotNull long ptrToDb, @Nullable CommitHookCallback hook
);
@@ -816,7 +826,7 @@ public final class CApi {
int nArg, int eTextRep, @NotNull SQLFunction func
);
- static native int sqlite3_data_count(@NotNull long ptrToStmt);
+ private static native int sqlite3_data_count(@NotNull long ptrToStmt);
public static int sqlite3_data_count(@NotNull sqlite3_stmt stmt){
return sqlite3_data_count(stmt.getNativePointer());
@@ -828,7 +838,7 @@ public final class CApi {
SQLITE_DBCONFIG_... options which uses this call form.
<p>Unlike the C API, this returns SQLITE_MISUSE if its db argument
- are null (as opposed to invoking UB).
+ is null (as opposed to invoking UB).
*/
public static native int sqlite3_db_config(
@NotNull sqlite3 db, int op, int onOff, @Nullable OutputPointer.Int32 out
@@ -851,7 +861,6 @@ public final class CApi {
return null==db ? null : sqlite3_db_name(db.getNativePointer(), ndx);
}
-
public static native String sqlite3_db_filename(
@NotNull sqlite3 db, @NotNull String dbName
);
@@ -871,7 +880,7 @@ public final class CApi {
public static native String sqlite3_errmsg(@NotNull sqlite3 db);
- static native int sqlite3_error_offset(@NotNull long ptrToDb);
+ private static native int sqlite3_error_offset(@NotNull long ptrToDb);
/**
Note that the returned byte offset values assume UTF-8-encoded
@@ -885,17 +894,17 @@ public final class CApi {
public static native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt);
- static native int sqlite3_extended_errcode(@NotNull long ptrToDb);
+ private static native int sqlite3_extended_errcode(@NotNull long ptrToDb);
public static int sqlite3_extended_errcode(@NotNull sqlite3 db){
return sqlite3_extended_errcode(db.getNativePointer());
}
- public static native boolean sqlite3_extended_result_codes(
- @NotNull sqlite3 db, boolean onoff
+ public static native int sqlite3_extended_result_codes(
+ @NotNull sqlite3 db, boolean on
);
- static native boolean sqlite3_get_autocommit(@NotNull long ptrToDb);
+ private static native boolean sqlite3_get_autocommit(@NotNull long ptrToDb);
public static boolean sqlite3_get_autocommit(@NotNull sqlite3 db){
return sqlite3_get_autocommit(db.getNativePointer());
@@ -905,7 +914,7 @@ public final class CApi {
@NotNull sqlite3_context cx, int n
);
- static native int sqlite3_finalize(long ptrToStmt);
+ private static native int sqlite3_finalize(long ptrToStmt);
public static int sqlite3_finalize(@NotNull sqlite3_stmt stmt){
return null==stmt ? 0 : sqlite3_finalize(stmt.clearNativePointer());
@@ -1199,26 +1208,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);
@@ -1277,7 +1286,7 @@ public final class CApi {
return sqlite3_prepare_multi(db, sql, 0, p);
}
- static native int sqlite3_preupdate_blobwrite(@NotNull long ptrToDb);
+ private static native int sqlite3_preupdate_blobwrite(@NotNull long ptrToDb);
/**
If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
@@ -1288,7 +1297,7 @@ public final class CApi {
return sqlite3_preupdate_blobwrite(db.getNativePointer());
}
- static native int sqlite3_preupdate_count(@NotNull long ptrToDb);
+ private static native int sqlite3_preupdate_count(@NotNull long ptrToDb);
/**
If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
@@ -1299,7 +1308,7 @@ public final class CApi {
return sqlite3_preupdate_count(db.getNativePointer());
}
- static native int sqlite3_preupdate_depth(@NotNull long ptrToDb);
+ private static native int sqlite3_preupdate_depth(@NotNull long ptrToDb);
/**
If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
@@ -1310,7 +1319,7 @@ public final class CApi {
return sqlite3_preupdate_depth(db.getNativePointer());
}
- static native PreupdateHookCallback sqlite3_preupdate_hook(
+ private static native PreupdateHookCallback sqlite3_preupdate_hook(
@NotNull long ptrToDb, @Nullable PreupdateHookCallback hook
);
@@ -1325,7 +1334,7 @@ public final class CApi {
return sqlite3_preupdate_hook(db.getNativePointer(), hook);
}
- static native int sqlite3_preupdate_new(@NotNull long ptrToDb, int col,
+ private static native int sqlite3_preupdate_new(@NotNull long ptrToDb, int col,
@NotNull OutputPointer.sqlite3_value out);
/**
@@ -1348,7 +1357,7 @@ public final class CApi {
return out.take();
}
- static native int sqlite3_preupdate_old(@NotNull long ptrToDb, int col,
+ private static native int sqlite3_preupdate_old(@NotNull long ptrToDb, int col,
@NotNull OutputPointer.sqlite3_value out);
/**
@@ -1399,7 +1408,7 @@ public final class CApi {
results in the C-level sqlite3_result_error() being called with a
complaint about the invalid argument.
*/
- static native void sqlite3_result_error(
+ private static native void sqlite3_result_error(
@NotNull sqlite3_context cx, @NotNull byte[] msg, int eTextRep
);
@@ -1471,9 +1480,6 @@ public final class CApi {
cross-language semantic mismatch and (B) Java doesn't need that
argument for its intended purpose (type safety).
- <p>Note that there is no sqlite3_column_java_object(), as the
- C-level API has no sqlite3_column_pointer() to proxy.
-
@see #sqlite3_value_java_object
@see #sqlite3_bind_java_object
*/
@@ -1676,7 +1682,7 @@ public final class CApi {
}
}
- static native RollbackHookCallback sqlite3_rollback_hook(
+ private static native RollbackHookCallback sqlite3_rollback_hook(
@NotNull long ptrToDb, @Nullable RollbackHookCallback hook
);
@@ -1732,13 +1738,13 @@ public final class CApi {
public static native boolean sqlite3_stmt_busy(@NotNull sqlite3_stmt stmt);
- static native int sqlite3_stmt_explain(@NotNull long ptrToStmt, int op);
+ private static native int sqlite3_stmt_explain(@NotNull long ptrToStmt, int op);
public static int sqlite3_stmt_explain(@NotNull sqlite3_stmt stmt, int op){
return sqlite3_stmt_explain(stmt.getNativePointer(), op);
}
- static native int sqlite3_stmt_isexplain(@NotNull long ptrToStmt);
+ private static native int sqlite3_stmt_isexplain(@NotNull long ptrToStmt);
public static int sqlite3_stmt_isexplain(@NotNull sqlite3_stmt stmt){
return sqlite3_stmt_isexplain(stmt.getNativePointer());
@@ -1788,7 +1794,7 @@ public final class CApi {
(int)escChar);
}
- static native int sqlite3_system_errno(@NotNull long ptrToDb);
+ private static native int sqlite3_system_errno(@NotNull long ptrToDb);
public static int sqlite3_system_errno(@NotNull sqlite3 db){
return sqlite3_system_errno(db.getNativePointer());
@@ -1834,13 +1840,13 @@ public final class CApi {
public static native int sqlite3_threadsafe();
- static native int sqlite3_total_changes(@NotNull long ptrToDb);
+ private static native int sqlite3_total_changes(@NotNull long ptrToDb);
public static int sqlite3_total_changes(@NotNull sqlite3 db){
return sqlite3_total_changes(db.getNativePointer());
}
- static native long sqlite3_total_changes64(@NotNull long ptrToDb);
+ private static native long sqlite3_total_changes64(@NotNull long ptrToDb);
public static long sqlite3_total_changes64(@NotNull sqlite3 db){
return sqlite3_total_changes64(db.getNativePointer());
@@ -1863,7 +1869,7 @@ public final class CApi {
@NotNull sqlite3 db, @Nullable String zSchema
);
- static native UpdateHookCallback sqlite3_update_hook(
+ private static native UpdateHookCallback sqlite3_update_hook(
@NotNull long ptrToDb, @Nullable UpdateHookCallback hook
);
@@ -1883,67 +1889,67 @@ public final class CApi {
sqlite3_create_function().
*/
- static native byte[] sqlite3_value_blob(@NotNull long ptrToValue);
+ private static native byte[] sqlite3_value_blob(@NotNull long ptrToValue);
public static byte[] sqlite3_value_blob(@NotNull sqlite3_value v){
return sqlite3_value_blob(v.getNativePointer());
}
- static native int sqlite3_value_bytes(@NotNull long ptrToValue);
+ private static native int sqlite3_value_bytes(@NotNull long ptrToValue);
public static int sqlite3_value_bytes(@NotNull sqlite3_value v){
return sqlite3_value_bytes(v.getNativePointer());
}
- static native int sqlite3_value_bytes16(@NotNull long ptrToValue);
+ private static native int sqlite3_value_bytes16(@NotNull long ptrToValue);
public static int sqlite3_value_bytes16(@NotNull sqlite3_value v){
return sqlite3_value_bytes16(v.getNativePointer());
}
- static native double sqlite3_value_double(@NotNull long ptrToValue);
+ private static native double sqlite3_value_double(@NotNull long ptrToValue);
public static double sqlite3_value_double(@NotNull sqlite3_value v){
return sqlite3_value_double(v.getNativePointer());
}
- static native sqlite3_value sqlite3_value_dup(@NotNull long ptrToValue);
+ private static native sqlite3_value sqlite3_value_dup(@NotNull long ptrToValue);
public static sqlite3_value sqlite3_value_dup(@NotNull sqlite3_value v){
return sqlite3_value_dup(v.getNativePointer());
}
- static native int sqlite3_value_encoding(@NotNull long ptrToValue);
+ private static native int sqlite3_value_encoding(@NotNull long ptrToValue);
public static int sqlite3_value_encoding(@NotNull sqlite3_value v){
return sqlite3_value_encoding(v.getNativePointer());
}
- static native void sqlite3_value_free(@Nullable long ptrToValue);
+ private static native void sqlite3_value_free(@Nullable long ptrToValue);
public static void sqlite3_value_free(@Nullable sqlite3_value v){
- sqlite3_value_free(v.getNativePointer());
+ if( null!=v ) sqlite3_value_free(v.clearNativePointer());
}
- static native boolean sqlite3_value_frombind(@NotNull long ptrToValue);
+ private static native boolean sqlite3_value_frombind(@NotNull long ptrToValue);
public static boolean sqlite3_value_frombind(@NotNull sqlite3_value v){
return sqlite3_value_frombind(v.getNativePointer());
}
- static native int sqlite3_value_int(@NotNull long ptrToValue);
+ private static native int sqlite3_value_int(@NotNull long ptrToValue);
public static int sqlite3_value_int(@NotNull sqlite3_value v){
return sqlite3_value_int(v.getNativePointer());
}
- static native long sqlite3_value_int64(@NotNull long ptrToValue);
+ private static native long sqlite3_value_int64(@NotNull long ptrToValue);
public static long sqlite3_value_int64(@NotNull sqlite3_value v){
return sqlite3_value_int64(v.getNativePointer());
}
- static native Object sqlite3_value_java_object(@NotNull long ptrToValue);
+ private static native Object sqlite3_value_java_object(@NotNull long ptrToValue);
/**
If the given value was set using {@link
@@ -1969,25 +1975,25 @@ public final class CApi {
return type.isInstance(o) ? (T)o : null;
}
- static native int sqlite3_value_nochange(@NotNull long ptrToValue);
+ private static native int sqlite3_value_nochange(@NotNull long ptrToValue);
public static int sqlite3_value_nochange(@NotNull sqlite3_value v){
return sqlite3_value_nochange(v.getNativePointer());
}
- static native int sqlite3_value_numeric_type(@NotNull long ptrToValue);
+ private static native int sqlite3_value_numeric_type(@NotNull long ptrToValue);
public static int sqlite3_value_numeric_type(@NotNull sqlite3_value v){
return sqlite3_value_numeric_type(v.getNativePointer());
}
- static native int sqlite3_value_subtype(@NotNull long ptrToValue);
+ private static native int sqlite3_value_subtype(@NotNull long ptrToValue);
public static int sqlite3_value_subtype(@NotNull sqlite3_value v){
return sqlite3_value_subtype(v.getNativePointer());
}
- static native byte[] sqlite3_value_text(@NotNull long ptrToValue);
+ private static native byte[] sqlite3_value_text(@NotNull long ptrToValue);
/**
Functions identially to the C API, and this note is just to
@@ -1999,13 +2005,13 @@ public final class CApi {
return sqlite3_value_text(v.getNativePointer());
}
- static native String sqlite3_value_text16(@NotNull long ptrToValue);
+ private static native String sqlite3_value_text16(@NotNull long ptrToValue);
public static String sqlite3_value_text16(@NotNull sqlite3_value v){
return sqlite3_value_text16(v.getNativePointer());
}
- static native int sqlite3_value_type(@NotNull long ptrToValue);
+ private static native int sqlite3_value_type(@NotNull long ptrToValue);
public static int sqlite3_value_type(@NotNull sqlite3_value v){
return sqlite3_value_type(v.getNativePointer());
@@ -2280,7 +2286,6 @@ public final class CApi {
// prepare flags
public static final int SQLITE_PREPARE_PERSISTENT = 1;
- public static final int SQLITE_PREPARE_NORMALIZE = 2;
public static final int SQLITE_PREPARE_NO_VTAB = 4;
// result codes
diff --git a/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java b/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java
index 049570256..7df748e8d 100644
--- a/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java
+++ b/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java
@@ -24,8 +24,9 @@ package org.sqlite.jni.capi;
propagated. For callback interfaces which support returning error
info to the core, the JNI binding will convert any exceptions to
C-level error information. For callback interfaces which do not
- support, all exceptions will necessarily be suppressed in order to
- retain the C-style no-throw semantics.
+ support returning error information, all exceptions will
+ necessarily be suppressed in order to retain the C-style no-throw
+ semantics and avoid invoking undefined behavior in the C layer.
<p>Callbacks of this style follow a common naming convention:
diff --git a/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java b/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java
index fe61fe506..ffd7fa94a 100644
--- a/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java
+++ b/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java
@@ -21,8 +21,9 @@ public interface CollationNeededCallback extends CallbackProxy {
Has the same semantics as the C-level sqlite3_create_collation()
callback.
- <p>If it throws, the exception message is passed on to the db and
- the exception is suppressed.
+ <p>Because the C API has no mechanism for reporting errors
+ from this callbacks, any exceptions thrown by this callback
+ are suppressed.
*/
- int call(sqlite3 db, int eTextRep, String collationName);
+ void call(sqlite3 db, int eTextRep, String collationName);
}
diff --git a/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java b/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java
index 24373bdf2..e1e55c78d 100644
--- a/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java
+++ b/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java
@@ -19,7 +19,8 @@ package org.sqlite.jni.capi;
public interface CommitHookCallback extends CallbackProxy {
/**
Works as documented for the C-level sqlite3_commit_hook()
- callback. Must not throw.
+ callback. If it throws, the exception is translated into
+ a db-level error.
*/
int call();
}
diff --git a/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java b/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java
index 99d3fb035..38f7c5613 100644
--- a/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java
+++ b/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java
@@ -19,7 +19,8 @@ package org.sqlite.jni.capi;
public interface PreupdateHookCallback extends CallbackProxy {
/**
Must function as described for the C-level sqlite3_preupdate_hook()
- callback.
+ callback. If it throws, the exception is translated to a
+ db-level error and the exception is suppressed.
*/
void call(sqlite3 db, int op, String dbName, String dbTable,
long iKey1, long iKey2 );
diff --git a/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java b/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java
index 5ce17e718..cf9c4b6e7 100644
--- a/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java
+++ b/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java
@@ -18,8 +18,9 @@ package org.sqlite.jni.capi;
*/
public interface RollbackHookCallback extends CallbackProxy {
/**
- Works as documented for the C-level sqlite3_rollback_hook()
- callback.
+ Must function as documented for the C-level sqlite3_rollback_hook()
+ callback. If it throws, the exception is translated into
+ a db-level error.
*/
void call();
}
diff --git a/ext/jni/src/org/sqlite/jni/capi/Tester1.java b/ext/jni/src/org/sqlite/jni/capi/Tester1.java
index 465718565..3ac58c67d 100644
--- a/ext/jni/src/org/sqlite/jni/capi/Tester1.java
+++ b/ext/jni/src/org/sqlite/jni/capi/Tester1.java
@@ -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.
@@ -327,7 +327,7 @@ public class Tester1 implements Runnable {
rc = sqlite3_prepare_v3(db, "INSERT INTO t2(a) VALUES(1),(2),(3)",
- SQLITE_PREPARE_NORMALIZE, outStmt);
+ 0, outStmt);
affirm(0 == rc);
stmt = outStmt.get();
affirm(0 != stmt.getNativePointer());
@@ -382,6 +382,15 @@ public class Tester1 implements Runnable {
stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
affirm( sqlite3_stmt_readonly(stmt) );
affirm( !sqlite3_stmt_busy(stmt) );
+ if( sqlite3_compileoption_used("ENABLE_COLUMN_METADATA") ){
+ /* Unlike in native C code, JNI won't trigger an
+ UnsatisfiedLinkError until these are called (on Linux, at
+ least). */
+ affirm("t".equals(sqlite3_column_table_name(stmt,0)));
+ affirm("main".equals(sqlite3_column_database_name(stmt,0)));
+ affirm("a".equals(sqlite3_column_origin_name(stmt,0)));
+ }
+
int total2 = 0;
while( SQLITE_ROW == sqlite3_step(stmt) ){
affirm( sqlite3_stmt_busy(stmt) );
@@ -593,9 +602,9 @@ public class Tester1 implements Runnable {
};
final CollationNeededCallback collLoader = new CollationNeededCallback(){
@Override
- public int call(sqlite3 dbArg, int eTextRep, String collationName){
+ public void call(sqlite3 dbArg, int eTextRep, String collationName){
affirm(dbArg == db/* as opposed to a temporary object*/);
- return sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation);
+ sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation);
}
};
int rc = sqlite3_collation_needed(db, collLoader);
@@ -1031,48 +1040,48 @@ public class Tester1 implements Runnable {
@SingleThreadOnly /* because threads inherently break this test */
private static void testBusy(){
final String dbName = "_busy-handler.db";
- final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3();
- final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+ try{
+ final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3();
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
- int rc = sqlite3_open(dbName, outDb);
- ++metrics.dbOpen;
- affirm( 0 == rc );
- final sqlite3 db1 = outDb.get();
- execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)");
- rc = sqlite3_open(dbName, outDb);
- ++metrics.dbOpen;
- affirm( 0 == rc );
- affirm( outDb.get() != db1 );
- final sqlite3 db2 = outDb.get();
-
- affirm( "main".equals( sqlite3_db_name(db1, 0) ) );
- rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo");
- affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) );
- affirm( "foo".equals( sqlite3_db_name(db1, 0) ) );
-
- final ValueHolder<Integer> xBusyCalled = new ValueHolder<>(0);
- BusyHandlerCallback handler = new BusyHandlerCallback(){
- @Override public int call(int n){
- //outln("busy handler #"+n);
- return n > 2 ? 0 : ++xBusyCalled.value;
- }
- };
- rc = sqlite3_busy_handler(db2, handler);
- affirm(0 == rc);
+ int rc = sqlite3_open(dbName, outDb);
+ ++metrics.dbOpen;
+ affirm( 0 == rc );
+ final sqlite3 db1 = outDb.get();
+ execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)");
+ rc = sqlite3_open(dbName, outDb);
+ ++metrics.dbOpen;
+ affirm( 0 == rc );
+ affirm( outDb.get() != db1 );
+ final sqlite3 db2 = outDb.get();
+
+ affirm( "main".equals( sqlite3_db_name(db1, 0) ) );
+ rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo");
+ affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) );
+ affirm( "foo".equals( sqlite3_db_name(db1, 0) ) );
+ affirm( SQLITE_MISUSE == sqlite3_db_config(db1, 0, 0, null) );
+
+ final ValueHolder<Integer> xBusyCalled = new ValueHolder<>(0);
+ BusyHandlerCallback handler = new BusyHandlerCallback(){
+ @Override public int call(int n){
+ //outln("busy handler #"+n);
+ return n > 2 ? 0 : ++xBusyCalled.value;
+ }
+ };
+ rc = sqlite3_busy_handler(db2, handler);
+ affirm(0 == rc);
- // Force a locked condition...
- execSql(db1, "BEGIN EXCLUSIVE");
- rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt);
- affirm( SQLITE_BUSY == rc);
- affirm( null == outStmt.get() );
- affirm( 3 == xBusyCalled.value );
- sqlite3_close_v2(db1);
- sqlite3_close_v2(db2);
- try{
- final java.io.File f = new java.io.File(dbName);
- f.delete();
- }catch(Exception e){
- /* ignore */
+ // Force a locked condition...
+ execSql(db1, "BEGIN EXCLUSIVE");
+ rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt);
+ affirm( SQLITE_BUSY == rc);
+ affirm( null == outStmt.get() );
+ affirm( 3 == xBusyCalled.value );
+ sqlite3_close_v2(db1);
+ sqlite3_close_v2(db2);
+ }finally{
+ try{(new java.io.File(dbName)).delete();}
+ catch(Exception e){/* ignore */}
}
}
@@ -1096,6 +1105,7 @@ public class Tester1 implements Runnable {
private void testCommitHook(){
final sqlite3 db = createNewDb();
+ sqlite3_extended_result_codes(db, true);
final ValueHolder<Integer> counter = new ValueHolder<>(0);
final ValueHolder<Integer> hookResult = new ValueHolder<>(0);
final CommitHookCallback theHook = new CommitHookCallback(){
@@ -1138,7 +1148,7 @@ public class Tester1 implements Runnable {
affirm( 5 == counter.value );
hookResult.value = SQLITE_ERROR;
int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;");
- affirm( SQLITE_CONSTRAINT == rc );
+ affirm( SQLITE_CONSTRAINT_COMMITHOOK == rc );
affirm( 6 == counter.value );
sqlite3_close_v2(db);
}
@@ -1357,6 +1367,9 @@ public class Tester1 implements Runnable {
authRc.value = SQLITE_DENY;
int rc = execSql(db, false, "UPDATE t SET a=2");
affirm( SQLITE_AUTH==rc );
+ sqlite3_set_authorizer(db, null);
+ rc = execSql(db, false, "UPDATE t SET a=2");
+ affirm( 0==rc );
// TODO: expand these tests considerably
sqlite3_close(db);
}
@@ -1418,7 +1431,7 @@ public class Tester1 implements Runnable {
val.value = 0;
final AutoExtensionCallback ax2 = new AutoExtensionCallback(){
- @Override public synchronized int call(sqlite3 db){
+ @Override public int call(sqlite3 db){
++val.value;
return 0;
}
@@ -1629,7 +1642,7 @@ public class Tester1 implements Runnable {
sqlite3_finalize(stmt);
b = sqlite3_blob_open(db, "main", "t", "a",
- sqlite3_last_insert_rowid(db), 1);
+ sqlite3_last_insert_rowid(db), 0);
affirm( null!=b );
rc = sqlite3_blob_reopen(b, 2);
affirm( 0==rc );
@@ -1701,7 +1714,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 +1776,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 +1809,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")){
@@ -1904,6 +1920,7 @@ public class Tester1 implements Runnable {
sqlite3_libversion_number(),"\n",
sqlite3_libversion(),"\n",SQLITE_SOURCE_ID,"\n",
"SQLITE_THREADSAFE=",sqlite3_threadsafe());
+ outln("JVM NIO support? ",sqlite3_jni_supports_nio() ? "YES" : "NO");
final boolean showLoopCount = (nRepeat>1 && nThread>1);
if( showLoopCount ){
outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each.");
diff --git a/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java b/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java
index 33d72a5dd..e3d491f67 100644
--- a/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java
+++ b/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java
@@ -19,7 +19,8 @@ package org.sqlite.jni.capi;
public interface UpdateHookCallback extends CallbackProxy {
/**
Must function as described for the C-level sqlite3_update_hook()
- callback.
+ callback. If it throws, the exception is translated into
+ a db-level error.
*/
void call(int opId, String dbName, String tableName, long rowId);
}
diff --git a/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java b/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java
index b3f03ac86..6d5db3f66 100644
--- a/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java
+++ b/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java
@@ -9,14 +9,15 @@
** May you share freely, never taking more than you give.
**
*************************************************************************
-** This file contains a set of tests for the sqlite3 JNI bindings.
+** This file holds utility code for the sqlite3 JNI bindings.
*/
package org.sqlite.jni.capi;
/**
A helper class which simply holds a single value. Its primary use
is for communicating values out of anonymous classes, as doing so
- requires a "final" reference.
+ requires a "final" reference, as well as communicating aggregate
+ SQL function state across calls to such functions.
*/
public class ValueHolder<T> {
public T value;
diff --git a/ext/jni/src/org/sqlite/jni/capi/sqlite3.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3.java
index 901317f0e..cc6f2e6e8 100644
--- a/ext/jni/src/org/sqlite/jni/capi/sqlite3.java
+++ b/ext/jni/src/org/sqlite/jni/capi/sqlite3.java
@@ -38,6 +38,6 @@ public final class sqlite3 extends NativePointerHolder<sqlite3>
}
@Override public void close(){
- CApi.sqlite3_close_v2(this.clearNativePointer());
+ CApi.sqlite3_close_v2(this);
}
}
diff --git a/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java
index 3b8b71f8a..564891c72 100644
--- a/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java
+++ b/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java
@@ -25,6 +25,6 @@ public final class sqlite3_stmt extends NativePointerHolder<sqlite3_stmt>
private sqlite3_stmt(){}
@Override public void close(){
- CApi.sqlite3_finalize(this.clearNativePointer());
+ CApi.sqlite3_finalize(this);
}
}
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java
index 6a38d4b53..fc63b5354 100644
--- a/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java
@@ -12,10 +12,6 @@
** This file is part of the wrapper1 interface for sqlite3.
*/
package org.sqlite.jni.wrapper1;
-import org.sqlite.jni.capi.CApi;
-import org.sqlite.jni.annotation.*;
-import org.sqlite.jni.capi.sqlite3_context;
-import org.sqlite.jni.capi.sqlite3_value;
/**
EXPERIMENTAL/INCOMPLETE/UNTESTED
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java
index 2d0ebfaf3..b3317029c 100644
--- a/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java
@@ -56,7 +56,7 @@ public interface SqlFunction {
*/
Arguments(sqlite3_context cx, sqlite3_value args[]){
this.cx = cx;
- this.args = args==null ? new sqlite3_value[0] : args;;
+ this.args = args==null ? new sqlite3_value[0] : args;
this.length = this.args.length;
}
@@ -76,6 +76,16 @@ public interface SqlFunction {
//! Returns the underlying sqlite3_context for these arguments.
sqlite3_context getContext(){return cx;}
+ /**
+ Returns the Sqlite (db) object associated with this UDF call,
+ or null if the UDF is somehow called without such an object or
+ the db has been closed in an untimely manner (e.g. closed by a
+ UDF call).
+ */
+ public Sqlite getDb(){
+ return Sqlite.fromNative( CApi.sqlite3_context_db_handle(cx) );
+ }
+
public int getArgCount(){ return args.length; }
public int getInt(int argNdx){return CApi.sqlite3_value_int(valueAt(argNdx));}
@@ -107,6 +117,10 @@ public interface SqlFunction {
public void resultErrorCode(int rc){CApi.sqlite3_result_error_code(cx, rc);}
public void resultObject(Object o){CApi.sqlite3_result_java_object(cx, o);}
public void resultNull(){CApi.sqlite3_result_null(cx);}
+ /**
+ Analog to sqlite3_result_value(), using the Value object at the
+ given argument index.
+ */
public void resultArg(int argNdx){CApi.sqlite3_result_value(cx, valueAt(argNdx));}
public void resultSubtype(int subtype){CApi.sqlite3_result_subtype(cx, subtype);}
public void resultZeroBlob(long n){
@@ -121,6 +135,17 @@ public interface SqlFunction {
public void resultText16(byte[] utf16){CApi.sqlite3_result_text16(cx, utf16);}
public void resultText16(String txt){CApi.sqlite3_result_text16(cx, txt);}
+ /**
+ Callbacks should invoke this on OOM errors, instead of throwing
+ OutOfMemoryError, because the latter cannot be propagated
+ through the C API.
+ */
+ public void resultNoMem(){CApi.sqlite3_result_error_nomem(cx);}
+
+ /**
+ Analog to sqlite3_set_auxdata() but throws if argNdx is out of
+ range.
+ */
public void setAuxData(int argNdx, Object o){
/* From the API docs: https://www.sqlite.org/c3ref/get_auxdata.html
@@ -132,6 +157,10 @@ public interface SqlFunction {
CApi.sqlite3_set_auxdata(cx, argNdx, o);
}
+ /**
+ Analog to sqlite3_get_auxdata() but throws if argNdx is out of
+ range.
+ */
public Object getAuxData(int argNdx){
valueAt(argNdx);
return CApi.sqlite3_get_auxdata(cx, argNdx);
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java b/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java
index b464bd7d5..e61b7e59d 100644
--- a/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java
@@ -13,10 +13,11 @@
*/
package org.sqlite.jni.wrapper1;
import java.nio.charset.StandardCharsets;
-import static org.sqlite.jni.capi.CApi.*;
import org.sqlite.jni.capi.CApi;
import org.sqlite.jni.capi.sqlite3;
import org.sqlite.jni.capi.sqlite3_stmt;
+import org.sqlite.jni.capi.sqlite3_backup;
+import org.sqlite.jni.capi.sqlite3_blob;
import org.sqlite.jni.capi.OutputPointer;
/**
@@ -29,9 +30,115 @@ import org.sqlite.jni.capi.OutputPointer;
public final class Sqlite implements AutoCloseable {
private sqlite3 db;
+ public static final int OK = CApi.SQLITE_OK;
+ public static final int ERROR = CApi.SQLITE_ERROR;
+ public static final int INTERNAL = CApi.SQLITE_INTERNAL;
+ public static final int PERM = CApi.SQLITE_PERM;
+ public static final int ABORT = CApi.SQLITE_ABORT;
+ public static final int BUSY = CApi.SQLITE_BUSY;
+ public static final int LOCKED = CApi.SQLITE_LOCKED;
+ public static final int NOMEM = CApi.SQLITE_NOMEM;
+ public static final int READONLY = CApi.SQLITE_READONLY;
+ public static final int INTERRUPT = CApi.SQLITE_INTERRUPT;
+ public static final int IOERR = CApi.SQLITE_IOERR;
+ public static final int CORRUPT = CApi.SQLITE_CORRUPT;
+ public static final int NOTFOUND = CApi.SQLITE_NOTFOUND;
+ public static final int FULL = CApi.SQLITE_FULL;
+ public static final int CANTOPEN = CApi.SQLITE_CANTOPEN;
+ public static final int PROTOCOL = CApi.SQLITE_PROTOCOL;
+ public static final int EMPTY = CApi.SQLITE_EMPTY;
+ public static final int SCHEMA = CApi.SQLITE_SCHEMA;
+ public static final int TOOBIG = CApi.SQLITE_TOOBIG;
+ public static final int CONSTRAINT = CApi. SQLITE_CONSTRAINT;
+ public static final int MISMATCH = CApi.SQLITE_MISMATCH;
+ public static final int MISUSE = CApi.SQLITE_MISUSE;
+ public static final int NOLFS = CApi.SQLITE_NOLFS;
+ public static final int AUTH = CApi.SQLITE_AUTH;
+ public static final int FORMAT = CApi.SQLITE_FORMAT;
+ public static final int RANGE = CApi.SQLITE_RANGE;
+ public static final int NOTADB = CApi.SQLITE_NOTADB;
+ public static final int NOTICE = CApi.SQLITE_NOTICE;
+ public static final int WARNING = CApi.SQLITE_WARNING;
+ public static final int ROW = CApi.SQLITE_ROW;
+ public static final int DONE = CApi.SQLITE_DONE;
+ public static final int ERROR_MISSING_COLLSEQ = CApi.SQLITE_ERROR_MISSING_COLLSEQ;
+ public static final int ERROR_RETRY = CApi.SQLITE_ERROR_RETRY;
+ public static final int ERROR_SNAPSHOT = CApi.SQLITE_ERROR_SNAPSHOT;
+ public static final int IOERR_READ = CApi.SQLITE_IOERR_READ;
+ public static final int IOERR_SHORT_READ = CApi.SQLITE_IOERR_SHORT_READ;
+ public static final int IOERR_WRITE = CApi.SQLITE_IOERR_WRITE;
+ public static final int IOERR_FSYNC = CApi.SQLITE_IOERR_FSYNC;
+ public static final int IOERR_DIR_FSYNC = CApi.SQLITE_IOERR_DIR_FSYNC;
+ public static final int IOERR_TRUNCATE = CApi.SQLITE_IOERR_TRUNCATE;
+ public static final int IOERR_FSTAT = CApi.SQLITE_IOERR_FSTAT;
+ public static final int IOERR_UNLOCK = CApi.SQLITE_IOERR_UNLOCK;
+ public static final int IOERR_RDLOCK = CApi.SQLITE_IOERR_RDLOCK;
+ public static final int IOERR_DELETE = CApi.SQLITE_IOERR_DELETE;
+ public static final int IOERR_BLOCKED = CApi.SQLITE_IOERR_BLOCKED;
+ public static final int IOERR_NOMEM = CApi.SQLITE_IOERR_NOMEM;
+ public static final int IOERR_ACCESS = CApi.SQLITE_IOERR_ACCESS;
+ public static final int IOERR_CHECKRESERVEDLOCK = CApi.SQLITE_IOERR_CHECKRESERVEDLOCK;
+ public static final int IOERR_LOCK = CApi.SQLITE_IOERR_LOCK;
+ public static final int IOERR_CLOSE = CApi.SQLITE_IOERR_CLOSE;
+ public static final int IOERR_DIR_CLOSE = CApi.SQLITE_IOERR_DIR_CLOSE;
+ public static final int IOERR_SHMOPEN = CApi.SQLITE_IOERR_SHMOPEN;
+ public static final int IOERR_SHMSIZE = CApi.SQLITE_IOERR_SHMSIZE;
+ public static final int IOERR_SHMLOCK = CApi.SQLITE_IOERR_SHMLOCK;
+ public static final int IOERR_SHMMAP = CApi.SQLITE_IOERR_SHMMAP;
+ public static final int IOERR_SEEK = CApi.SQLITE_IOERR_SEEK;
+ public static final int IOERR_DELETE_NOENT = CApi.SQLITE_IOERR_DELETE_NOENT;
+ public static final int IOERR_MMAP = CApi.SQLITE_IOERR_MMAP;
+ public static final int IOERR_GETTEMPPATH = CApi.SQLITE_IOERR_GETTEMPPATH;
+ public static final int IOERR_CONVPATH = CApi.SQLITE_IOERR_CONVPATH;
+ public static final int IOERR_VNODE = CApi.SQLITE_IOERR_VNODE;
+ public static final int IOERR_AUTH = CApi.SQLITE_IOERR_AUTH;
+ public static final int IOERR_BEGIN_ATOMIC = CApi.SQLITE_IOERR_BEGIN_ATOMIC;
+ public static final int IOERR_COMMIT_ATOMIC = CApi.SQLITE_IOERR_COMMIT_ATOMIC;
+ public static final int IOERR_ROLLBACK_ATOMIC = CApi.SQLITE_IOERR_ROLLBACK_ATOMIC;
+ public static final int IOERR_DATA = CApi.SQLITE_IOERR_DATA;
+ public static final int IOERR_CORRUPTFS = CApi.SQLITE_IOERR_CORRUPTFS;
+ public static final int LOCKED_SHAREDCACHE = CApi.SQLITE_LOCKED_SHAREDCACHE;
+ public static final int LOCKED_VTAB = CApi.SQLITE_LOCKED_VTAB;
+ public static final int BUSY_RECOVERY = CApi.SQLITE_BUSY_RECOVERY;
+ public static final int BUSY_SNAPSHOT = CApi.SQLITE_BUSY_SNAPSHOT;
+ public static final int BUSY_TIMEOUT = CApi.SQLITE_BUSY_TIMEOUT;
+ public static final int CANTOPEN_NOTEMPDIR = CApi.SQLITE_CANTOPEN_NOTEMPDIR;
+ public static final int CANTOPEN_ISDIR = CApi.SQLITE_CANTOPEN_ISDIR;
+ public static final int CANTOPEN_FULLPATH = CApi.SQLITE_CANTOPEN_FULLPATH;
+ public static final int CANTOPEN_CONVPATH = CApi.SQLITE_CANTOPEN_CONVPATH;
+ public static final int CANTOPEN_SYMLINK = CApi.SQLITE_CANTOPEN_SYMLINK;
+ public static final int CORRUPT_VTAB = CApi.SQLITE_CORRUPT_VTAB;
+ public static final int CORRUPT_SEQUENCE = CApi.SQLITE_CORRUPT_SEQUENCE;
+ public static final int CORRUPT_INDEX = CApi.SQLITE_CORRUPT_INDEX;
+ public static final int READONLY_RECOVERY = CApi.SQLITE_READONLY_RECOVERY;
+ public static final int READONLY_CANTLOCK = CApi.SQLITE_READONLY_CANTLOCK;
+ public static final int READONLY_ROLLBACK = CApi.SQLITE_READONLY_ROLLBACK;
+ public static final int READONLY_DBMOVED = CApi.SQLITE_READONLY_DBMOVED;
+ public static final int READONLY_CANTINIT = CApi.SQLITE_READONLY_CANTINIT;
+ public static final int READONLY_DIRECTORY = CApi.SQLITE_READONLY_DIRECTORY;
+ public static final int ABORT_ROLLBACK = CApi.SQLITE_ABORT_ROLLBACK;
+ public static final int CONSTRAINT_CHECK = CApi.SQLITE_CONSTRAINT_CHECK;
+ public static final int CONSTRAINT_COMMITHOOK = CApi.SQLITE_CONSTRAINT_COMMITHOOK;
+ public static final int CONSTRAINT_FOREIGNKEY = CApi.SQLITE_CONSTRAINT_FOREIGNKEY;
+ public static final int CONSTRAINT_FUNCTION = CApi.SQLITE_CONSTRAINT_FUNCTION;
+ public static final int CONSTRAINT_NOTNULL = CApi.SQLITE_CONSTRAINT_NOTNULL;
+ public static final int CONSTRAINT_PRIMARYKEY = CApi.SQLITE_CONSTRAINT_PRIMARYKEY;
+ public static final int CONSTRAINT_TRIGGER = CApi.SQLITE_CONSTRAINT_TRIGGER;
+ public static final int CONSTRAINT_UNIQUE = CApi.SQLITE_CONSTRAINT_UNIQUE;
+ public static final int CONSTRAINT_VTAB = CApi.SQLITE_CONSTRAINT_VTAB;
+ public static final int CONSTRAINT_ROWID = CApi.SQLITE_CONSTRAINT_ROWID;
+ public static final int CONSTRAINT_PINNED = CApi.SQLITE_CONSTRAINT_PINNED;
+ public static final int CONSTRAINT_DATATYPE = CApi.SQLITE_CONSTRAINT_DATATYPE;
+ public static final int NOTICE_RECOVER_WAL = CApi.SQLITE_NOTICE_RECOVER_WAL;
+ public static final int NOTICE_RECOVER_ROLLBACK = CApi.SQLITE_NOTICE_RECOVER_ROLLBACK;
+ public static final int WARNING_AUTOINDEX = CApi.SQLITE_WARNING_AUTOINDEX;
+ public static final int AUTH_USER = CApi.SQLITE_AUTH_USER;
+ public static final int OK_LOAD_PERMANENTLY = CApi.SQLITE_OK_LOAD_PERMANENTLY;
+
public static final int OPEN_READWRITE = CApi.SQLITE_OPEN_READWRITE;
public static final int OPEN_CREATE = CApi.SQLITE_OPEN_CREATE;
public static final int OPEN_EXRESCODE = CApi.SQLITE_OPEN_EXRESCODE;
+
public static final int TXN_NONE = CApi.SQLITE_TXN_NONE;
public static final int TXN_READ = CApi.SQLITE_TXN_READ;
public static final int TXN_WRITE = CApi.SQLITE_TXN_WRITE;
@@ -44,6 +151,20 @@ public final class Sqlite implements AutoCloseable {
public static final int STATUS_PAGECACHE_SIZE = CApi.SQLITE_STATUS_PAGECACHE_SIZE;
public static final int STATUS_MALLOC_COUNT = CApi.SQLITE_STATUS_MALLOC_COUNT;
+ public static final int DBSTATUS_LOOKASIDE_USED = CApi.SQLITE_DBSTATUS_LOOKASIDE_USED;
+ public static final int DBSTATUS_CACHE_USED = CApi.SQLITE_DBSTATUS_CACHE_USED;
+ public static final int DBSTATUS_SCHEMA_USED = CApi.SQLITE_DBSTATUS_SCHEMA_USED;
+ public static final int DBSTATUS_STMT_USED = CApi.SQLITE_DBSTATUS_STMT_USED;
+ public static final int DBSTATUS_LOOKASIDE_HIT = CApi.SQLITE_DBSTATUS_LOOKASIDE_HIT;
+ public static final int DBSTATUS_LOOKASIDE_MISS_SIZE = CApi.SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE;
+ public static final int DBSTATUS_LOOKASIDE_MISS_FULL = CApi.SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL;
+ public static final int DBSTATUS_CACHE_HIT = CApi.SQLITE_DBSTATUS_CACHE_HIT;
+ public static final int DBSTATUS_CACHE_MISS = CApi.SQLITE_DBSTATUS_CACHE_MISS;
+ public static final int DBSTATUS_CACHE_WRITE = CApi.SQLITE_DBSTATUS_CACHE_WRITE;
+ public static final int DBSTATUS_DEFERRED_FKS = CApi.SQLITE_DBSTATUS_DEFERRED_FKS;
+ public static final int DBSTATUS_CACHE_USED_SHARED = CApi.SQLITE_DBSTATUS_CACHE_USED_SHARED;
+ public static final int DBSTATUS_CACHE_SPILL = CApi.SQLITE_DBSTATUS_CACHE_SPILL;
+
public static final int LIMIT_LENGTH = CApi.SQLITE_LIMIT_LENGTH;
public static final int LIMIT_SQL_LENGTH = CApi.SQLITE_LIMIT_SQL_LENGTH;
public static final int LIMIT_COLUMN = CApi.SQLITE_LIMIT_COLUMN;
@@ -58,14 +179,95 @@ public final class Sqlite implements AutoCloseable {
public static final int LIMIT_WORKER_THREADS = CApi.SQLITE_LIMIT_WORKER_THREADS;
public static final int PREPARE_PERSISTENT = CApi.SQLITE_PREPARE_PERSISTENT;
- 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;
+
+ public static final int DBCONFIG_ENABLE_FKEY = CApi.SQLITE_DBCONFIG_ENABLE_FKEY;
+ public static final int DBCONFIG_ENABLE_TRIGGER = CApi.SQLITE_DBCONFIG_ENABLE_TRIGGER;
+ public static final int DBCONFIG_ENABLE_FTS3_TOKENIZER = CApi.SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER;
+ public static final int DBCONFIG_ENABLE_LOAD_EXTENSION = CApi.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION;
+ public static final int DBCONFIG_NO_CKPT_ON_CLOSE = CApi.SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE;
+ public static final int DBCONFIG_ENABLE_QPSG = CApi.SQLITE_DBCONFIG_ENABLE_QPSG;
+ public static final int DBCONFIG_TRIGGER_EQP = CApi.SQLITE_DBCONFIG_TRIGGER_EQP;
+ public static final int DBCONFIG_RESET_DATABASE = CApi.SQLITE_DBCONFIG_RESET_DATABASE;
+ public static final int DBCONFIG_DEFENSIVE = CApi.SQLITE_DBCONFIG_DEFENSIVE;
+ public static final int DBCONFIG_WRITABLE_SCHEMA = CApi.SQLITE_DBCONFIG_WRITABLE_SCHEMA;
+ public static final int DBCONFIG_LEGACY_ALTER_TABLE = CApi.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE;
+ public static final int DBCONFIG_DQS_DML = CApi.SQLITE_DBCONFIG_DQS_DML;
+ public static final int DBCONFIG_DQS_DDL = CApi.SQLITE_DBCONFIG_DQS_DDL;
+ public static final int DBCONFIG_ENABLE_VIEW = CApi.SQLITE_DBCONFIG_ENABLE_VIEW;
+ public static final int DBCONFIG_LEGACY_FILE_FORMAT = CApi.SQLITE_DBCONFIG_LEGACY_FILE_FORMAT;
+ public static final int DBCONFIG_TRUSTED_SCHEMA = CApi.SQLITE_DBCONFIG_TRUSTED_SCHEMA;
+ public static final int DBCONFIG_STMT_SCANSTATUS = CApi.SQLITE_DBCONFIG_STMT_SCANSTATUS;
+ public static final int DBCONFIG_REVERSE_SCANORDER = CApi.SQLITE_DBCONFIG_REVERSE_SCANORDER;
+
+ public static final int UTF8 = CApi.SQLITE_UTF8;
+ public static final int UTF16 = CApi.SQLITE_UTF16;
+ public static final int UTF16LE = CApi.SQLITE_UTF16LE;
+ public static final int UTF16BE = CApi.SQLITE_UTF16BE;
+ /* We elide the UTF16_ALIGNED from this interface because it
+ is irrelevant for the Java interface. */
+
+ public static final int DENY = CApi.SQLITE_DENY;
+ public static final int IGNORE = CApi.SQLITE_IGNORE;
+ public static final int CREATE_INDEX = CApi.SQLITE_CREATE_INDEX;
+ public static final int CREATE_TABLE = CApi.SQLITE_CREATE_TABLE;
+ public static final int CREATE_TEMP_INDEX = CApi.SQLITE_CREATE_TEMP_INDEX;
+ public static final int CREATE_TEMP_TABLE = CApi.SQLITE_CREATE_TEMP_TABLE;
+ public static final int CREATE_TEMP_TRIGGER = CApi.SQLITE_CREATE_TEMP_TRIGGER;
+ public static final int CREATE_TEMP_VIEW = CApi.SQLITE_CREATE_TEMP_VIEW;
+ public static final int CREATE_TRIGGER = CApi.SQLITE_CREATE_TRIGGER;
+ public static final int CREATE_VIEW = CApi.SQLITE_CREATE_VIEW;
+ public static final int DELETE = CApi.SQLITE_DELETE;
+ public static final int DROP_INDEX = CApi.SQLITE_DROP_INDEX;
+ public static final int DROP_TABLE = CApi.SQLITE_DROP_TABLE;
+ public static final int DROP_TEMP_INDEX = CApi.SQLITE_DROP_TEMP_INDEX;
+ public static final int DROP_TEMP_TABLE = CApi.SQLITE_DROP_TEMP_TABLE;
+ public static final int DROP_TEMP_TRIGGER = CApi.SQLITE_DROP_TEMP_TRIGGER;
+ public static final int DROP_TEMP_VIEW = CApi.SQLITE_DROP_TEMP_VIEW;
+ public static final int DROP_TRIGGER = CApi.SQLITE_DROP_TRIGGER;
+ public static final int DROP_VIEW = CApi.SQLITE_DROP_VIEW;
+ public static final int INSERT = CApi.SQLITE_INSERT;
+ public static final int PRAGMA = CApi.SQLITE_PRAGMA;
+ public static final int READ = CApi.SQLITE_READ;
+ public static final int SELECT = CApi.SQLITE_SELECT;
+ public static final int TRANSACTION = CApi.SQLITE_TRANSACTION;
+ public static final int UPDATE = CApi.SQLITE_UPDATE;
+ public static final int ATTACH = CApi.SQLITE_ATTACH;
+ public static final int DETACH = CApi.SQLITE_DETACH;
+ public static final int ALTER_TABLE = CApi.SQLITE_ALTER_TABLE;
+ public static final int REINDEX = CApi.SQLITE_REINDEX;
+ public static final int ANALYZE = CApi.SQLITE_ANALYZE;
+ public static final int CREATE_VTABLE = CApi.SQLITE_CREATE_VTABLE;
+ public static final int DROP_VTABLE = CApi.SQLITE_DROP_VTABLE;
+ public static final int FUNCTION = CApi.SQLITE_FUNCTION;
+ public static final int SAVEPOINT = CApi.SQLITE_SAVEPOINT;
+ public static final int RECURSIVE = CApi.SQLITE_RECURSIVE;
+
//! 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
@@ -76,7 +278,7 @@ public final class Sqlite implements AutoCloseable {
*/
public static Sqlite open(String filename, int flags, String vfsName){
final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
- final int rc = sqlite3_open_v2(filename, out, flags, vfsName);
+ final int rc = CApi.sqlite3_open_v2(filename, out, flags, vfsName);
final sqlite3 n = out.take();
if( 0!=rc ){
if( null==n ) throw new SqliteException(rc);
@@ -84,7 +286,12 @@ public final class Sqlite implements AutoCloseable {
n.close();
throw ex;
}
- return new Sqlite(n);
+ final Sqlite rv = new Sqlite(n);
+ synchronized(nativeToWrapper){
+ nativeToWrapper.put(n, rv);
+ }
+ runAutoExtensions(rv);
+ return rv;
}
public static Sqlite open(String filename, int flags){
@@ -92,7 +299,7 @@ public final class Sqlite implements AutoCloseable {
}
public static Sqlite open(String filename){
- return open(filename, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, null);
+ return open(filename, OPEN_READWRITE|OPEN_CREATE, null);
}
public static String libVersion(){
@@ -108,33 +315,68 @@ public final class Sqlite implements AutoCloseable {
}
/**
- As per sqlite3_status64(), but returns its current and high-water
- results as a two-element array. Throws if the first argument is
- not one of the STATUS_... constants.
+ Returns the value of the native library's build-time value of the
+ SQLITE_THREADSAFE build option.
*/
- public long[] libStatus(int op, boolean resetStats){
- org.sqlite.jni.capi.OutputPointer.Int64 pCurrent =
- new org.sqlite.jni.capi.OutputPointer.Int64();
- org.sqlite.jni.capi.OutputPointer.Int64 pHighwater =
- new org.sqlite.jni.capi.OutputPointer.Int64();
- final int rc = CApi.sqlite3_status64(op, pCurrent, pHighwater, resetStats);
- checkRc(rc);
- return new long[] {pCurrent.value, pHighwater.value};
+ public static int libThreadsafe(){
+ return CApi.sqlite3_threadsafe();
}
- @Override public void close(){
- if(null!=this.db){
- this.db.close();
- this.db = null;
+ /**
+ Analog to sqlite3_compileoption_get().
+ */
+ public static String compileOptionGet(int n){
+ return CApi.sqlite3_compileoption_get(n);
+ }
+
+ /**
+ Analog to sqlite3_compileoption_used().
+ */
+ public static boolean compileOptionUsed(String optName){
+ return CApi.sqlite3_compileoption_used(optName);
+ }
+
+ private static boolean hasNormalizeSql =
+ compileOptionUsed("ENABLE_NORMALIZE");
+
+ /**
+ Throws UnsupportedOperationException if check is false.
+ flag is expected to be the name of an SQLITE_ENABLE_...
+ build flag.
+ */
+ private static void checkSupported(boolean check, String flag){
+ if( !check ){
+ throw new UnsupportedOperationException(
+ "Library was built without "+flag
+ );
}
}
/**
- Returns the value of the native library's build-time value of the
- SQLITE_THREADSAFE build option.
+ Analog to sqlite3_complete().
*/
- public static int libThreadsafe(){
- return CApi.sqlite3_threadsafe();
+ public static boolean isCompleteStatement(String sql){
+ switch(CApi.sqlite3_complete(sql)){
+ case 0: return false;
+ case CApi.SQLITE_MISUSE:
+ throw new IllegalArgumentException("Input may not be null.");
+ case CApi.SQLITE_NOMEM:
+ throw new OutOfMemoryError();
+ default:
+ return true;
+ }
+ }
+
+ public static int keywordCount(){
+ return CApi.sqlite3_keyword_count();
+ }
+
+ public static boolean keywordCheck(String word){
+ return CApi.sqlite3_keyword_check(word);
+ }
+
+ public static String keywordName(int index){
+ return CApi.sqlite3_keyword_name(index);
}
public static boolean strglob(String glob, String txt){
@@ -146,6 +388,61 @@ public final class Sqlite implements AutoCloseable {
}
/**
+ Output object for use with status() and libStatus().
+ */
+ public static final class Status {
+ /** The current value for the requested status() or libStatus() metric. */
+ long current;
+ /** The peak value for the requested status() or libStatus() metric. */
+ long peak;
+ };
+
+ /**
+ As per sqlite3_status64(), but returns its current and high-water
+ results as a Status object. Throws if the first argument is
+ not one of the STATUS_... constants.
+ */
+ public static Status libStatus(int op, boolean resetStats){
+ org.sqlite.jni.capi.OutputPointer.Int64 pCurrent =
+ new org.sqlite.jni.capi.OutputPointer.Int64();
+ org.sqlite.jni.capi.OutputPointer.Int64 pHighwater =
+ new org.sqlite.jni.capi.OutputPointer.Int64();
+ checkRc2( CApi.sqlite3_status64(op, pCurrent, pHighwater, resetStats) );
+ final Status s = new Status();
+ s.current = pCurrent.value;
+ s.peak = pHighwater.value;
+ return s;
+ }
+
+ /**
+ As per sqlite3_db_status(), but returns its current and
+ high-water results as a Status object. Throws if the first
+ argument is not one of the DBSTATUS_... constants or on any other
+ misuse.
+ */
+ public Status status(int op, boolean resetStats){
+ org.sqlite.jni.capi.OutputPointer.Int32 pCurrent =
+ new org.sqlite.jni.capi.OutputPointer.Int32();
+ org.sqlite.jni.capi.OutputPointer.Int32 pHighwater =
+ new org.sqlite.jni.capi.OutputPointer.Int32();
+ checkRc( CApi.sqlite3_db_status(thisDb(), op, pCurrent, pHighwater, resetStats) );
+ final Status s = new Status();
+ s.current = pCurrent.value;
+ s.peak = pHighwater.value;
+ return s;
+ }
+
+ @Override public void close(){
+ if(null!=this.db){
+ synchronized(nativeToWrapper){
+ nativeToWrapper.remove(this.db);
+ }
+ this.db.close();
+ this.db = null;
+ }
+ }
+
+ /**
Returns this object's underlying native db handle, or null if
this instance has been closed. This is very specifically not
public.
@@ -169,28 +466,71 @@ public final class Sqlite implements AutoCloseable {
extracted from it, else only the string form of rc is used. It is
the caller's responsibility to filter out non-error codes such as
SQLITE_ROW and SQLITE_DONE before calling this.
+
+ As a special case, if rc is SQLITE_NOMEM, an OutOfMemoryError is
+ thrown.
*/
private void checkRc(int rc){
if( 0!=rc ){
- if( null==db || 0==sqlite3_errcode(db)) throw new SqliteException(rc);
- else throw new SqliteException(db);
+ if( CApi.SQLITE_NOMEM==rc ){
+ throw new OutOfMemoryError();
+ }else if( null==db || 0==CApi.sqlite3_errcode(db) ){
+ throw new SqliteException(rc);
+ }else{
+ throw new SqliteException(db);
+ }
+ }
+ }
+
+ /**
+ Like checkRc() but behaves as if that function were
+ called with a null db object.
+ */
+ private static void checkRc2(int rc){
+ if( 0!=rc ){
+ if( CApi.SQLITE_NOMEM==rc ){
+ throw new OutOfMemoryError();
+ }else{
+ throw new SqliteException(rc);
+ }
}
}
/**
- prepFlags must be 0 or a bitmask of the PREPARE_... constants.
+ Toggles the use of extended result codes on or off. By default
+ they are turned off, but they can be enabled by default by
+ including the OPEN_EXRESCODE flag when opening a database.
+
+ Because this API reports db-side errors using exceptions,
+ enabling this may change the values returned by
+ SqliteException.errcode().
+ */
+ public void useExtendedResultCodes(boolean on){
+ checkRc( CApi.sqlite3_extended_result_codes(thisDb(), on) );
+ }
- prepare() TODOs include:
+ /**
+ 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).
- - overloads taking byte[] and ByteBuffer.
+ The first argument must be SQL input in UTF-8 encoding.
- - 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.
+ prepFlags must be 0 or a bitmask of the PREPARE_... constants.
+
+ For processing multiple statements from a single input, use
+ prepareMulti().
+
+ 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 = 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 ){
@@ -207,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));
@@ -269,10 +712,6 @@ public final class Sqlite implements AutoCloseable {
return CApi.sqlite3_get_autocommit(thisDb());
}
- public void setBusyTimeout(int ms){
- checkRc(CApi.sqlite3_busy_timeout(thisDb(), ms));
- }
-
/**
Analog to sqlite3_txn_state(). Returns one of TXN_NONE, TXN_READ,
or TXN_WRITE to denote this database's current transaction state
@@ -300,6 +739,19 @@ public final class Sqlite implements AutoCloseable {
}
/**
+ Analog to sqlite3_db_config() for the call forms which take one
+ of the boolean-type db configuration flags (namely the
+ DBCONFIG_... constants defined in this class). On success it
+ returns the result of that underlying call. Throws on error.
+ */
+ public boolean dbConfig(int op, boolean on){
+ org.sqlite.jni.capi.OutputPointer.Int32 pOut =
+ new org.sqlite.jni.capi.OutputPointer.Int32();
+ checkRc( CApi.sqlite3_db_config(thisDb(), op, on ? 1 : 0, pOut) );
+ return pOut.get()!=0;
+ }
+
+ /**
Analog to the variant of sqlite3_db_config() for configuring the
SQLITE_DBCONFIG_MAINDBNAME option. Throws on error.
*/
@@ -332,7 +784,7 @@ public final class Sqlite implements AutoCloseable {
/**
Analog to sqlite3_release_memory().
*/
- public static int releaseMemory(int n){
+ public static int libReleaseMemory(int n){
return CApi.sqlite3_release_memory(n);
}
@@ -418,31 +870,111 @@ 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;
/**
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.
+
+ Design note: if this is final then we cannot zero it in
+ finalizeStmt().
*/
- private final int resultColCount;
+ private 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);
+ 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.
@@ -468,13 +1000,20 @@ public final class Sqlite implements AutoCloseable {
name finalize() here because this one requires a different
signature. It does not throw on error here because "destructors
do not throw." If it returns non-0, the object is still
- finalized.
+ finalized, but the result code is an indication that something
+ went wrong in a prior call into the statement's API, as
+ documented for sqlite3_finalize().
*/
public int finalizeStmt(){
int rc = 0;
if( null!=stmt ){
- sqlite3_finalize(stmt);
+ synchronized(nativeToWrapper){
+ nativeToWrapper.remove(this.stmt);
+ }
+ CApi.sqlite3_finalize(stmt);
stmt = null;
+ _db = null;
+ resultColCount = 0;
}
return rc;
}
@@ -492,8 +1031,8 @@ public final class Sqlite implements AutoCloseable {
private int checkRc(int rc){
switch(rc){
case 0:
- case SQLITE_ROW:
- case SQLITE_DONE: return rc;
+ case CApi.SQLITE_ROW:
+ case CApi.SQLITE_DONE: return rc;
default:
if( null==stmt ) throw new SqliteException(rc);
else throw new SqliteException(this);
@@ -506,7 +1045,7 @@ public final class Sqlite implements AutoCloseable {
result.
*/
public boolean step(){
- switch(checkRc(sqlite3_step(thisStmt()))){
+ switch(checkRc(CApi.sqlite3_step(thisStmt()))){
case CApi.SQLITE_ROW: return true;
case CApi.SQLITE_DONE: return false;
default:
@@ -514,17 +1053,13 @@ public final class Sqlite implements AutoCloseable {
"This \"cannot happen\": all possible result codes were checked already."
);
}
- /*
- 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; }
+ /**
+ Returns the Sqlite which prepared this statement, or null if
+ this statement has been finalized.
+ */
+ public Sqlite getDb(){ return this._db; }
/**
Works like sqlite3_reset() but throws on error.
@@ -533,6 +1068,46 @@ public final class Sqlite implements AutoCloseable {
checkRc(CApi.sqlite3_reset(thisStmt()));
}
+ public boolean isBusy(){
+ return CApi.sqlite3_stmt_busy(thisStmt());
+ }
+
+ public boolean isReadOnly(){
+ return CApi.sqlite3_stmt_readonly(thisStmt());
+ }
+
+ public String sql(){
+ return CApi.sqlite3_sql(thisStmt());
+ }
+
+ public String expandedSql(){
+ return CApi.sqlite3_expanded_sql(thisStmt());
+ }
+
+ /**
+ Analog to sqlite3_stmt_explain() but throws if op is invalid.
+ */
+ public void explain(int op){
+ checkRc(CApi.sqlite3_stmt_explain(thisStmt(), op));
+ }
+
+ /**
+ Analog to sqlite3_stmt_isexplain().
+ */
+ public int isExplain(){
+ return CApi.sqlite3_stmt_isexplain(thisStmt());
+ }
+
+ /**
+ Analog to sqlite3_normalized_sql(), but throws
+ UnsupportedOperationException if the library was built without
+ the SQLITE_ENABLE_NORMALIZE flag.
+ */
+ public String normalizedSql(){
+ Sqlite.checkSupported(hasNormalizeSql, "SQLITE_ENABLE_NORMALIZE");
+ return CApi.sqlite3_normalized_sql(thisStmt());
+ }
+
public void clearBindings(){
CApi.sqlite3_clear_bindings( thisStmt() );
}
@@ -569,8 +1144,8 @@ public final class Sqlite implements AutoCloseable {
public void bindText16(int ndx, byte[] utf16){
checkRc(CApi.sqlite3_bind_text16(thisStmt(), ndx, utf16));
}
- public void bindText16(int ndx, String txt){
- checkRc(CApi.sqlite3_bind_text16(thisStmt(), ndx, txt));
+ public void bindText16(int ndx, String asUtf16){
+ checkRc(CApi.sqlite3_bind_text16(thisStmt(), ndx, asUtf16));
}
public void bindZeroBlob(int ndx, int n){
checkRc(CApi.sqlite3_bind_zeroblob(thisStmt(), ndx, n));
@@ -635,4 +1210,608 @@ public final class Sqlite implements AutoCloseable {
}
} /* Stmt class */
+ /**
+ Interface for auto-extensions, as per the
+ sqlite3_auto_extension() API.
+
+ Design note: the chicken/egg timing of auto-extension execution
+ requires that this feature be entirely re-implemented in Java
+ because the C-level API has no access to the Sqlite type so
+ cannot pass on an object of that type while the database is being
+ opened. One side effect of this reimplementation is that this
+ class's list of auto-extensions is 100% independent of the
+ C-level list so, e.g., clearAutoExtensions() will have no effect
+ on auto-extensions added via the C-level API and databases opened
+ from that level of API will not be passed to this level's
+ AutoExtension instances.
+ */
+ public interface AutoExtension {
+ public void call(Sqlite db);
+ }
+
+ private static final java.util.Set<AutoExtension> autoExtensions =
+ new java.util.LinkedHashSet<>();
+
+ /**
+ Passes db to all auto-extensions. If any one of them throws,
+ db.close() is called before the exception is propagated.
+ */
+ private static void runAutoExtensions(Sqlite db){
+ AutoExtension list[];
+ synchronized(autoExtensions){
+ /* Avoid that modifications to the AutoExtension list from within
+ auto-extensions affect this execution of this list. */
+ list = autoExtensions.toArray(new AutoExtension[0]);
+ }
+ try {
+ for( AutoExtension ax : list ) ax.call(db);
+ }catch(Exception e){
+ db.close();
+ throw e;
+ }
+ }
+
+ /**
+ Analog to sqlite3_auto_extension(), adds the given object to the
+ list of auto-extensions if it is not already in that list. The
+ given object will be run as part of Sqlite.open(), and passed the
+ being-opened database. If the extension throws then open() will
+ fail.
+
+ This API does not guaranty whether or not manipulations made to
+ the auto-extension list from within auto-extension callbacks will
+ affect the current traversal of the auto-extension list. Whether
+ or not they do is unspecified and subject to change between
+ versions. e.g. if an AutoExtension calls addAutoExtension(),
+ whether or not the new extension will be run on the being-opened
+ database is undefined.
+
+ Note that calling Sqlite.open() from an auto-extension will
+ necessarily result in recursion loop and (eventually) a stack
+ overflow.
+ */
+ public static void addAutoExtension( AutoExtension e ){
+ if( null==e ){
+ throw new IllegalArgumentException("AutoExtension may not be null.");
+ }
+ synchronized(autoExtensions){
+ autoExtensions.add(e);
+ }
+ }
+
+ /**
+ Removes the given object from the auto-extension list if it is in
+ that list, otherwise this has no side-effects beyond briefly
+ locking that list.
+ */
+ public static void removeAutoExtension( AutoExtension e ){
+ synchronized(autoExtensions){
+ autoExtensions.remove(e);
+ }
+ }
+
+ /**
+ Removes all auto-extensions which were added via addAutoExtension().
+ */
+ public static void clearAutoExtensions(){
+ synchronized(autoExtensions){
+ autoExtensions.clear();
+ }
+ }
+
+ /**
+ Encapsulates state related to the sqlite3 backup API. Use
+ Sqlite.initBackup() to create new instances.
+ */
+ public static final class Backup implements AutoCloseable {
+ private sqlite3_backup b = null;
+ private Sqlite dbTo = null;
+ private Sqlite dbFrom = null;
+
+ Backup(Sqlite dbDest, String schemaDest,Sqlite dbSrc, String schemaSrc){
+ this.dbTo = dbDest;
+ this.dbFrom = dbSrc;
+ b = CApi.sqlite3_backup_init(dbDest.nativeHandle(), schemaDest,
+ dbSrc.nativeHandle(), schemaSrc);
+ if(null==b) toss();
+ }
+
+ private void toss(){
+ int rc = CApi.sqlite3_errcode(dbTo.nativeHandle());
+ if(0!=rc) throw new SqliteException(dbTo);
+ rc = CApi.sqlite3_errcode(dbFrom.nativeHandle());
+ if(0!=rc) throw new SqliteException(dbFrom);
+ throw new SqliteException(CApi.SQLITE_ERROR);
+ }
+
+ private sqlite3_backup getNative(){
+ if( null==b ) throw new IllegalStateException("This Backup is already closed.");
+ return b;
+ }
+ /**
+ If this backup is still active, this completes the backup and
+ frees its native resources, otherwise it this is a no-op.
+ */
+ public void finish(){
+ if( null!=b ){
+ CApi.sqlite3_backup_finish(b);
+ b = null;
+ dbTo = null;
+ dbFrom = null;
+ }
+ }
+
+ /** Equivalent to finish(). */
+ @Override public void close(){
+ this.finish();
+ }
+
+ /**
+ Analog to sqlite3_backup_step(). Returns 0 if stepping succeeds
+ or, Sqlite.DONE if the end is reached, Sqlite.BUSY if one of
+ the databases is busy, Sqlite.LOCKED if one of the databases is
+ locked, and throws for any other result code or if this object
+ has been closed. Note that BUSY and LOCKED are not necessarily
+ permanent errors, so do not trigger an exception.
+ */
+ public int step(int pageCount){
+ final int rc = CApi.sqlite3_backup_step(getNative(), pageCount);
+ switch(rc){
+ case 0:
+ case Sqlite.DONE:
+ case Sqlite.BUSY:
+ case Sqlite.LOCKED:
+ return rc;
+ default:
+ toss();
+ return CApi.SQLITE_ERROR/*not reached*/;
+ }
+ }
+
+ /**
+ Analog to sqlite3_backup_pagecount().
+ */
+ public int pageCount(){
+ return CApi.sqlite3_backup_pagecount(getNative());
+ }
+
+ /**
+ Analog to sqlite3_backup_remaining().
+ */
+ public int remaining(){
+ return CApi.sqlite3_backup_remaining(getNative());
+ }
+ }
+
+ /**
+ Analog to sqlite3_backup_init(). If schemaSrc is null, "main" is
+ assumed. Throws if either this db or dbSrc (the source db) are
+ not opened, if either of schemaDest or schemaSrc are null, or if
+ the underlying call to sqlite3_backup_init() fails.
+
+ The returned object must eventually be cleaned up by either
+ arranging for it to be auto-closed (e.g. using
+ try-with-resources) or by calling its finish() method.
+ */
+ public Backup initBackup(String schemaDest, Sqlite dbSrc, String schemaSrc){
+ thisDb();
+ dbSrc.thisDb();
+ if( null==schemaSrc || null==schemaDest ){
+ throw new IllegalArgumentException(
+ "Neither the source nor destination schema name may be null."
+ );
+ }
+ return new Backup(this, schemaDest, dbSrc, schemaSrc);
+ }
+
+
+ /**
+ Callback type for use with createCollation().
+ */
+ public interface Collation {
+ /**
+ Called by the SQLite core to compare inputs. Implementations
+ must compare its two arguments using memcmp(3) semantics.
+
+ Warning: the SQLite core has no mechanism for reporting errors
+ from custom collations and its workflow does not accommodate
+ propagation of exceptions from callbacks. Any exceptions thrown
+ from collations will be silently supressed and sorting results
+ will be unpredictable.
+ */
+ int call(byte[] lhs, byte[] rhs);
+ }
+
+ /**
+ Analog to sqlite3_create_collation().
+
+ Throws if name is null or empty, c is null, or the encoding flag
+ is invalid. The encoding must be one of the UTF8, UTF16, UTF16LE,
+ or UTF16BE constants.
+ */
+ public void createCollation(String name, int encoding, Collation c){
+ thisDb();
+ if( null==name || 0==name.length()){
+ throw new IllegalArgumentException("Collation name may not be null or empty.");
+ }
+ if( null==c ){
+ throw new IllegalArgumentException("Collation may not be null.");
+ }
+ switch(encoding){
+ case UTF8:
+ case UTF16:
+ case UTF16LE:
+ case UTF16BE:
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid Collation encoding.");
+ }
+ checkRc(
+ CApi.sqlite3_create_collation(
+ thisDb(), name, encoding, new org.sqlite.jni.capi.CollationCallback(){
+ @Override public int call(byte[] lhs, byte[] rhs){
+ try{return c.call(lhs, rhs);}
+ catch(Exception e){return 0;}
+ }
+ @Override public void xDestroy(){}
+ }
+ )
+ );
+ }
+
+ /**
+ Callback for use with onCollationNeeded().
+ */
+ public interface CollationNeeded {
+ /**
+ Must behave as documented for the callback for
+ sqlite3_collation_needed().
+
+ Warning: the C API has no mechanism for reporting or
+ propagating errors from this callback, so any exceptions it
+ throws are suppressed.
+ */
+ void call(Sqlite db, int encoding, String collationName);
+ }
+
+ /**
+ Sets up the given object to be called by the SQLite core when it
+ encounters a collation name which it does not know. Pass a null
+ object to disconnect the object from the core. This replaces any
+ existing collation-needed loader, or is a no-op if the given
+ object is already registered. Throws if registering the loader
+ fails.
+ */
+ public void onCollationNeeded( CollationNeeded cn ){
+ org.sqlite.jni.capi.CollationNeededCallback cnc = null;
+ if( null!=cn ){
+ cnc = new org.sqlite.jni.capi.CollationNeededCallback(){
+ @Override public void call(sqlite3 db, int encoding, String collationName){
+ final Sqlite xdb = Sqlite.fromNative(db);
+ if(null!=xdb) cn.call(xdb, encoding, collationName);
+ }
+ };
+ }
+ checkRc( CApi.sqlite3_collation_needed(thisDb(), cnc) );
+ }
+
+ /**
+ Callback for use with busyHandler().
+ */
+ public interface BusyHandler {
+ /**
+ Must function as documented for the C-level
+ sqlite3_busy_handler() callback argument, minus the (void*)
+ argument the C-level function requires.
+
+ If this function throws, it is translated to a database-level
+ error.
+ */
+ int call(int n);
+ }
+
+ /**
+ Analog to sqlite3_busy_timeout().
+ */
+ public void setBusyTimeout(int ms){
+ checkRc(CApi.sqlite3_busy_timeout(thisDb(), ms));
+ }
+
+ /**
+ Analog to sqlite3_busy_handler(). If b is null then any
+ current handler is cleared.
+ */
+ public void setBusyHandler( BusyHandler b ){
+ org.sqlite.jni.capi.BusyHandlerCallback bhc = null;
+ if( null!=b ){
+ bhc = new org.sqlite.jni.capi.BusyHandlerCallback(){
+ @Override public int call(int n){
+ return b.call(n);
+ }
+ };
+ }
+ checkRc( CApi.sqlite3_busy_handler(thisDb(), bhc) );
+ }
+
+ public interface CommitHook {
+ /**
+ Must behave as documented for the C-level sqlite3_commit_hook()
+ callback. If it throws, the exception is translated into
+ a db-level error.
+ */
+ int call();
+ }
+
+ /**
+ A level of indirection to permit setCommitHook() to have similar
+ semantics as the C API, returning the previous hook. The caveat
+ is that if the low-level API is used to install a hook, it will
+ have a different hook type than Sqlite.CommitHook so
+ setCommitHook() will return null instead of that object.
+ */
+ private static class CommitHookProxy
+ implements org.sqlite.jni.capi.CommitHookCallback {
+ final CommitHook commitHook;
+ CommitHookProxy(CommitHook ch){
+ this.commitHook = ch;
+ }
+ @Override public int call(){
+ return commitHook.call();
+ }
+ }
+
+ /**
+ Analog to sqlite3_commit_hook(). Returns the previous hook, if
+ any (else null). Throws if this db is closed.
+
+ Minor caveat: if a commit hook is set on this object's underlying
+ db handle using the lower-level SQLite API, this function may
+ return null when replacing it, despite there being a hook,
+ because it will have a different callback type. So long as the
+ handle is only manipulated via the high-level API, this caveat
+ does not apply.
+ */
+ public CommitHook setCommitHook( CommitHook c ){
+ CommitHookProxy chp = null;
+ if( null!=c ){
+ chp = new CommitHookProxy(c);
+ }
+ final org.sqlite.jni.capi.CommitHookCallback rv =
+ CApi.sqlite3_commit_hook(thisDb(), chp);
+ return (rv instanceof CommitHookProxy)
+ ? ((CommitHookProxy)rv).commitHook
+ : null;
+ }
+
+
+ public interface RollbackHook {
+ /**
+ Must behave as documented for the C-level sqlite3_rollback_hook()
+ callback. If it throws, the exception is translated into
+ a db-level error.
+ */
+ void call();
+ }
+
+ /**
+ A level of indirection to permit setRollbackHook() to have similar
+ semantics as the C API, returning the previous hook. The caveat
+ is that if the low-level API is used to install a hook, it will
+ have a different hook type than Sqlite.RollbackHook so
+ setRollbackHook() will return null instead of that object.
+ */
+ private static class RollbackHookProxy
+ implements org.sqlite.jni.capi.RollbackHookCallback {
+ final RollbackHook rollbackHook;
+ RollbackHookProxy(RollbackHook ch){
+ this.rollbackHook = ch;
+ }
+ @Override public void call(){rollbackHook.call();}
+ }
+
+ /**
+ Analog to sqlite3_rollback_hook(). Returns the previous hook, if
+ any (else null). Throws if this db is closed.
+
+ Minor caveat: if a rollback hook is set on this object's underlying
+ db handle using the lower-level SQLite API, this function may
+ return null when replacing it, despite there being a hook,
+ because it will have a different callback type. So long as the
+ handle is only manipulated via the high-level API, this caveat
+ does not apply.
+ */
+ public RollbackHook setRollbackHook( RollbackHook c ){
+ RollbackHookProxy chp = null;
+ if( null!=c ){
+ chp = new RollbackHookProxy(c);
+ }
+ final org.sqlite.jni.capi.RollbackHookCallback rv =
+ CApi.sqlite3_rollback_hook(thisDb(), chp);
+ return (rv instanceof RollbackHookProxy)
+ ? ((RollbackHookProxy)rv).rollbackHook
+ : null;
+ }
+
+ public interface UpdateHook {
+ /**
+ Must function as described for the C-level sqlite3_update_hook()
+ callback.
+ */
+ void call(int opId, String dbName, String tableName, long rowId);
+ }
+
+ /**
+ A level of indirection to permit setUpdateHook() to have similar
+ semantics as the C API, returning the previous hook. The caveat
+ is that if the low-level API is used to install a hook, it will
+ have a different hook type than Sqlite.UpdateHook so
+ setUpdateHook() will return null instead of that object.
+ */
+ private static class UpdateHookProxy
+ implements org.sqlite.jni.capi.UpdateHookCallback {
+ final UpdateHook updateHook;
+ UpdateHookProxy(UpdateHook ch){
+ this.updateHook = ch;
+ }
+ @Override public void call(int opId, String dbName, String tableName, long rowId){
+ updateHook.call(opId, dbName, tableName, rowId);
+ }
+ }
+
+ /**
+ Analog to sqlite3_update_hook(). Returns the previous hook, if
+ any (else null). Throws if this db is closed.
+
+ Minor caveat: if a update hook is set on this object's underlying
+ db handle using the lower-level SQLite API, this function may
+ return null when replacing it, despite there being a hook,
+ because it will have a different callback type. So long as the
+ handle is only manipulated via the high-level API, this caveat
+ does not apply.
+ */
+ public UpdateHook setUpdateHook( UpdateHook c ){
+ UpdateHookProxy chp = null;
+ if( null!=c ){
+ chp = new UpdateHookProxy(c);
+ }
+ final org.sqlite.jni.capi.UpdateHookCallback rv =
+ CApi.sqlite3_update_hook(thisDb(), chp);
+ return (rv instanceof UpdateHookProxy)
+ ? ((UpdateHookProxy)rv).updateHook
+ : null;
+ }
+
+
+ /**
+ Callback interface for use with setProgressHandler().
+ */
+ public interface ProgressHandler {
+ /**
+ Must behave as documented for the C-level sqlite3_progress_handler()
+ callback. If it throws, the exception is translated into
+ a db-level error.
+ */
+ int call();
+ }
+
+ /**
+ Analog to sqlite3_progress_handler(), sets the current progress
+ handler or clears it if p is null.
+
+ Note that this API, in contrast to setUpdateHook(),
+ setRollbackHook(), and setCommitHook(), cannot return the
+ previous handler. That inconsistency is part of the lower-level C
+ API.
+ */
+ public void setProgressHandler( int n, ProgressHandler p ){
+ org.sqlite.jni.capi.ProgressHandlerCallback phc = null;
+ if( null!=p ){
+ phc = new org.sqlite.jni.capi.ProgressHandlerCallback(){
+ @Override public int call(){ return p.call(); }
+ };
+ }
+ CApi.sqlite3_progress_handler( thisDb(), n, phc );
+ }
+
+
+ /**
+ Callback for use with setAuthorizer().
+ */
+ public interface Authorizer {
+ /**
+ Must function as described for the C-level
+ sqlite3_set_authorizer() callback. If it throws, the error is
+ converted to a db-level error and the exception is suppressed.
+ */
+ int call(int opId, String s1, String s2, String s3, String s4);
+ }
+
+ /**
+ Analog to sqlite3_set_authorizer(), this sets the current
+ authorizer callback, or clears if it passed null.
+ */
+ public void setAuthorizer( Authorizer a ) {
+ org.sqlite.jni.capi.AuthorizerCallback ac = null;
+ if( null!=a ){
+ ac = new org.sqlite.jni.capi.AuthorizerCallback(){
+ @Override public int call(int opId, String s1, String s2, String s3, String s4){
+ return a.call(opId, s1, s2, s3, s4);
+ }
+ };
+ }
+ checkRc( CApi.sqlite3_set_authorizer( thisDb(), ac ) );
+ }
+
+ /**
+ Object type for use with blobOpen()
+ */
+ public final class Blob implements AutoCloseable {
+ private Sqlite db;
+ private sqlite3_blob b;
+ Blob(Sqlite db, sqlite3_blob b){
+ this.db = db;
+ this.b = b;
+ }
+
+ /**
+ Analog to sqlite3_blob_close().
+ */
+ @Override public void close(){
+ if( null!=b ){
+ CApi.sqlite3_blob_close(b);
+ b = null;
+ db = null;
+ }
+ }
+
+ /**
+ Analog to sqlite3_blob_reopen() but throws on error.
+ */
+ public void reopen(long newRowId){
+ db.checkRc( CApi.sqlite3_blob_reopen(b, newRowId) );
+ }
+
+ /**
+ Analog to sqlite3_blob_write() but throws on error.
+ */
+ public void write( byte[] bytes, int atOffset ){
+ db.checkRc( CApi.sqlite3_blob_write(b, bytes, atOffset) );
+ }
+
+ /**
+ Analog to sqlite3_blob_read() but throws on error.
+ */
+ public void read( byte[] dest, int atOffset ){
+ db.checkRc( CApi.sqlite3_blob_read(b, dest, atOffset) );
+ }
+
+ /**
+ Analog to sqlite3_blob_bytes().
+ */
+ public int bytes(){
+ return CApi.sqlite3_blob_bytes(b);
+ }
+ }
+
+ /**
+ Analog to sqlite3_blob_open(). Returns a Blob object for the
+ given database, table, column, and rowid. The blob is opened for
+ read-write mode if writeable is true, else it is read-only.
+
+ The returned object must eventually be freed, before this
+ database is closed, by either arranging for it to be auto-closed
+ or calling its close() method.
+
+ Throws on error.
+ */
+ public Blob blobOpen(String dbName, String tableName, String columnName,
+ long iRow, boolean writeable){
+ final OutputPointer.sqlite3_blob out = new OutputPointer.sqlite3_blob();
+ checkRc(
+ CApi.sqlite3_blob_open(thisDb(), dbName, tableName, columnName,
+ iRow, writeable ? 1 : 0, out)
+ );
+ return new Blob(this, out.take());
+ }
+
}
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java b/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java
index 27cfc0e6b..9b4440f19 100644
--- a/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java
@@ -50,7 +50,7 @@ public final class SqliteException extends java.lang.RuntimeException {
/**
Records the current error state of db (which must not be null and
- must refer to an opened db object). Note that this does NOT close
+ must refer to an opened db object). Note that this does not close
the db.
Design note: closing the db on error is really only useful during
@@ -74,7 +74,7 @@ public final class SqliteException extends java.lang.RuntimeException {
}
public SqliteException(Sqlite.Stmt stmt){
- this(stmt.db());
+ this(stmt.getDb());
}
public int errcode(){ return errCode; }
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java b/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java
index ca4e6d052..c276e383b 100644
--- a/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java
@@ -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.
@@ -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));
}
@@ -133,6 +133,9 @@ public class Tester2 implements Runnable {
Executes all SQL statements in the given string. If throwOnError
is true then it will throw for any prepare/step errors, else it
will return the corresponding non-0 result code.
+
+ TODO: reimplement this in the high-level API once it has the
+ multi-prepare capability.
*/
public static int execSql(Sqlite dbw, boolean throwOnError, String sql){
final sqlite3 db = dbw.nativeHandle();
@@ -163,7 +166,7 @@ public class Tester2 implements Runnable {
}
CApi.sqlite3_finalize(stmt);
affirm(0 == stmt.getNativePointer());
- if(CApi.SQLITE_DONE!=rc){
+ if(Sqlite.DONE!=rc){
break;
}
}
@@ -181,7 +184,7 @@ public class Tester2 implements Runnable {
@SingleThreadOnly /* because it's thread-agnostic */
private void test1(){
- affirm(CApi.sqlite3_libversion_number() == CApi.SQLITE_VERSION_NUMBER);
+ affirm(Sqlite.libVersionNumber() == CApi.SQLITE_VERSION_NUMBER);
}
/* Copy/paste/rename this to add new tests. */
@@ -211,15 +214,24 @@ public class Tester2 implements Runnable {
void testOpenDb1(){
Sqlite db = openDb();
affirm( 0!=db.nativeHandle().getNativePointer() );
+ affirm( "main".equals( db.dbName(0) ) );
+ db.setMainDbName("foo");
+ affirm( "foo".equals( db.dbName(0) ) );
+ affirm( db.dbConfig(Sqlite.DBCONFIG_DEFENSIVE, true)
+ /* The underlying function has different mangled names in jdk8
+ vs jdk19, and this call is here to ensure that the build
+ fails if it cannot find both names. */ );
+ affirm( !db.dbConfig(Sqlite.DBCONFIG_DEFENSIVE, false) );
+ SqliteException ex = null;
+ try{ db.dbConfig(0, false); }
+ catch(SqliteException e){ ex = e; }
+ affirm( null!=ex );
+ ex = null;
db.close();
affirm( null==db.nativeHandle() );
- SqliteException ex = null;
- try {
- db = openDb("/no/such/dir/.../probably");
- }catch(SqliteException e){
- ex = e;
- }
+ try{ db = openDb("/no/such/dir/.../probably"); }
+ catch(SqliteException e){ ex = e; }
affirm( ex!=null );
affirm( ex.errcode() != 0 );
affirm( ex.extendedErrcode() != 0 );
@@ -232,6 +244,7 @@ public class Tester2 implements Runnable {
Sqlite.Stmt stmt = db.prepare("SELECT ?1");
Exception e = null;
affirm( null!=stmt.nativeHandle() );
+ affirm( db == stmt.getDb() );
affirm( 1==stmt.bindParameterCount() );
affirm( "?1".equals(stmt.bindParameterName(1)) );
affirm( null==stmt.bindParameterName(2) );
@@ -285,21 +298,30 @@ public class Tester2 implements Runnable {
final ValueHolder<Integer> vh = new ValueHolder<>(0);
final ScalarFunction f = new ScalarFunction(){
public void xFunc(SqlFunction.Arguments args){
+ affirm( db == args.getDb() );
for( SqlFunction.Arguments.Arg arg : args ){
vh.value += arg.getInt();
}
+ args.resultInt(vh.value);
}
public void xDestroy(){
++xDestroyCalled.value;
}
};
db.createFunction("myfunc", -1, f);
- execSql(db, "select myfunc(1,2,3)");
+ Sqlite.Stmt q = db.prepare("select myfunc(1,2,3)");
+ affirm( q.step() );
affirm( 6 == vh.value );
+ affirm( 6 == q.columnInt(0) );
+ q.finalizeStmt();
+ affirm( 0 == xDestroyCalled.value );
vh.value = 0;
- execSql(db, "select myfunc(-1,-2,-3)");
+ q = db.prepare("select myfunc(-1,-2,-3)");
+ affirm( q.step() );
affirm( -6 == vh.value );
+ affirm( -6 == q.columnInt(0) );
affirm( 0 == xDestroyCalled.value );
+ q.finalizeStmt();
}
affirm( 1 == xDestroyCalled.value );
}
@@ -412,6 +434,540 @@ public class Tester2 implements Runnable {
db.close();
}
+ private void testKeyword(){
+ final int n = Sqlite.keywordCount();
+ affirm( n>0 );
+ affirm( !Sqlite.keywordCheck("_nope_") );
+ affirm( Sqlite.keywordCheck("seLect") );
+ affirm( null!=Sqlite.keywordName(0) );
+ affirm( null!=Sqlite.keywordName(n-1) );
+ affirm( null==Sqlite.keywordName(n) );
+ }
+
+
+ private void testExplain(){
+ final Sqlite db = openDb();
+ Sqlite.Stmt q = db.prepare("SELECT 1");
+ affirm( 0 == q.isExplain() );
+ q.explain(0);
+ affirm( 0 == q.isExplain() );
+ q.explain(1);
+ affirm( 1 == q.isExplain() );
+ q.explain(2);
+ affirm( 2 == q.isExplain() );
+ Exception ex = null;
+ try{
+ q.explain(-1);
+ }catch(Exception e){
+ ex = e;
+ }
+ affirm( ex instanceof SqliteException );
+ q.finalizeStmt();
+ 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 testStatus(){
+ final Sqlite db = openDb();
+ execSql(db, "create table t(a); insert into t values(1),(2),(3)");
+
+ Sqlite.Status s = Sqlite.libStatus(Sqlite.STATUS_MEMORY_USED, false);
+ affirm( s.current > 0 );
+ affirm( s.peak >= s.current );
+
+ s = db.status(Sqlite.DBSTATUS_SCHEMA_USED, false);
+ affirm( s.current > 0 );
+ affirm( s.peak == 0 /* always 0 for SCHEMA_USED */ );
+
+ db.close();
+ }
+
+ @SingleThreadOnly /* because multiple threads legitimately make these
+ results unpredictable */
+ private synchronized void testAutoExtension(){
+ final ValueHolder<Integer> val = new ValueHolder<>(0);
+ final ValueHolder<String> toss = new ValueHolder<>(null);
+ final Sqlite.AutoExtension ax = new Sqlite.AutoExtension(){
+ @Override public void call(Sqlite db){
+ ++val.value;
+ if( null!=toss.value ){
+ throw new RuntimeException(toss.value);
+ }
+ }
+ };
+ Sqlite.addAutoExtension(ax);
+ openDb().close();
+ affirm( 1==val.value );
+ openDb().close();
+ affirm( 2==val.value );
+ Sqlite.clearAutoExtensions();
+ openDb().close();
+ affirm( 2==val.value );
+
+ Sqlite.addAutoExtension( ax );
+ Sqlite.addAutoExtension( ax ); // Must not add a second entry
+ Sqlite.addAutoExtension( ax ); // or a third one
+ openDb().close();
+ affirm( 3==val.value );
+
+ Sqlite db = openDb();
+ affirm( 4==val.value );
+ execSql(db, "ATTACH ':memory:' as foo");
+ affirm( 4==val.value, "ATTACH uses the same connection, not sub-connections." );
+ db.close();
+ db = null;
+
+ Sqlite.removeAutoExtension(ax);
+ openDb().close();
+ affirm( 4==val.value );
+ Sqlite.addAutoExtension(ax);
+ Exception err = null;
+ toss.value = "Throwing from auto_extension.";
+ try{
+ openDb();
+ }catch(Exception e){
+ err = e;
+ }
+ affirm( err!=null );
+ affirm( err.getMessage().indexOf(toss.value)>=0 );
+ toss.value = null;
+
+ val.value = 0;
+ final Sqlite.AutoExtension ax2 = new Sqlite.AutoExtension(){
+ @Override public void call(Sqlite db){
+ ++val.value;
+ }
+ };
+ Sqlite.addAutoExtension(ax2);
+ openDb().close();
+ affirm( 2 == val.value );
+ Sqlite.removeAutoExtension(ax);
+ openDb().close();
+ affirm( 3 == val.value );
+ Sqlite.addAutoExtension(ax);
+ openDb().close();
+ affirm( 5 == val.value );
+ Sqlite.removeAutoExtension(ax2);
+ openDb().close();
+ affirm( 6 == val.value );
+ Sqlite.addAutoExtension(ax2);
+ openDb().close();
+ affirm( 8 == val.value );
+
+ Sqlite.clearAutoExtensions();
+ openDb().close();
+ affirm( 8 == val.value );
+ }
+
+ private void testBackup(){
+ final Sqlite dbDest = openDb();
+
+ try (Sqlite dbSrc = openDb()) {
+ execSql(dbSrc, new String[]{
+ "pragma page_size=512; VACUUM;",
+ "create table t(a);",
+ "insert into t(a) values(1),(2),(3);"
+ });
+ Exception e = null;
+ try {
+ dbSrc.initBackup("main",dbSrc,"main");
+ }catch(Exception x){
+ e = x;
+ }
+ affirm( e instanceof SqliteException );
+ e = null;
+ try (Sqlite.Backup b = dbDest.initBackup("main",dbSrc,"main")) {
+ affirm( null!=b );
+ int rc;
+ while( Sqlite.DONE!=(rc = b.step(1)) ){
+ affirm( 0==rc );
+ }
+ affirm( b.pageCount() > 0 );
+ b.finish();
+ }
+ }
+
+ try (Sqlite.Stmt q = dbDest.prepare("SELECT sum(a) from t")) {
+ q.step();
+ affirm( q.columnInt(0) == 6 );
+ }
+ dbDest.close();
+ }
+
+ private void testCollation(){
+ final Sqlite db = openDb();
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ final Sqlite.Collation myCollation = new Sqlite.Collation() {
+ private String myState =
+ "this is local state. There is much like it, but this is mine.";
+ @Override
+ // Reverse-sorts its inputs...
+ public int call(byte[] lhs, byte[] rhs){
+ int len = lhs.length > rhs.length ? rhs.length : lhs.length;
+ int c = 0, i = 0;
+ for(i = 0; i < len; ++i){
+ c = lhs[i] - rhs[i];
+ if(0 != c) break;
+ }
+ if(0==c){
+ if(i < lhs.length) c = 1;
+ else if(i < rhs.length) c = -1;
+ }
+ return -c;
+ }
+ };
+ final Sqlite.CollationNeeded collLoader = new Sqlite.CollationNeeded(){
+ @Override
+ public void call(Sqlite dbArg, int eTextRep, String collationName){
+ affirm(dbArg == db);
+ db.createCollation("reversi", eTextRep, myCollation);
+ }
+ };
+ db.onCollationNeeded(collLoader);
+ Sqlite.Stmt stmt = db.prepare("SELECT a FROM t ORDER BY a COLLATE reversi");
+ int counter = 0;
+ while( stmt.step() ){
+ final String val = stmt.columnText16(0);
+ ++counter;
+ switch(counter){
+ case 1: affirm("c".equals(val)); break;
+ case 2: affirm("b".equals(val)); break;
+ case 3: affirm("a".equals(val)); break;
+ }
+ }
+ affirm(3 == counter);
+ stmt.finalizeStmt();
+ stmt = db.prepare("SELECT a FROM t ORDER BY a");
+ counter = 0;
+ while( stmt.step() ){
+ final String val = stmt.columnText16(0);
+ ++counter;
+ //outln("Non-REVERSI'd row#"+counter+": "+val);
+ switch(counter){
+ case 3: affirm("c".equals(val)); break;
+ case 2: affirm("b".equals(val)); break;
+ case 1: affirm("a".equals(val)); break;
+ }
+ }
+ affirm(3 == counter);
+ stmt.finalizeStmt();
+ db.onCollationNeeded(null);
+ db.close();
+ }
+
+ @SingleThreadOnly /* because threads inherently break this test */
+ private void testBusy(){
+ final String dbName = "_busy-handler.db";
+ try{
+ Sqlite db1 = openDb(dbName);
+ ++metrics.dbOpen;
+ execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)");
+ Sqlite db2 = openDb(dbName);
+ ++metrics.dbOpen;
+
+ final ValueHolder<Integer> xBusyCalled = new ValueHolder<>(0);
+ Sqlite.BusyHandler handler = new Sqlite.BusyHandler(){
+ @Override public int call(int n){
+ return n > 2 ? 0 : ++xBusyCalled.value;
+ }
+ };
+ db2.setBusyHandler(handler);
+
+ // Force a locked condition...
+ execSql(db1, "BEGIN EXCLUSIVE");
+ int rc = 0;
+ SqliteException ex = null;
+ try{
+ db2.prepare("SELECT * from t");
+ }catch(SqliteException x){
+ ex = x;
+ }
+ affirm( null!=ex );
+ affirm( Sqlite.BUSY == ex.errcode() );
+ affirm( 3 == xBusyCalled.value );
+ db1.close();
+ db2.close();
+ }finally{
+ try{(new java.io.File(dbName)).delete();}
+ catch(Exception e){/* ignore */}
+ }
+ }
+
+ private void testCommitHook(){
+ final Sqlite db = openDb();
+ final ValueHolder<Integer> counter = new ValueHolder<>(0);
+ final ValueHolder<Integer> hookResult = new ValueHolder<>(0);
+ final Sqlite.CommitHook theHook = new Sqlite.CommitHook(){
+ @Override public int call(){
+ ++counter.value;
+ return hookResult.value;
+ }
+ };
+ Sqlite.CommitHook oldHook = db.setCommitHook(theHook);
+ affirm( null == oldHook );
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ affirm( 2 == counter.value );
+ execSql(db, "BEGIN; SELECT 1; SELECT 2; COMMIT;");
+ affirm( 2 == counter.value /* NOT invoked if no changes are made */ );
+ execSql(db, "BEGIN; update t set a='d' where a='c'; COMMIT;");
+ affirm( 3 == counter.value );
+ oldHook = db.setCommitHook(theHook);
+ affirm( theHook == oldHook );
+ execSql(db, "BEGIN; update t set a='e' where a='d'; COMMIT;");
+ affirm( 4 == counter.value );
+ oldHook = db.setCommitHook(null);
+ affirm( theHook == oldHook );
+ execSql(db, "BEGIN; update t set a='f' where a='e'; COMMIT;");
+ affirm( 4 == counter.value );
+ oldHook = db.setCommitHook(null);
+ affirm( null == oldHook );
+ execSql(db, "BEGIN; update t set a='g' where a='f'; COMMIT;");
+ affirm( 4 == counter.value );
+
+ final Sqlite.CommitHook newHook = new Sqlite.CommitHook(){
+ @Override public int call(){return 0;}
+ };
+ oldHook = db.setCommitHook(newHook);
+ affirm( null == oldHook );
+ execSql(db, "BEGIN; update t set a='h' where a='g'; COMMIT;");
+ affirm( 4 == counter.value );
+ oldHook = db.setCommitHook(theHook);
+ affirm( newHook == oldHook );
+ execSql(db, "BEGIN; update t set a='i' where a='h'; COMMIT;");
+ affirm( 5 == counter.value );
+ hookResult.value = CApi.SQLITE_ERROR;
+ int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;");
+ affirm( CApi.SQLITE_CONSTRAINT_COMMITHOOK == rc );
+ affirm( 6 == counter.value );
+ db.close();
+ }
+
+ private void testRollbackHook(){
+ final Sqlite db = openDb();
+ final ValueHolder<Integer> counter = new ValueHolder<>(0);
+ final Sqlite.RollbackHook theHook = new Sqlite.RollbackHook(){
+ @Override public void call(){
+ ++counter.value;
+ }
+ };
+ Sqlite.RollbackHook oldHook = db.setRollbackHook(theHook);
+ affirm( null == oldHook );
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ affirm( 0 == counter.value );
+ execSql(db, false, "BEGIN; SELECT 1; SELECT 2; ROLLBACK;");
+ affirm( 1 == counter.value /* contra to commit hook, is invoked if no changes are made */ );
+
+ final Sqlite.RollbackHook newHook = new Sqlite.RollbackHook(){
+ @Override public void call(){}
+ };
+ oldHook = db.setRollbackHook(newHook);
+ affirm( theHook == oldHook );
+ execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+ affirm( 1 == counter.value );
+ oldHook = db.setRollbackHook(theHook);
+ affirm( newHook == oldHook );
+ execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+ affirm( 2 == counter.value );
+ int rc = execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+ affirm( 0 == rc );
+ affirm( 3 == counter.value );
+ db.close();
+ }
+
+ private void testUpdateHook(){
+ final Sqlite db = openDb();
+ final ValueHolder<Integer> counter = new ValueHolder<>(0);
+ final ValueHolder<Integer> expectedOp = new ValueHolder<>(0);
+ final Sqlite.UpdateHook theHook = new Sqlite.UpdateHook(){
+ @Override
+ public void call(int opId, String dbName, String tableName, long rowId){
+ ++counter.value;
+ if( 0!=expectedOp.value ){
+ affirm( expectedOp.value == opId );
+ }
+ }
+ };
+ Sqlite.UpdateHook oldHook = db.setUpdateHook(theHook);
+ affirm( null == oldHook );
+ expectedOp.value = Sqlite.INSERT;
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ affirm( 3 == counter.value );
+ expectedOp.value = Sqlite.UPDATE;
+ execSql(db, "update t set a='d' where a='c';");
+ affirm( 4 == counter.value );
+ oldHook = db.setUpdateHook(theHook);
+ affirm( theHook == oldHook );
+ expectedOp.value = Sqlite.DELETE;
+ execSql(db, "DELETE FROM t where a='d'");
+ affirm( 5 == counter.value );
+ oldHook = db.setUpdateHook(null);
+ affirm( theHook == oldHook );
+ execSql(db, "update t set a='e' where a='b';");
+ affirm( 5 == counter.value );
+ oldHook = db.setUpdateHook(null);
+ affirm( null == oldHook );
+
+ final Sqlite.UpdateHook newHook = new Sqlite.UpdateHook(){
+ @Override public void call(int opId, String dbName, String tableName, long rowId){
+ }
+ };
+ oldHook = db.setUpdateHook(newHook);
+ affirm( null == oldHook );
+ execSql(db, "update t set a='h' where a='a'");
+ affirm( 5 == counter.value );
+ oldHook = db.setUpdateHook(theHook);
+ affirm( newHook == oldHook );
+ expectedOp.value = Sqlite.UPDATE;
+ execSql(db, "update t set a='i' where a='h'");
+ affirm( 6 == counter.value );
+ db.close();
+ }
+
+ private void testProgress(){
+ final Sqlite db = openDb();
+ final ValueHolder<Integer> counter = new ValueHolder<>(0);
+ db.setProgressHandler(1, new Sqlite.ProgressHandler(){
+ @Override public int call(){
+ ++counter.value;
+ return 0;
+ }
+ });
+ execSql(db, "SELECT 1; SELECT 2;");
+ affirm( counter.value > 0 );
+ int nOld = counter.value;
+ db.setProgressHandler(0, null);
+ execSql(db, "SELECT 1; SELECT 2;");
+ affirm( nOld == counter.value );
+ db.close();
+ }
+
+ private void testAuthorizer(){
+ final Sqlite db = openDb();
+ final ValueHolder<Integer> counter = new ValueHolder<>(0);
+ final ValueHolder<Integer> authRc = new ValueHolder<>(0);
+ final Sqlite.Authorizer auth = new Sqlite.Authorizer(){
+ public int call(int op, String s0, String s1, String s2, String s3){
+ ++counter.value;
+ //outln("xAuth(): "+s0+" "+s1+" "+s2+" "+s3);
+ return authRc.value;
+ }
+ };
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ db.setAuthorizer(auth);
+ execSql(db, "UPDATE t SET a=1");
+ affirm( 1 == counter.value );
+ authRc.value = Sqlite.DENY;
+ int rc = execSql(db, false, "UPDATE t SET a=2");
+ affirm( Sqlite.AUTH==rc );
+ db.setAuthorizer(null);
+ rc = execSql(db, false, "UPDATE t SET a=2");
+ affirm( 0==rc );
+ db.close();
+ }
+
+ private void testBlobOpen(){
+ final Sqlite db = openDb();
+
+ execSql(db, "CREATE TABLE T(a BLOB);"
+ +"INSERT INTO t(rowid,a) VALUES(1, 'def'),(2, 'XYZ');"
+ );
+ Sqlite.Blob b = db.blobOpen("main", "t", "a",
+ db.lastInsertRowId(), true);
+ affirm( 3==b.bytes() );
+ b.write(new byte[] {100, 101, 102 /*"DEF"*/}, 0);
+ b.close();
+ Sqlite.Stmt stmt = db.prepare("SELECT length(a), a FROM t ORDER BY a");
+ affirm( stmt.step() );
+ affirm( 3 == stmt.columnInt(0) );
+ affirm( "def".equals(stmt.columnText16(1)) );
+ stmt.finalizeStmt();
+
+ b = db.blobOpen("main", "t", "a", db.lastInsertRowId(), false);
+ b.reopen(2);
+ final byte[] tgt = new byte[3];
+ b.read( tgt, 0 );
+ affirm( 100==tgt[0] && 101==tgt[1] && 102==tgt[2], "DEF" );
+ b.close();
+ 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();
+ }
+ }
+ );
+ 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 );
@@ -419,7 +975,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: ");
@@ -482,7 +1038,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.
@@ -511,7 +1069,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")){
@@ -529,7 +1087,7 @@ public class Tester2 implements Runnable {
}
if( sqlLog ){
- if( CApi.sqlite3_compileoption_used("ENABLE_SQLLOG") ){
+ if( Sqlite.compileOptionUsed("ENABLE_SQLLOG") ){
final ConfigSqllogCallback log = new ConfigSqllogCallback() {
@Override public void call(sqlite3 db, String msg, int op){
switch(op){
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java
index 479fc74d7..a3905567d 100644
--- a/ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java
@@ -12,10 +12,6 @@
** This file is part of the wrapper1 interface for sqlite3.
*/
package org.sqlite.jni.wrapper1;
-import org.sqlite.jni.capi.CApi;
-import org.sqlite.jni.annotation.*;
-import org.sqlite.jni.capi.sqlite3_context;
-import org.sqlite.jni.capi.sqlite3_value;
/**
A SqlFunction implementation for window functions. The T type
diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js
index c26bce25b..36ca4c976 100644
--- a/ext/wasm/tester1.c-pp.js
+++ b/ext/wasm/tester1.c-pp.js
@@ -1688,7 +1688,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
wasm.sqlite3_wasm_vfs_unlink(0, filename);
}
}
- }/*sqlite3_js_vfs_create_file()*/)
+ }/*sqlite3_js_posix_create_file()*/)
////////////////////////////////////////////////////////////////////
.t({
@@ -2605,28 +2605,6 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
}
}
}/*kvvfs sanity checks*/)
- .t({
- name: 'kvvfs sqlite3_js_vfs_create_file()',
- predicate: ()=>"kvvfs does not currently support this",
- test: function(sqlite3){
- let db;
- try {
- db = new this.JDb(this.kvvfsDbFile);
- const exp = capi.sqlite3_js_db_export(db);
- db.close();
- this.kvvfsUnlink();
- capi.sqlite3_js_vfs_create_file("kvvfs", this.kvvfsDbFile, exp);
- db = new this.JDb(filename);
- T.assert(6 === db.selectValue('select count(*) from kvvfs'));
- }finally{
- db.close();
- this.kvvfsUnlink();
- }
- delete this.kvvfsDbFile;
- delete this.kvvfsUnlink;
- delete this.JDb;
- }
- }/*kvvfs sqlite3_js_vfs_create_file()*/)
;/* end kvvfs tests */
////////////////////////////////////////////////////////////////////////
diff --git a/manifest b/manifest
index 9aadbdf94..90388082d 100644
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Enhance\sthe\sJSONB\slookup\sroutine\swith\slogic\sto\sapply\sedits.\s\sThe\snew\slogic\sis\ncurrently\sunused\sand\shence\suntested\sbut\sdoes\snot\screate\sany\sregressions.
-D 2023-11-03T12:09:22.342
+C Merge\srecent\strunk\senhancements\sinto\sthe\sjsonb\sbranch,\sand\sespecially\sthe\nfiner-grain\scharacterization\sof\sJSON\sfunction\sproperties.
+D 2023-11-08T17:11:13.302
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -89,12 +89,12 @@ F ext/fts3/unicode/parseunicode.tcl a981bd6466d12dd17967515801c3ff23f74a281be1a0
F ext/fts5/extract_api_docs.tcl a36e54ec777172ddd3f9a88daf593b00848368e0
F ext/fts5/fts5.h 05501612cc655504c5dce8ba765ab621d50fc478490089beaa0d75e00b23e520
F ext/fts5/fts5Int.h 78a63cc0795186cde5384816a9403a68c65774b35d952e05b81a1b4b158e07c8
-F ext/fts5/fts5_aux.c 35c4101613eff86902877a4dedd9400b07922e412cbdd637b45041dce2fd5388
+F ext/fts5/fts5_aux.c ee770eec0af8646db9e18fc01a0dad7345b5f5e8cbba236704cfae2d777022ad
F ext/fts5/fts5_buffer.c 3001fbabb585d6de52947b44b455235072b741038391f830d6b729225eeaf6a5
F ext/fts5/fts5_config.c 054359543566cbff1ba65a188330660a5457299513ac71c53b3a07d934c7b081
F ext/fts5/fts5_expr.c bd3b81ce669c4104e34ffe66570af1999a317b142c15fccb112de9fb0caa57a6
-F ext/fts5/fts5_hash.c 65e7707bc8774706574346d18c20218facf87de3599b995963c3e6d6809f203d
-F ext/fts5/fts5_index.c 730c9c32ada18ce1eb7ff847b36507f4b005d88d47af7b47db521e695a8ea4c7
+F ext/fts5/fts5_hash.c 076058f93327051952a752dc765df1acfe783eb11b419b30652aa1fc1f987902
+F ext/fts5/fts5_index.c 01b671fedd2189f6969385d96facc4c06d9c441f0f91d584386a62b724282f9f
F ext/fts5/fts5_main.c a07ed863b8bd9e6fefb62db2fd40a3518eb30a5f7dcfda5be915dd2db45efa2f
F ext/fts5/fts5_storage.c 5d10b9bdcce5b90656cad13c7d12ad4148677d4b9e3fca0481fca56d6601426d
F ext/fts5/fts5_tcl.c b1445cbe69908c411df8084a10b2485500ac70a9c747cdc8cda175a3da59d8ae
@@ -193,7 +193,7 @@ F ext/fts5/test/fts5plan.test b65cfcca9ddd6fdaa118c61e17aeec8e8433bc5b6bb307abd1
F ext/fts5/test/fts5porter.test 8d08010c28527db66bc3feebd2b8767504aaeb9b101a986342fa7833d49d0d15
F ext/fts5/test/fts5porter2.test 0d251a673f02fa13ca7f011654873b3add20745f7402f108600a23e52d8c7457
F ext/fts5/test/fts5prefix.test a0fa67b06650f2deaa7bf27745899d94e0fb547ad9ecbd08bfad98c04912c056
-F ext/fts5/test/fts5prefix2.test 3847ce46f70b82d61c6095103a9d7c53f2952c40a4704157bc079c04d9c8b18b
+F ext/fts5/test/fts5prefix2.test ad751d4a5b029726ee908a7664e27d27bde7584218b8d7944c2a323afd381432
F ext/fts5/test/fts5query.test ac363b17a442620bb0780e93c24f16a5f963dfe2f23dc85647b869efcfada728
F ext/fts5/test/fts5rank.test 30f29e278cd7fb8831ba4f082feb74d8eb90c463bf07113ae200afc2b467ef32
F ext/fts5/test/fts5rebuild.test 55d6f17715cddbf825680dd6551efbc72ed916d8cf1cde40a46fc5d785b451e7
@@ -216,6 +216,7 @@ F ext/fts5/test/fts5synonym2.test 8f891fc49cc1e8daed727051e77e1f42849c784a6a54be
F ext/fts5/test/fts5tok1.test 1f7817499f5971450d8c4a652114b3d833393c8134e32422d0af27884ffe9cef
F ext/fts5/test/fts5tok2.test dcacb32d4a2a3f0dd3215d4a3987f78ae4be21a2
F ext/fts5/test/fts5tokenizer.test ac3c9112b263a639fb0508ae73a3ee886bf4866d2153771a8e8a20c721305a43
+F ext/fts5/test/fts5tokenizer2.test cb5428c7cfb3b6a74b7adfcde65506e329112003e8dffa7501d01c2d18d02569
F ext/fts5/test/fts5trigram.test 6c4e37864f3e7d90673db5563d9736d7e40080ab94d10ebdffa94c1b77941da0
F ext/fts5/test/fts5trigram2.test 9fe4207f8a4241747aff1005258b564958588d21bfd240d6cd4c2e955d31c156
F ext/fts5/test/fts5ubsan.test 783d5a8d13ebfa169e634940228db54540780e3ba7a87ad1e4510e61440bf64b
@@ -237,49 +238,49 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c
F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9
F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282
F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8
-F ext/jni/GNUmakefile 36919b7c4fb8447da4330df9996c7b064b766957f8b7be214a30eab55a8b8072
+F ext/jni/GNUmakefile df91212d772011e3d39712a0e38586856c42528b6ee3d507a5bb3b3248c0ecbc
F ext/jni/README.md ef9ac115e97704ea995d743b4a8334e23c659e5534c3b64065a5405256d5f2f4
F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa
-F ext/jni/src/c/sqlite3-jni.c afe9c25b82279a28fe2c81f869070fa0d434b0a8ccd7f8aca0e8173db410d14a
-F ext/jni/src/c/sqlite3-jni.h 1c45fd4689cec42f3d84d2fee41bb494016a12fcb5fd80291095590666a14015
+F ext/jni/src/c/sqlite3-jni.c 6b95974189d7cc394afbe15507050f1d174170a65be5a4dad201ab11f0a9777a
+F ext/jni/src/c/sqlite3-jni.h 18925c56d6664fdec081c56daf3b2ffa0e0ff6b9b128b9f39b84862f34ba0601
F ext/jni/src/org/sqlite/jni/annotation/NotNull.java a99341e88154e70447596b1af6a27c586317df41a7e0f246fd41370cd7b723b2
F ext/jni/src/org/sqlite/jni/annotation/Nullable.java 0b1879852707f752512d4db9d7edd0d8db2f0c2612316ce1c832715e012ff6ba
F ext/jni/src/org/sqlite/jni/annotation/package-info.java 977b374aed9d5853cbf3438ba3b0940abfa2ea4574f702a2448ee143b98ac3ca
F ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java 1afa90d3f236f79cc7fcd2497e111992644f7596fbc8e8bcf7f1908ae00acd6c
F ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java 0b72cdff61533b564d65b63418129656daa9a9f30e7e7be982bd5ab394b1dbd0
-F ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java 7ed409d5449684616cc924534e22ff6b07d361f12ad904b69ecb10e0568a8013
+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 24aba7b14b11da52cd47083608d37c186122c2e2072e75b2ff923d1f15bfb9e5
-F ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 0bfd6e56e8265c2f05c9207665707285534d78f8466ef0e0430c65677f00943d
+F ext/jni/src/org/sqlite/jni/capi/CApi.java 16a28138c3c25f33356193970644389ff8ebc0720499549653934b2529c8d1dd
+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 f81cf10b79c52f9b2e9247d523d29ae48863935f60420eae35f257c38c80ce95
-F ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java 29c002f3c638cc80f7db1594564a262d1beb32637824c3dca2d60a224d1f71d7
+F ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java 5bfa226a8e7a92e804fd52d6e42b4c7b875fa7a94f8e2c330af8cc244a8920ab
+F ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java 482f53dfec9e3ac2a9070d3fceebd56250932aaaf7c4f5bc8de29fc011416e0c
F ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java b995ca412f59b631803b93aa5b3684fce62e335d1e123207084c054abfd488d4
F ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java 701f2e4d8bdeb27cfbeeb56315d15b13d8752b0fdbca705f31bd4366c58d8a33
F ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java b7036dcb1ef1b39f1f36ac605dde0ff1a24a9a01ade6aa1a605039443e089a61
F ext/jni/src/org/sqlite/jni/capi/OutputPointer.java 68f60aec7aeb5cd4e5fb83449037f668c63cb99f682ee1036cc226d0cbd909b9
F ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java aca8f9fa72e3b6602bc9a7dd3ae9f5b2808103fbbee9b2749dc96c19cdc261a1
-F ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java 819d938e26208adde17ca4b7ddde1d8cd6915b6ab7b708249a9787beca6bd6b6
+F ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java efcf57545c5e282d1dd332fa63329b3b218d98f356ef107a9dbe3979be82213a
F ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java 01bc0c238eed2d5f93c73522cb7849a445cc9098c2ed1e78248fa20ed1cfde5b
F ext/jni/src/org/sqlite/jni/capi/ResultCode.java 8141171f1bcf9f46eef303b9d3c5dc2537a25ad1628f3638398d8a60cacefa7f
-F ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java 105e324d09c207100485e7667ad172e64322c62426bb49b547e9b0dc9c33f5f0
+F ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java e172210a2080e851ebb694c70e9f0bf89284237795e38710a7f5f1b61e3f6787
F ext/jni/src/org/sqlite/jni/capi/SQLFunction.java 0d1e9afc9ff8a2adb94a155b72385155fa3b8011a5cca0bb3c28468c7131c1a5
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 b1a0c015d92a8d0c07a8f6751e9b057557cec9d803e002d48ee5f3b9963abd55
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 9f9e151f1da017b706c0ee5f40f4c86b54e773d6ae4339723e0cc85a456251ab
+F ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java c8bdf7848e6599115d601bcc9427ff902cb33129b9be32870ac6808e04b6ae56
+F ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 22d365746a78c5cd7ae10c39444eb7bbf1a819aad4bb7eb77b1edc47773a3950
F ext/jni/src/org/sqlite/jni/capi/WindowFunction.java caf4396f91b2567904cf94bc538a069fd62260d975bd037d15a02a890ed1ef9e
F ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java f3abb8dd7381f53ebba909437090caf68200f06717b8a7d6aa96fa3e8133117d
F ext/jni/src/org/sqlite/jni/capi/package-info.java 08ff986a65d2be9162442c82d28a65ce431d826f188520717c2ecb1484d0a50e
-F ext/jni/src/org/sqlite/jni/capi/sqlite3.java 4010bbebc5bf44e2044e610786088cdee7dc155da2b333c0551492ff1cedf33b
+F ext/jni/src/org/sqlite/jni/capi/sqlite3.java c6a5c555d163d76663534f2b2cce7cab15325b9852d0f58c6688a85e73ae52f0
F ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java 6742b431cd4d77e8000c1f92ec66265a58414c86bf3b0b5fbcb1164e08477227
F ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java f204ab6ab1263e119fe43730141a00662d80972129a5351dfb11aae5d282df36
F ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java f0ef982009c335c4393ffcb68051809ca1711e4f47bcb8d1d46952f22c01bc22
-F ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java ff579621e9bd5ffbc6b2ef9f996c12db4df6e0c8cc5697c91273e5fca279fcf8
+F ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java 293b5fa7d5b5724c87de544654aca1103d76f3092bc2c8f4360102a65ba25dff
F ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java e1d62a257c13504b46d39d5c21c49cf157ad73fda00cc5f34c931aa008c37049
F ext/jni/src/org/sqlite/jni/fts5/Fts5.java e94681023785f1eff5399f0ddc82f46b035977d350f14838db659236ebdf6b41
F ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java 338637e6e5a2cc385d962b220f3c1f475cc371d12ae43d18ef27327b6e6225f7
@@ -292,14 +293,14 @@ F ext/jni/src/org/sqlite/jni/fts5/fts5_api.java a8e88c3783d21cec51b0748568a96653
F ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java 9e2b954d210d572552b28aca523b272fae14bd41e318921b22f65b728d5bf978
F ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java 92bdaa3893bd684533004d64ade23d329843f809cd0d0f4f1a2856da6e6b4d90
F ext/jni/src/org/sqlite/jni/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e
-F ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java bbe60ac7fd8718edb215a23dc901771bcedb1df3b46d9cf6caff6f419828587f
+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 9be20f3ba6edbe63285224d5d640dbfb0d84fb8cf5134084e3ae40004ec9d6d9
-F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java fae71b7454fa3d9243ad26c80e55b590c044a0f0a23d18cae21f0cfa3a92a969
-F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java aa85b4b05fae240b14f3d332f9524a2f80c619fb03856be72b4adda866b63b72
-F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 13008f8d3c34c1dd55c3afe6dd18dcf94316874cde893ab0661a973fc51a87a4
+F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 2833afdb9af5c1949bb35f4c926a5351fba9d1cdf0996864caa7b47827a346c7
+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 40806dbbf8e120f115e33255d1813db13b40f0a598869e299a947a580429939b
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/org/sqlite/jni/wrapper1/WindowFunction.java c7d1452f9ff26175b3c19bbf273116cc2846610af68e01756d755f037fe7319f
F ext/jni/src/tests/000-000-sanity.test c3427a0e0ac84d7cbe4c95fdc1cd4b61f9ddcf43443408f3000139478c4dc745
F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70
F ext/jni/src/tests/900-001-fts.test bf0ce17a8d082773450e91f2388f5bbb2dfa316d0b676c313c637a91198090f0
@@ -635,7 +636,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555
F ext/wasm/test-opfs-vfs.js f09266873e1a34d9bdb6d3981ec8c9e382f31f215c9fd2f9016d2394b8ae9b7b
F ext/wasm/tester1-worker.html ebc4b820a128963afce328ecf63ab200bd923309eb939f4110510ab449e9814c
F ext/wasm/tester1.c-pp.html 1c1bc78b858af2019e663b1a31e76657b73dc24bede28ca92fbe917c3a972af2
-F ext/wasm/tester1.c-pp.js d628826e936bd143d64e0fa3089752abeeeea38a34a7e2b18d364f090d4e99c6
+F ext/wasm/tester1.c-pp.js a92dc256738dbd1b50f142d1fd0c835294ba09b7bb6526650360e942f88cb63f
F ext/wasm/tests/opfs/concurrency/index.html 0802373d57034d51835ff6041cda438c7a982deea6079efd98098d3e42fbcbc1
F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d
F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2
@@ -669,7 +670,7 @@ F src/build.c 189e4517d67f09f0a3e0d8e1faa6e2ef0c2e95f6ac82e33c912cb7efa2a359cc
F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490
F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
F src/ctime.c 23331529e654be40ca97d171cbbffe9b3d4c71cc53b78fe5501230675952da8b
-F src/date.c eebc54a00e888d3c56147779e9f361b77d62fd69ff2008c5373946aa1ba1d574
+F src/date.c 3b8d02977d160e128469de38493b4085f7c5cf4073193459909a6af3cf6d7c91
F src/dbpage.c 80e46e1df623ec40486da7a5086cb723b0275a6e2a7b01d9f9b5da0f04ba2782
F src/dbstat.c 3b677254d512fcafd4d0b341bf267b38b235ccfddbef24f9154e19360fa22e43
F src/delete.c cb766727c78e715f9fb7ec8a7d03658ed2a3016343ca687acfcec9083cdca500
@@ -683,7 +684,7 @@ F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51
F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6
F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276
-F src/json.c e109da4d5f29e239db837dad09a886a78b6a217592ea8c71e3b584483ce9b179
+F src/json.c e745701b9859a0940f8314eb59e913685a98a606506deef004675b43129da840
F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa
F src/loadext.c 7432c944ff197046d67a1207790a1b13eec4548c85a9457eb0896bb3641dfb36
F src/main.c e1bc8864834697503d370d94613be945d05ca1c5ebdda43e7d5c8ee8c48d433c
@@ -707,7 +708,7 @@ F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63
F src/os_common.h 6c0eb8dd40ef3e12fe585a13e709710267a258e2c8dd1c40b1948a1d14582e06
F src/os_kv.c 4d39e1f1c180b11162c6dc4aa8ad34053873a639bac6baae23272fc03349986a
F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d872107
-F src/os_unix.c cb116fde9e3ca3c1bbfdf89d6928f776a2a34da168e2667426523a4db353b271
+F src/os_unix.c 0a33005e6426702c7e76f3d451f296c088693a95b2be28ba9ef59c8d8529ce6b
F src/os_win.c 4a50a154aeebc66a1f8fb79c1ff6dd5fe3d005556533361e0d460d41cb6a45a8
F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
F src/pager.c 699aab8dfc88056d796b03b40c0ab979040d58dfc3ae9db207f1be91e4880bbf
@@ -723,12 +724,12 @@ F src/printf.c 9da63b9ae1c14789bcae12840f5d800fd9302500cd2d62733fac77f0041b4750
F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
F src/resolve.c d017bad7ba8e778617701a0e986fdeb393d67d6afa84fb28ef4e8b8ad2acf916
F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
-F src/select.c 8ebbc67478e2d43f0b24c79258597646e526c5001d07801bba7d4059088c8b12
-F src/shell.c.in aebfbedaa7706d2c73ab7366a19fc8bc40ea68b70d2529f7feef54e6eb077ea2
-F src/sqlite.h.in ef0e41e83ad1ac0dcc9ec9939bf541a44b1c5de821bee2d6c61754c3252f3276
+F src/select.c a19daa26e95f7245106a31f288b2f50c72d1f2cc156703f04c8c91450e111515
+F src/shell.c.in 7312c571ebf518fc8927bbb5aeb4fa67e5b0dfb2adae4258dcd1ccae42c11e1f
+F src/sqlite.h.in a0fce680a40fe81b13eae3749d001134d9fe0a43aecc09a8986520d5119acfcd
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h 3f046c04ea3595d6bfda99b781926b17e672fd6d27da2ba6d8d8fc39981dcb54
-F src/sqliteInt.h 707095a0591e02f4866ed9798cbdd97a8ae8cf4d98f061808944c2cd1c95d1a9
+F src/sqliteInt.h 1412a692dfb5d615e416f3ddb9b33e5b6bd39f70432bb84046c50574fc604b51
F src/sqliteLimit.h 33b1c9baba578d34efe7dfdb43193b366111cdf41476b1e82699e14c11ee1fb6
F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
@@ -793,19 +794,19 @@ F src/upsert.c fa125a8d3410ce9a97b02cb50f7ae68a2476c405c76aa692d3acf6b8586e9242
F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0
F src/util.c b22cc9f203a8c0b9ee5338a67f8860347d14845864c10248bebe84518a781677
F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104
-F src/vdbe.c 4c76111311e06e74d2519f9cfee5caaaee722bf04559e16f4758a18a0c723b6c
+F src/vdbe.c 5f7432b22b66a09503caab15e86f582f7b55299e1d366799896ae3e354192f09
F src/vdbe.h 41485521f68e9437fdb7ec4a90f9d86ab294e9bb8281e33b235915e29122cfc0
F src/vdbeInt.h 949669dfd8a41550d27dcb905b494f2ccde9a2e6c1b0b04daa1227e2e74c2b2c
F src/vdbeapi.c 22a2661a2886f6b142fce91e95533a1841135e8217f65297d7df353a0eddf650
-F src/vdbeaux.c dffcf79e7e415fcd6e4c8ac1ec7124cae5257018443adf09551c807655b04993
+F src/vdbeaux.c f3997b5956c8d97bd2fc3392db42caecddfa6549e9df82e0a7e5804653ca475a
F src/vdbeblob.c 13f9287b55b6356b4b1845410382d6bede203ceb29ef69388a4a3d007ffacbe5
-F src/vdbemem.c c936e9002af4993b84c4eb7133d6b1190efe46d391cc86117ecd67ba17b1a04b
+F src/vdbemem.c 0012d5f01cc866833847c2f3ae4c318ac53a1cb3d28acad9c35e688039464cf0
F src/vdbesort.c 237840ca1947511fa59bd4e18b9eeae93f2af2468c34d2427b059f896230a547
F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823
F src/vdbevtab.c 2143db7db0ceed69b21422581f434baffc507a08d831565193a7a02882a1b6a7
F src/vtab.c 154725ebecd3bc02f7fbd7ad3974334f73fff76e02a964e828e48a7c5fb7efff
F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
-F src/wal.c 01e051a1e713d9eabdb25df38602837cec8f4c2cae448ce2cf6accc87af903e9
+F src/wal.c bba7db5dae3ffe2c6b9c173fc10be4b570b125e985cb5b95a6c22716213adde4
F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452
F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2
F src/where.c 313ce81270d2a414672370e1ee74e65949ad620519193d4cac2986d073cbc8a0
@@ -1015,7 +1016,7 @@ F test/ctime.test 340f362f41f92972bbd71f44e10569a5cc694062b692231bd08aa6fe6c1c47
F test/cursorhint.test 05cf0febe5c5f8a31f199401fd1c9322249e753950d55f26f9d5aca61408a270
F test/cursorhint2.test 6f3aa9cb19e7418967a10ec6905209bcbb5968054da855fc36c8beee9ae9c42f
F test/dataversion1.test 6e5e86ac681f0782e766ebcb56c019ae001522d114e0e111e5ebf68ccf2a7bb8
-F test/date.test c0d17cdfd89395bc78087b131e3538d96f864b5029c335318011accc7c0d0934
+F test/date.test ff2341a1ef71b9a27979494d299222f9a293aa22cb9ff6e9c38d88a895317ebf
F test/date2.test 7e12ec14aaf4d5e6294b4ba140445b0eca06ea50062a9c3a69c4ee13d0b6f8b1
F test/date3.test a1b77abf05c6772fe5ca2337cac1398892f2a41e62bce7e6be0f4a08a0e64ae5
F test/date4.test 8aeb3de5b5e9fda968baa9357e4c0fae573724b7904943410195a19e96e31b6a
@@ -1487,7 +1488,6 @@ F test/recover.test fd5199f928757cb308661b5fdca1abc19398a798ff7f24b57c3071e9f8e0
F test/regexp1.test 8f2a8bc1569666e29a4cee6c1a666cd224eb6d50e2470d1dc1df995170f3e0f1
F test/regexp2.test 55ed41da802b0e284ac7e2fe944be3948f93ff25abbca0361a609acfed1368b5
F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2118d
-F test/releasetest_data.tcl 80ef3941bf7ad136f4dc3d8960786f10a4f488797238171bdd757cc6eb4c3efa
F test/resetdb.test 54c06f18bc832ac6d6319e5ab23d5c8dd49fdbeec7c696d791682a8006bd5fc3
F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb
F test/returning1.test db532cde29d6aebbc48c6ddc3149b30476f8e69ca7a2c4b53986c7635e6fd8ec
@@ -1587,7 +1587,7 @@ F test/snapshot2.test 8d6ff5dd9cc503f6e12d408a30409c3f9c653507b24408d9cd7195931c
F test/snapshot3.test 8744313270c55f6e18574283553d3c5c5fe4c5970585663613a0e75c151e599b
F test/snapshot4.test d4e9347ef2fcabc491fc893506c7bbaf334da3be111d6eb4f3a97cc623b78322
F test/snapshot_fault.test 129234ceb9b26a0e1000e8563a16e790f5c1412354e70749cbd78c3d5d07d60a
-F test/snapshot_up.test a0a29c4cf33475fcef07c3f8e64af795e24ab91b4cc68295863402a393cdd41c
+F test/snapshot_up.test 77dc7853bfb2b4fa249f76e1714cfa1e596826165d9ef22c06ac3a0b7b778d9a
F test/soak.test 18944cf21b94a7fe0df02016a6ee1e9632bc4e8d095a0cb49d95e15d5cca2d5c
F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087
F test/sort.test f86751134159abb5e5fd4381a0d7038c91013638cd1e3fa1d7850901f6df6196
@@ -1651,7 +1651,7 @@ F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637
F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc
F test/tester.tcl 68454ef88508c196d19e8694daa27bff7107a91857799eaa12f417188ae53ede
F test/testrunner.tcl 8a6721213bce1cfd3b33e1588cc6431143d96b98819206bf91f5a205fbb150d4
-F test/testrunner_data.tcl 7f73f93634d32dafc857ed491b840f371113d09fde6a8bfb9e47b938d47b8c85
+F test/testrunner_data.tcl e4d5017290a6d5c11785e36cc94c67d8bb950c8cdc2dbe4c1db2a3a583812560
F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899
F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502
F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7
@@ -1952,8 +1952,6 @@ F test/walshared.test 42e3808582504878af237ea02c42ca793e8a0efaa19df7df26ac573370
F test/walslow.test c05c68d4dc2700a982f89133ce103a1a84cc285f
F test/walthread.test 14b20fcfa6ae152f5d8e12f5dc8a8a724b7ef189f5d8ef1e2ceab79f2af51747
F test/walvfs.test e1a6ad0f3c78e98b55c3d5f0889cf366cc0d0a1cb2bccb44ac9ec67384adc4a1
-F test/wapp.tcl b440cd8cf57953d3a49e7ee81e6a18f18efdaf113b69f7d8482b0710a64566ec
-F test/wapptest.tcl 1bea58a6a8e68a73f542ee4fca28b771b84ed803bd0c9e385087070b3d747b3c x
F test/where.test 59abb854eee24f166b5f7ba9d17eb250abc59ce0a66c48912ffb10763648196d
F test/where2.test 03c21a11e7b90e2845fc3c8b4002fc44cc2797fa74c86ee47d70bd7ea4f29ed6
F test/where3.test 5b4ffc0ac2ea0fe92f02b1244b7531522fe4d7bccf6fa8741d54e82c10e67753
@@ -2109,7 +2107,7 @@ F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaa
F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848
F tool/src-verify.c 41c586dee84d0b190ad13e0282ed83d4a65ec9fefde9adf4943efdf6558eea7f
F tool/srcck1.c 371de5363b70154012955544f86fdee8f6e5326f
-F tool/srctree-check.tcl 9f1098d439159692ece45cc9e701b35df4956269399fe0c770e41f235d1ce5e6
+F tool/srctree-check.tcl c15f860a3c97d5f7b4c14b60392d9466af29dd006c4ef18127f502641e2977a8
F tool/stack_usage.tcl f8e71b92cdb099a147dad572375595eae55eca43
F tool/stripccomments.c 20b8aabc4694d0d4af5566e42da1f1a03aff057689370326e9269a9ddcffdc37
F tool/symbols-mingw.sh 4dbcea7e74768305384c9fd2ed2b41bbf9f0414d
@@ -2142,8 +2140,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 b089bf46374b374d02d7654c65eb3e75d1777638b398061e47644af0dab48c9b
-R 02d78f9919449761c67674ad6f60265a
+P b12110276fc15d1b6b0cc455f89747ace7a09650d5ba433d8bb431bb4e5bc951 b2b62546c4a5e9dccb8aa0cb8eda228d662c69159e320b01a377317bc909e89f
+R d8efa19343b75dfdaa31ac930f849ac1
U drh
-Z f8a451c94f8b9175a3b99dd9c9c86acd
+Z 8a4148addede5d91729ce4e74e65d8c3
# Remove this line to create a well-formed Fossil manifest.
diff --git a/manifest.uuid b/manifest.uuid
index c8ced97e7..0529da599 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-b12110276fc15d1b6b0cc455f89747ace7a09650d5ba433d8bb431bb4e5bc951 \ No newline at end of file
+72393b003f9f8675e4a124dddd09607b5b34ddefe56716b283c68c0982fb3d96 \ No newline at end of file
diff --git a/src/date.c b/src/date.c
index f16308544..e493542db 100644
--- a/src/date.c
+++ b/src/date.c
@@ -1043,6 +1043,12 @@ static int isDate(
}
computeJD(p);
if( p->isError || !validJulianDay(p->iJD) ) return 1;
+ if( argc==1 && p->validYMD && p->D>28 ){
+ /* Make sure a YYYY-MM-DD is normalized.
+ ** Example: 2023-02-31 -> 2023-03-03 */
+ assert( p->validJD );
+ p->validYMD = 0;
+ }
return 0;
}
diff --git a/src/json.c b/src/json.c
index e998af523..04349899c 100644
--- a/src/json.c
+++ b/src/json.c
@@ -5859,37 +5859,43 @@ static sqlite3_module jsonTreeModule = {
void sqlite3RegisterJsonFunctions(void){
#ifndef SQLITE_OMIT_JSON
static FuncDef aJsonFunc[] = {
- JFUNCTION(json, 1, 0, jsonRemoveFunc),
- JFUNCTION(jsonb, 1, JSON_BLOB, jsonbFunc),
- JFUNCTION(json_array, -1, 0, jsonArrayFunc),
- JFUNCTION(jsonb_array, -1, JSON_BLOB, jsonArrayFunc),
- JFUNCTION(json_array_length, 1, 0, jsonArrayLengthFunc),
- JFUNCTION(json_array_length, 2, 0, jsonArrayLengthFunc),
- JFUNCTION(json_error_position,1, 0, jsonErrorFunc),
- JFUNCTION(json_extract, -1, 0, jsonExtractFunc),
- JFUNCTION(jsonb_extract, -1, JSON_BLOB, jsonExtractFunc),
- JFUNCTION(->, 2, JSON_JSON, jsonExtractFunc),
- JFUNCTION(->>, 2, JSON_SQL, jsonExtractFunc),
- JFUNCTION(json_insert, -1, 0, jsonSetFunc),
- JFUNCTION(jsonb_insert, -1, JSON_BLOB, jsonSetFunc),
- JFUNCTION(json_object, -1, 0, jsonObjectFunc),
- JFUNCTION(jsonb_object, -1, JSON_BLOB, jsonObjectFunc),
- JFUNCTION(json_patch, 2, 0, jsonPatchFunc),
- JFUNCTION(jsonb_patch, 2, JSON_BLOB, jsonPatchFunc),
- JFUNCTION(json_quote, 1, 0, jsonQuoteFunc),
- JFUNCTION(json_remove, -1, 0, jsonRemoveFunc),
- JFUNCTION(jsonb_remove, -1, JSON_BLOB, jsonRemoveFunc),
- JFUNCTION(json_replace, -1, 0, jsonReplaceFunc),
- JFUNCTION(jsonb_replace, -1, JSON_BLOB, jsonReplaceFunc),
- JFUNCTION(json_set, -1, JSON_ISSET, jsonSetFunc),
- JFUNCTION(jsonb_set, -1, JSON_ISSET|JSON_BLOB, jsonSetFunc),
- JFUNCTION(json_type, 1, 0, jsonTypeFunc),
- JFUNCTION(json_type, 2, 0, jsonTypeFunc),
- JFUNCTION(json_valid, 1, 0, jsonValidFunc),
+ /* Might return JSON text (subtype J) */
+ /* | */
+ /* Uses cache ------, | ,---- Returns JSONB */
+ /* | | | */
+ /* Number of arguments ---, | | | ,--- Flags */
+ /* | | | | | */
+ JFUNCTION(json, 1, 1, 1, 0, 0, jsonRemoveFunc),
+ JFUNCTION(jsonb, 1, 1, 0, 1, 0, jsonbFunc),
+ JFUNCTION(json_array, -1, 0, 1, 0, 0, jsonArrayFunc),
+ JFUNCTION(jsonb_array, -1, 0, 0, 1, 0, jsonArrayFunc),
+ JFUNCTION(json_array_length, 1, 1, 0, 0, 0, jsonArrayLengthFunc),
+ JFUNCTION(json_array_length, 2, 1, 0, 0, 0, jsonArrayLengthFunc),
+ JFUNCTION(json_error_position,1, 1, 0, 0, 0, jsonErrorFunc),
+ JFUNCTION(json_extract, -1, 1, 1, 0, 0, jsonExtractFunc),
+ JFUNCTION(jsonb_extract, -1, 1, 0, 1, 0, jsonExtractFunc),
+ JFUNCTION(->, 2, 1, 1, 0, JSON_JSON, jsonExtractFunc),
+ JFUNCTION(->>, 2, 1, 0, 0, JSON_SQL, jsonExtractFunc),
+ JFUNCTION(json_insert, -1, 1, 1, 0, 0, jsonSetFunc),
+ JFUNCTION(jsonb_insert, -1, 1, 0, 1, 0, jsonSetFunc),
+ JFUNCTION(json_object, -1, 0, 1, 0, 0, jsonObjectFunc),
+ JFUNCTION(jsonb_object, -1, 0, 0, 1, 0, jsonObjectFunc),
+ JFUNCTION(json_patch, 2, 1, 1, 0, 0, jsonPatchFunc),
+ JFUNCTION(jsonb_patch, 2, 1, 0, 1, 0, jsonPatchFunc),
+ JFUNCTION(json_quote, 1, 0, 1, 0, 0, jsonQuoteFunc),
+ JFUNCTION(json_remove, -1, 1, 1, 0, 0, jsonRemoveFunc),
+ JFUNCTION(jsonb_remove, -1, 1, 0, 1, 0, jsonRemoveFunc),
+ JFUNCTION(json_replace, -1, 1, 1, 0, 0, jsonReplaceFunc),
+ JFUNCTION(jsonb_replace, -1, 1, 0, 1, 0, jsonReplaceFunc),
+ JFUNCTION(json_set, -1, 1, 1, 0, JSON_ISSET, jsonSetFunc),
+ JFUNCTION(jsonb_set, -1, 1, 0, 1, JSON_ISSET, jsonSetFunc),
+ JFUNCTION(json_type, 1, 1, 0, 0, 0, jsonTypeFunc),
+ JFUNCTION(json_type, 2, 1, 0, 0, 0, jsonTypeFunc),
+ JFUNCTION(json_valid, 1, 1, 0, 0, 0, jsonValidFunc),
#if SQLITE_DEBUG
- JFUNCTION(json_parse, 1, 0, jsonParseFunc),
- JFUNCTION(json_test1, 1, 0, jsonTest1Func),
- JFUNCTION(jsonb_test2, 1, 0, jsonbTest2),
+ JFUNCTION(json_parse, 1, 1, 0, 0, 0, jsonParseFunc),
+ JFUNCTION(json_test1, 1, 1, 0, 0, 0, jsonTest1Func),
+ JFUNCTION(jsonb_test2, 1, 1, 0, 1, 0, jsonbTest2),
#endif
WAGGREGATE(json_group_array, 1, 0, 0,
jsonArrayStep, jsonArrayFinal, jsonArrayValue, jsonGroupInverse,
diff --git a/src/os_unix.c b/src/os_unix.c
index a33e6f4df..cd3e0fc54 100644
--- a/src/os_unix.c
+++ b/src/os_unix.c
@@ -4980,12 +4980,15 @@ static int unixShmLock(
** It is not permitted to block on the RECOVER lock.
*/
#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
- assert( (flags & SQLITE_SHM_UNLOCK) || pDbFd->iBusyTimeout==0 || (
- (ofst!=2) /* not RECOVER */
- && (ofst!=1 || (p->exclMask|p->sharedMask)==0)
- && (ofst!=0 || (p->exclMask|p->sharedMask)<3)
- && (ofst<3 || (p->exclMask|p->sharedMask)<(1<<ofst))
- ));
+ {
+ u16 lockMask = (p->exclMask|p->sharedMask);
+ assert( (flags & SQLITE_SHM_UNLOCK) || pDbFd->iBusyTimeout==0 || (
+ (ofst!=2) /* not RECOVER */
+ && (ofst!=1 || lockMask==0 || lockMask==2)
+ && (ofst!=0 || lockMask<3)
+ && (ofst<3 || lockMask<(1<<ofst))
+ ));
+ }
#endif
mask = (1<<(ofst+n)) - (1<<ofst);
diff --git a/src/select.c b/src/select.c
index e97a5afb7..7a79385e0 100644
--- a/src/select.c
+++ b/src/select.c
@@ -8472,7 +8472,7 @@ int sqlite3Select(
updateAccumulator(pParse, regAcc, pAggInfo, eDist);
if( eDist!=WHERE_DISTINCT_NOOP ){
struct AggInfo_func *pF = pAggInfo->aFunc;
- if( ALWAYS(pF) ){
+ if( pF ){
fixDistinctOpenEph(pParse, eDist, pF->iDistinct, pF->iDistAddr);
}
}
diff --git a/src/shell.c.in b/src/shell.c.in
index 3cfe1a94a..8d201bbbd 100644
--- a/src/shell.c.in
+++ b/src/shell.c.in
@@ -10074,8 +10074,14 @@ static int do_meta_command(char *zLine, ShellState *p){
sqlite3_db_config(
p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0
);
-#ifndef SQLITE_ENABLE_STMT_SCANSTATUS
+#if !defined(SQLITE_ENABLE_STMT_SCANSTATUS)
raw_printf(stderr, "Warning: .scanstats not available in this build.\n");
+#elif !defined(SQLITE_ENABLE_BYTECODE_VTAB)
+ if( p->scanstatsOn==3 ){
+ raw_printf(stderr,
+ "Warning: \".scanstats vm\" not available in this build.\n"
+ );
+ }
#endif
}else{
raw_printf(stderr, "Usage: .scanstats on|off|est\n");
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index 01a7f4d4e..2317d98f7 100644
--- a/src/sqlite.h.in
+++ b/src/sqlite.h.in
@@ -5913,14 +5913,22 @@ sqlite3 *sqlite3_context_db_handle(sqlite3_context*);
** <li> ^(when sqlite3_set_auxdata() is invoked again on the same
** parameter)^, or
** <li> ^(during the original sqlite3_set_auxdata() call when a memory
-** allocation error occurs.)^ </ul>
+** allocation error occurs.)^
+** <li> ^(during the original sqlite3_set_auxdata() call if the function
+** is evaluated during query planning instead of during query execution,
+** as sometimes happens with [SQLITE_ENABLE_STAT4].)^ </ul>
**
-** Note the last bullet in particular. The destructor X in
+** Note the last two bullets in particular. The destructor X in
** sqlite3_set_auxdata(C,N,P,X) might be called immediately, before the
** sqlite3_set_auxdata() interface even returns. Hence sqlite3_set_auxdata()
** should be called near the end of the function implementation and the
** function implementation should not make any use of P after
-** sqlite3_set_auxdata() has been called.
+** sqlite3_set_auxdata() has been called. Furthermore, a call to
+** sqlite3_get_auxdata() that occurs immediately after a corresponding call
+** to sqlite3_set_auxdata() might still return NULL if an out-of-memory
+** condition occurred during the sqlite3_set_auxdata() call or if the
+** function is being evaluated during query planning rather than during
+** query execution.
**
** ^(In practice, auxiliary data is preserved between function calls for
** function parameters that are compile-time constants, including literal
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 23beb48de..f14e67f12 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -2014,7 +2014,7 @@ struct FuncDestructor {
#define SQLITE_FUNC_SLOCHNG 0x2000 /* "Slow Change". Value constant during a
** single query - might change over time */
#define SQLITE_FUNC_TEST 0x4000 /* Built-in testing functions */
-/* 0x8000 -- available for reuse */
+#define SQLITE_FUNC_RUNONLY 0x8000 /* Cannot be used by valueFromFunction */
#define SQLITE_FUNC_WINDOW 0x00010000 /* Built-in window-only function */
#define SQLITE_FUNC_INTERNAL 0x00040000 /* For use by NestedParse() only */
#define SQLITE_FUNC_DIRECT 0x00080000 /* Not for use in TRIGGERs or VIEWs */
@@ -2113,10 +2113,10 @@ struct FuncDestructor {
#define MFUNCTION(zName, nArg, xPtr, xFunc) \
{nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \
xPtr, 0, xFunc, 0, 0, 0, #zName, {0} }
-#define JFUNCTION(zName, nArg, iArg, xFunc) \
- {nArg, SQLITE_FUNC_BUILTIN|SQLITE_DETERMINISTIC|\
- SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \
- SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} }
+#define JFUNCTION(zName, nArg, bUseCache, bSubtype, bJsonB, iArg, xFunc) \
+ {nArg, SQLITE_FUNC_BUILTIN|SQLITE_DETERMINISTIC|SQLITE_FUNC_CONSTANT|\
+ SQLITE_UTF8|((bUseCache)*SQLITE_FUNC_RUNONLY)|((bSubtype)*SQLITE_SUBTYPE), \
+ SQLITE_INT_TO_PTR(iArg|((bJsonB)*JSON_BLOB)),0,xFunc,0, 0, 0, #zName, {0} }
#define INLINE_FUNC(zName, nArg, iArg, mFlags) \
{nArg, SQLITE_FUNC_BUILTIN|\
SQLITE_UTF8|SQLITE_FUNC_INLINE|SQLITE_FUNC_CONSTANT|(mFlags), \
diff --git a/src/vdbe.c b/src/vdbe.c
index 598560822..f13506a3a 100644
--- a/src/vdbe.c
+++ b/src/vdbe.c
@@ -2033,7 +2033,7 @@ case OP_AddImm: { /* in1 */
pIn1 = &aMem[pOp->p1];
memAboutToChange(p, pIn1);
sqlite3VdbeMemIntegerify(pIn1);
- pIn1->u.i += pOp->p2;
+ *(u64*)&pIn1->u.i += (u64)pOp->p2;
break;
}
diff --git a/src/vdbeaux.c b/src/vdbeaux.c
index 54317b816..1d9921b19 100644
--- a/src/vdbeaux.c
+++ b/src/vdbeaux.c
@@ -1526,7 +1526,7 @@ static void SQLITE_NOINLINE vdbeChangeP4Full(
int n
){
if( pOp->p4type ){
- freeP4(p->db, pOp->p4type, pOp->p4.p);
+ assert( pOp->p4type > P4_FREE_IF_LE );
pOp->p4type = 0;
pOp->p4.p = 0;
}
diff --git a/src/vdbemem.c b/src/vdbemem.c
index eee828143..d52716468 100644
--- a/src/vdbemem.c
+++ b/src/vdbemem.c
@@ -1515,7 +1515,7 @@ static int valueFromFunction(
#endif
assert( pFunc );
if( (pFunc->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0
- || (pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL)
+ || (pFunc->funcFlags & (SQLITE_FUNC_NEEDCOLL|SQLITE_FUNC_RUNONLY))!=0
){
return SQLITE_OK;
}
diff --git a/src/wal.c b/src/wal.c
index d63c13ffc..655d78f59 100644
--- a/src/wal.c
+++ b/src/wal.c
@@ -4248,9 +4248,14 @@ int sqlite3WalCheckpoint(
/* Read the wal-index header. */
SEH_TRY {
if( rc==SQLITE_OK ){
+ /* For a passive checkpoint, do not re-enable blocking locks after
+ ** reading the wal-index header. A passive checkpoint should not block
+ ** or invoke the busy handler. The only lock such a checkpoint may
+ ** attempt to obtain is a lock on a read-slot, and it should give up
+ ** immediately and do a partial checkpoint if it cannot obtain it. */
walDisableBlocking(pWal);
rc = walIndexReadHdr(pWal, &isChanged);
- (void)walEnableBlocking(pWal);
+ if( eMode2!=SQLITE_CHECKPOINT_PASSIVE ) (void)walEnableBlocking(pWal);
if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){
sqlite3OsUnfetch(pWal->pDbFd, 0, 0);
}
diff --git a/test/date.test b/test/date.test
index fb76dac8a..19cecc2db 100644
--- a/test/date.test
+++ b/test/date.test
@@ -146,6 +146,8 @@ datetest 2.49 {datetime('2003-10-22 12:24','0000 second')} {2003-10-22 12:24:00}
datetest 2.50 {datetime('2003-10-22 12:24','0001 second')} {2003-10-22 12:24:01}
datetest 2.51 {datetime('2003-10-22 12:24','nonsense')} NULL
+datetest 2.60 {datetime('2023-02-31')} {2023-03-03 00:00:00}
+
datetest 3.1 {strftime('%d','2003-10-31 12:34:56.432')} 31
datetest 3.2.1 {strftime('pre%fpost','2003-10-31 12:34:56.432')} pre56.432post
datetest 3.2.2 {strftime('%f','2003-10-31 12:34:59.9999999')} 59.999
@@ -452,6 +454,9 @@ datetest 13.31 {date('2001-01-01','+1.5 years')} {2002-07-02}
datetest 13.32 {date('2002-01-01','+1.5 years')} {2003-07-02}
datetest 13.33 {date('2002-01-01','-1.5 years')} {2000-07-02}
datetest 13.34 {date('2001-01-01','-1.5 years')} {1999-07-02}
+datetest 13.35 {date('2023-02-28')} {2023-02-28}
+datetest 13.36 {date('2023-02-29')} {2023-03-01}
+datetest 13.37 {date('2023-04-31')} {2023-05-01}
# Test for issues reported by BareFeet (list.sql at tandb.com.au)
# on mailing list on 2008-06-12.
diff --git a/test/releasetest_data.tcl b/test/releasetest_data.tcl
deleted file mode 100644
index 4ed57a9c8..000000000
--- a/test/releasetest_data.tcl
+++ /dev/null
@@ -1,845 +0,0 @@
-# 2019 August 01
-#
-# 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 implements a program that produces scripts (either shell scripts
-# or batch files) to implement a particular test that is part of the SQLite
-# release testing procedure. For example, to run veryquick.test with a
-# specified set of -D compiler switches.
-#
-# A "configuration" is a set of options passed to [./configure] and [make]
-# to build the SQLite library in a particular fashion. A "platform" is a
-# list of tests; most platforms are named after the hardware/OS platform
-# that the tests will be run on as part of the release procedure. Each
-# "test" is a combination of a configuration and a makefile target (e.g.
-# "fulltest"). The program may be invoked as follows:
-#
-set USAGE {
-$argv0 script ?-msvc? CONFIGURATION TARGET
- Given a configuration and make target, return a bash (or, if -msvc
- is specified, batch) script to execute the test. The first argument
- passed to the script must be a directory containing SQLite source code.
-
-$argv0 configurations
- List available configurations.
-
-$argv0 platforms
- List available platforms.
-
-$argv0 tests ?-nodebug? PLATFORM
- List tests in a specified platform. If the -nodebug switch is
- specified, synthetic debug/ndebug configurations are omitted. Each
- test is a combination of a configuration and a makefile target.
-}
-
-# Omit comments (text between # and \n) in a long multi-line string.
-#
-proc strip_comments {in} {
- regsub -all {#[^\n]*\n} $in {} out
- return $out
-}
-
-array set ::Configs [strip_comments {
- "Default" {
- -O2
- --disable-amalgamation --disable-shared
- --enable-session
- -DSQLITE_ENABLE_RBU
- }
- "All-Debug" {
- --enable-debug --enable-all
- }
- "All-O0" {
- -O0 --enable-all
- }
- "Sanitize" {
- CC=clang -fsanitize=address,undefined
- -DSQLITE_ENABLE_STAT4
- -DCONFIG_SLOWDOWN_FACTOR=5.0
- --enable-debug
- --enable-all
- }
- "Stdcall" {
- -DUSE_STDCALL=1
- -O2
- }
- "Have-Not" {
- # The "Have-Not" configuration sets all possible -UHAVE_feature options
- # in order to verify that the code works even on platforms that lack
- # these support services.
- -DHAVE_FDATASYNC=0
- -DHAVE_GMTIME_R=0
- -DHAVE_ISNAN=0
- -DHAVE_LOCALTIME_R=0
- -DHAVE_LOCALTIME_S=0
- -DHAVE_MALLOC_USABLE_SIZE=0
- -DHAVE_STRCHRNUL=0
- -DHAVE_USLEEP=0
- -DHAVE_UTIME=0
- }
- "Unlock-Notify" {
- -O2
- -DSQLITE_ENABLE_UNLOCK_NOTIFY
- -DSQLITE_THREADSAFE
- -DSQLITE_TCL_DEFAULT_FULLMUTEX=1
- }
- "User-Auth" {
- -O2
- -DSQLITE_USER_AUTHENTICATION=1
- }
- "Secure-Delete" {
- -O2
- -DSQLITE_SECURE_DELETE=1
- -DSQLITE_SOUNDEX=1
- }
- "Update-Delete-Limit" {
- -O2
- -DSQLITE_DEFAULT_FILE_FORMAT=4
- -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1
- -DSQLITE_ENABLE_STMT_SCANSTATUS
- -DSQLITE_LIKE_DOESNT_MATCH_BLOBS
- -DSQLITE_ENABLE_CURSOR_HINTS
- }
- "Check-Symbols" {
- -DSQLITE_MEMDEBUG=1
- -DSQLITE_ENABLE_FTS3_PARENTHESIS=1
- -DSQLITE_ENABLE_FTS3=1
- -DSQLITE_ENABLE_RTREE=1
- -DSQLITE_ENABLE_MEMSYS5=1
- -DSQLITE_ENABLE_MEMSYS3=1
- -DSQLITE_ENABLE_COLUMN_METADATA=1
- -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1
- -DSQLITE_SECURE_DELETE=1
- -DSQLITE_SOUNDEX=1
- -DSQLITE_ENABLE_ATOMIC_WRITE=1
- -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1
- -DSQLITE_ENABLE_OVERSIZE_CELL_CHECK=1
- -DSQLITE_ENABLE_STAT4
- -DSQLITE_ENABLE_STMT_SCANSTATUS
- --enable-fts5 --enable-session
- }
- "Debug-One" {
- --disable-shared
- -O2 -funsigned-char
- -DSQLITE_DEBUG=1
- -DSQLITE_MEMDEBUG=1
- -DSQLITE_MUTEX_NOOP=1
- -DSQLITE_TCL_DEFAULT_FULLMUTEX=1
- -DSQLITE_ENABLE_FTS3=1
- -DSQLITE_ENABLE_RTREE=1
- -DSQLITE_ENABLE_MEMSYS5=1
- -DSQLITE_ENABLE_COLUMN_METADATA=1
- -DSQLITE_ENABLE_STAT4
- -DSQLITE_ENABLE_HIDDEN_COLUMNS
- -DSQLITE_MAX_ATTACHED=125
- -DSQLITE_MUTATION_TEST
- --enable-fts5
- }
- "Debug-Two" {
- -DSQLITE_DEFAULT_MEMSTATUS=0
- -DSQLITE_MAX_EXPR_DEPTH=0
- --enable-debug
- }
- "Fast-One" {
- -O6
- -DSQLITE_ENABLE_FTS4=1
- -DSQLITE_ENABLE_RTREE=1
- -DSQLITE_ENABLE_STAT4
- -DSQLITE_ENABLE_RBU
- -DSQLITE_MAX_ATTACHED=125
- -DSQLITE_MAX_MMAP_SIZE=12884901888
- -DSQLITE_ENABLE_SORTER_MMAP=1
- -DLONGDOUBLE_TYPE=double
- --enable-session
- }
- "Device-One" {
- -O2
- -DSQLITE_DEBUG=1
- -DSQLITE_DEFAULT_AUTOVACUUM=1
- -DSQLITE_DEFAULT_CACHE_SIZE=64
- -DSQLITE_DEFAULT_PAGE_SIZE=1024
- -DSQLITE_DEFAULT_TEMP_CACHE_SIZE=32
- -DSQLITE_DISABLE_LFS=1
- -DSQLITE_ENABLE_ATOMIC_WRITE=1
- -DSQLITE_ENABLE_IOTRACE=1
- -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1
- -DSQLITE_MAX_PAGE_SIZE=4096
- -DSQLITE_OMIT_LOAD_EXTENSION=1
- -DSQLITE_OMIT_PROGRESS_CALLBACK=1
- -DSQLITE_OMIT_VIRTUALTABLE=1
- -DSQLITE_ENABLE_HIDDEN_COLUMNS
- -DSQLITE_TEMP_STORE=3
- }
- "Device-Two" {
- -DSQLITE_4_BYTE_ALIGNED_MALLOC=1
- -DSQLITE_DEFAULT_AUTOVACUUM=1
- -DSQLITE_DEFAULT_CACHE_SIZE=1000
- -DSQLITE_DEFAULT_LOCKING_MODE=0
- -DSQLITE_DEFAULT_PAGE_SIZE=1024
- -DSQLITE_DEFAULT_TEMP_CACHE_SIZE=1000
- -DSQLITE_DISABLE_LFS=1
- -DSQLITE_ENABLE_FTS3=1
- -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1
- -DSQLITE_ENABLE_RTREE=1
- -DSQLITE_MAX_COMPOUND_SELECT=50
- -DSQLITE_MAX_PAGE_SIZE=32768
- -DSQLITE_OMIT_TRACE=1
- -DSQLITE_TEMP_STORE=3
- -DSQLITE_THREADSAFE=2
- --enable-fts5 --enable-session
- }
- "Locking-Style" {
- -O2
- -DSQLITE_ENABLE_LOCKING_STYLE=1
- }
- "Apple" {
- -Os
- -DHAVE_GMTIME_R=1
- -DHAVE_ISNAN=1
- -DHAVE_LOCALTIME_R=1
- -DHAVE_PREAD=1
- -DHAVE_PWRITE=1
- -DHAVE_UTIME=1
- -DSQLITE_DEFAULT_CACHE_SIZE=1000
- -DSQLITE_DEFAULT_CKPTFULLFSYNC=1
- -DSQLITE_DEFAULT_MEMSTATUS=1
- -DSQLITE_DEFAULT_PAGE_SIZE=1024
- -DSQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS=1
- -DSQLITE_ENABLE_API_ARMOR=1
- -DSQLITE_ENABLE_AUTO_PROFILE=1
- -DSQLITE_ENABLE_FLOCKTIMEOUT=1
- -DSQLITE_ENABLE_FTS3=1
- -DSQLITE_ENABLE_FTS3_PARENTHESIS=1
- -DSQLITE_ENABLE_FTS3_TOKENIZER=1
- -DSQLITE_ENABLE_PERSIST_WAL=1
- -DSQLITE_ENABLE_PURGEABLE_PCACHE=1
- -DSQLITE_ENABLE_RTREE=1
- -DSQLITE_ENABLE_SNAPSHOT=1
- # -DSQLITE_ENABLE_SQLLOG=1
- -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1
- -DSQLITE_MAX_LENGTH=2147483645
- -DSQLITE_MAX_VARIABLE_NUMBER=500000
- # -DSQLITE_MEMDEBUG=1
- -DSQLITE_NO_SYNC=1
- -DSQLITE_OMIT_AUTORESET=1
- -DSQLITE_OMIT_LOAD_EXTENSION=1
- -DSQLITE_PREFER_PROXY_LOCKING=1
- -DSQLITE_SERIES_CONSTRAINT_VERIFY=1
- -DSQLITE_THREADSAFE=2
- -DSQLITE_USE_URI=1
- -DSQLITE_WRITE_WALFRAME_PREBUFFERED=1
- -DUSE_GUARDED_FD=1
- -DUSE_PREAD=1
- --enable-fts5
- }
- "Extra-Robustness" {
- -DSQLITE_ENABLE_OVERSIZE_CELL_CHECK=1
- -DSQLITE_MAX_ATTACHED=62
- }
- "Devkit" {
- -DSQLITE_DEFAULT_FILE_FORMAT=4
- -DSQLITE_MAX_ATTACHED=30
- -DSQLITE_ENABLE_COLUMN_METADATA
- -DSQLITE_ENABLE_FTS4
- -DSQLITE_ENABLE_FTS5
- -DSQLITE_ENABLE_FTS4_PARENTHESIS
- -DSQLITE_DISABLE_FTS4_DEFERRED
- -DSQLITE_ENABLE_RTREE
- --enable-fts5
- }
- "No-lookaside" {
- -DSQLITE_TEST_REALLOC_STRESS=1
- -DSQLITE_OMIT_LOOKASIDE=1
- }
- "Valgrind" {
- -DSQLITE_ENABLE_STAT4
- -DSQLITE_ENABLE_FTS4
- -DSQLITE_ENABLE_RTREE
- -DSQLITE_ENABLE_HIDDEN_COLUMNS
- -DLONGDOUBLE_TYPE=double
- -DCONFIG_SLOWDOWN_FACTOR=8.0
- }
-
- "Windows-Memdebug" {
- MEMDEBUG=1
- DEBUG=3
- }
- "Windows-Win32Heap" {
- WIN32HEAP=1
- DEBUG=4
- }
-
- # The next group of configurations are used only by the
- # Failure-Detection platform. They are all the same, but we need
- # different names for them all so that they results appear in separate
- # subdirectories.
- #
- Fail0 {-O0}
- Fail2 {-O0}
- Fail3 {-O0}
- Fail4 {-O0}
- FuzzFail1 {-O0}
- FuzzFail2 {-O0}
-}]
-if {$tcl_platform(os)=="Darwin"} {
- lappend Configs(Apple) -DSQLITE_ENABLE_LOCKING_STYLE=1
-}
-
-array set ::Platforms [strip_comments {
- Linux-x86_64 {
- "Check-Symbols*" "" checksymbols
- "Fast-One" QUICKTEST_INCLUDE=rbu.test "fuzztest test"
- "Debug-One" "" "mptest test"
- "Debug-Two" "" test
- "Have-Not" "" test
- "Secure-Delete" "" test
- "Unlock-Notify" QUICKTEST_INCLUDE=notify2.test test
- "User-Auth" "" tcltest
- "Update-Delete-Limit" "" test
- "Extra-Robustness" "" test
- "Device-Two" "" "threadtest test"
- "No-lookaside" "" test
- "Devkit" "" test
- "Apple" "" test
- "Sanitize*" "" test
- "Device-One" "" alltest
- "Default" "" "threadtest fuzztest alltest"
- "Valgrind*" "" valgrindtest
- }
- Linux-i686 {
- "Devkit" "" test
- "Have-Not" "" test
- "Unlock-Notify" QUICKTEST_INCLUDE=notify2.test test
- "Device-One" "" test
- "Device-Two" "" test
- "Default" "" "threadtest fuzztest alltest"
- }
- Darwin-i386 {
- "Locking-Style" "" "mptest test"
- "Have-Not" "" test
- "Apple" "" "threadtest fuzztest alltest"
- }
- Darwin-x86_64 {
- "Locking-Style" "" "mptest test"
- "Have-Not" "" test
- "Apple" "" "threadtest fuzztest alltest"
- }
- Darwin-arm64 {
- "Locking-Style" "" "mptest test"
- "Have-Not" "" test
- "Apple" "" "threadtest fuzztest alltest"
- }
- "Windows NT-intel" {
- "Stdcall" "" test
- "Have-Not" "" test
- "Windows-Memdebug*" "" test
- "Windows-Win32Heap*" "" test
- "Default" "" "mptest fulltestonly"
- }
- "Windows NT-amd64" {
- "Stdcall" "" test
- "Have-Not" "" test
- "Windows-Memdebug*" "" test
- "Windows-Win32Heap*" "" test
- "Default" "" "mptest fulltestonly"
- }
-
- # The Failure-Detection platform runs various tests that deliberately
- # fail. This is used as a test of this script to verify that this script
- # correctly identifies failures.
- #
- Failure-Detection {
- Fail0* "TEST_FAILURE=0" test
- Sanitize* "TEST_FAILURE=1" test
- Fail2* "TEST_FAILURE=2" valgrindtest
- Fail3* "TEST_FAILURE=3" valgrindtest
- Fail4* "TEST_FAILURE=4" test
- FuzzFail1* "TEST_FAILURE=5" test
- FuzzFail2* "TEST_FAILURE=5" valgrindtest
- }
-}]
-
-#--------------------------------------------------------------------------
-#--------------------------------------------------------------------------
-#--------------------------------------------------------------------------
-# End of configuration section.
-#--------------------------------------------------------------------------
-#--------------------------------------------------------------------------
-#--------------------------------------------------------------------------
-
-# Configuration verification: Check that each entry in the list of configs
-# specified for each platforms exists.
-#
-foreach {key value} [array get ::Platforms] {
- foreach {v vars t} $value {
- if {[string range $v end end]=="*"} {
- set v [string range $v 0 end-1]
- }
- if {0==[info exists ::Configs($v)]} {
- puts stderr "No such configuration: \"$v\""
- exit -1
- }
- }
-}
-
-proc usage {} {
- global argv0
- puts stderr [subst $::USAGE]
- exit 1
-}
-
-proc is_prefix {p str min} {
- set n [string length $p]
- if {$n<$min} { return 0 }
- if {[string range $str 0 [expr $n-1]]!=$p} { return 0 }
- return 1
-}
-
-proc main_configurations {} {
- foreach k [lsort [array names ::Configs]] {
- puts $k
- }
-}
-
-proc main_platforms {} {
- foreach k [lsort [array names ::Platforms]] {
- puts "\"$k\""
- }
-}
-
-proc main_script {args} {
- set bMsvc 0
- set nArg [llength $args]
- if {$nArg==3} {
- if {![is_prefix [lindex $args 0] -msvc 2]} usage
- set bMsvc 1
- } elseif {$nArg<2 || $nArg>3} {
- usage
- }
- set config [lindex $args end-1]
- set target [lindex $args end]
-
- set opts [list] ;# OPTS value
- set cflags [expr {$bMsvc ? "-Zi" : "-g"}] ;# CFLAGS value
- set makeOpts [list] ;# Extra args for [make]
- set configOpts [list] ;# Extra args for [configure]
-
- if {$::tcl_platform(platform)=="windows" || $bMsvc} {
- lappend opts -DSQLITE_OS_WIN=1
- } else {
- lappend opts -DSQLITE_OS_UNIX=1
- }
-
- # Figure out if this is a synthetic ndebug or debug configuration.
- #
- set bRemoveDebug 0
- if {[string match *-ndebug $config]} {
- set bRemoveDebug 1
- set config [string range $config 0 end-7]
- }
- if {[string match *-debug $config]} {
- lappend opts -DSQLITE_DEBUG
- lappend opts -DSQLITE_EXTRA_IFNULLROW
- set config [string range $config 0 end-6]
- }
- regexp {^(.*)-[0-9]+} $config -> config
-
- # Ensure that the named configuration exists.
- #
- if {![info exists ::Configs($config)]} {
- puts stderr "No such config: $config"
- exit 1
- }
-
- # Loop through the parameters of the nominated configuration, updating
- # $opts, $cflags, $makeOpts and $configOpts along the way. Rules are as
- # follows:
- #
- # 1. If the parameter begins with a "*", discard it.
- #
- # 2. If $bRemoveDebug is set and the parameter is -DSQLITE_DEBUG or
- # -DSQLITE_DEBUG=1, discard it
- #
- # 3. If the parameter begins with "-D", add it to $opts.
- #
- # 4. If the parameter begins with "--" add it to $configOpts. Unless
- # this command is preparing a script for MSVC - then add an
- # equivalent to $makeOpts or $opts.
- #
- # 5. If the parameter begins with "-" add it to $cflags. If in MSVC
- # mode and the parameter is an -O<integer> option, instead add
- # an OPTIMIZATIONS=<integer> switch to $makeOpts.
- #
- # 6. If none of the above apply, add the parameter to $makeOpts
- #
- foreach param $::Configs($config) {
- if {[string range $param 0 0]=="*"} continue
-
- if {$bRemoveDebug} {
- if {$param=="-DSQLITE_DEBUG" || $param=="-DSQLITE_DEBUG=1"
- || $param=="-DSQLITE_MEMDEBUG" || $param=="-DSQLITE_MEMDEBUG=1"
- || $param=="--enable-debug"
- } {
- continue
- }
- }
-
- if {[string range $param 0 1]=="-D"} {
- lappend opts $param
- continue
- }
-
- if {[string range $param 0 1]=="--"} {
- if {$bMsvc} {
- switch -- $param {
- --disable-amalgamation {
- lappend makeOpts USE_AMALGAMATION=0
- }
- --disable-shared {
- lappend makeOpts USE_CRT_DLL=0 DYNAMIC_SHELL=0
- }
- --enable-fts5 {
- lappend opts -DSQLITE_ENABLE_FTS5
- }
- --enable-shared {
- lappend makeOpts USE_CRT_DLL=1 DYNAMIC_SHELL=1
- }
- --enable-session {
- lappend opts -DSQLITE_ENABLE_PREUPDATE_HOOK
- lappend opts -DSQLITE_ENABLE_SESSION
- }
- default {
- error "Cannot translate $param for MSVC"
- }
- }
- } else {
- lappend configOpts $param
- }
-
- continue
- }
-
- if {[string range $param 0 0]=="-"} {
- if {$bMsvc && [regexp -- {^-O(\d+)$} $param -> level]} {
- lappend makeOpts OPTIMIZATIONS=$level
- } else {
- lappend cflags $param
- }
- continue
- }
-
- lappend makeOpts $param
- }
-
- # Some configurations specify -DHAVE_USLEEP=0. For all others, add
- # -DHAVE_USLEEP=1.
- #
- if {[lsearch $opts "-DHAVE_USLEEP=0"]<0} {
- lappend opts -DHAVE_USLEEP=1
- }
-
- if {$bMsvc==0} {
- puts {set -e}
- puts {}
- puts {if [ "$#" -ne 1 ] ; then}
- puts { echo "Usage: $0 <sqlite-src-dir>" }
- puts { exit -1 }
- puts {fi }
- puts {SRCDIR=$1}
- puts {}
- puts "TCL=\"[::tcl::pkgconfig get libdir,install]\""
-
- puts "\$SRCDIR/configure --with-tcl=\$TCL $configOpts"
- puts {}
- puts {OPTS=" -DSQLITE_NO_SYNC=1"}
- foreach o $opts {
- puts "OPTS=\"\$OPTS $o\""
- }
- puts {}
- puts "CFLAGS=\"$cflags\""
- puts {}
- puts "make $target \"CFLAGS=\$CFLAGS\" \"OPTS=\$OPTS\" $makeOpts"
- } else {
-
- puts {set SRCDIR=%1}
- set makecmd "nmake /f %SRCDIR%\\Makefile.msc TOP=%SRCDIR% $target "
- append makecmd "\"CFLAGS=$cflags\" \"OPTS=$opts\" $makeOpts"
-
- puts "set TMP=%CD%"
- puts $makecmd
- }
-}
-
-proc main_trscript {args} {
- set bMsvc 0
- set nArg [llength $args]
- if {$nArg==3} {
- if {![is_prefix [lindex $args 0] -msvc 2]} usage
- set bMsvc 1
- } elseif {$nArg<2 || $nArg>3} {
- usage
- }
- set config [lindex $args end-1]
- set srcdir [lindex $args end]
-
- set opts [list] ;# OPTS value
- set cflags [expr {$bMsvc ? "-Zi" : "-g"}] ;# CFLAGS value
- set makeOpts [list] ;# Extra args for [make]
- set configOpts [list] ;# Extra args for [configure]
-
- if {$::tcl_platform(platform)=="windows" || $bMsvc} {
- lappend opts -DSQLITE_OS_WIN=1
- } else {
- lappend opts -DSQLITE_OS_UNIX=1
- }
-
- # Figure out if this is a synthetic ndebug or debug configuration.
- #
- set bRemoveDebug 0
- if {[string match *-ndebug $config]} {
- set bRemoveDebug 1
- set config [string range $config 0 end-7]
- }
- if {[string match *-debug $config]} {
- lappend opts -DSQLITE_DEBUG
- lappend opts -DSQLITE_EXTRA_IFNULLROW
- set config [string range $config 0 end-6]
- }
- regexp {^(.*)-[0-9]+} $config -> config
-
- # Ensure that the named configuration exists.
- #
- if {![info exists ::Configs($config)]} {
- puts stderr "No such config: $config"
- exit 1
- }
-
- # Loop through the parameters of the nominated configuration, updating
- # $opts, $cflags, $makeOpts and $configOpts along the way. Rules are as
- # follows:
- #
- # 1. If the parameter begins with a "*", discard it.
- #
- # 2. If $bRemoveDebug is set and the parameter is -DSQLITE_DEBUG or
- # -DSQLITE_DEBUG=1, discard it
- #
- # 3. If the parameter begins with "-D", add it to $opts.
- #
- # 4. If the parameter begins with "--" add it to $configOpts. Unless
- # this command is preparing a script for MSVC - then add an
- # equivalent to $makeOpts or $opts.
- #
- # 5. If the parameter begins with "-" add it to $cflags. If in MSVC
- # mode and the parameter is an -O<integer> option, instead add
- # an OPTIMIZATIONS=<integer> switch to $makeOpts.
- #
- # 6. If none of the above apply, add the parameter to $makeOpts
- #
- foreach param $::Configs($config) {
- if {[string range $param 0 0]=="*"} continue
-
- if {$bRemoveDebug} {
- if {$param=="-DSQLITE_DEBUG" || $param=="-DSQLITE_DEBUG=1"
- || $param=="-DSQLITE_MEMDEBUG" || $param=="-DSQLITE_MEMDEBUG=1"
- || $param=="--enable-debug"
- } {
- continue
- }
- }
-
- if {[string range $param 0 1]=="-D"} {
- lappend opts $param
- continue
- }
-
- if {[string range $param 0 1]=="--"} {
- if {$bMsvc} {
- switch -- $param {
- --disable-amalgamation {
- lappend makeOpts USE_AMALGAMATION=0
- }
- --disable-shared {
- lappend makeOpts USE_CRT_DLL=0 DYNAMIC_SHELL=0
- }
- --enable-fts5 {
- lappend opts -DSQLITE_ENABLE_FTS5
- }
- --enable-shared {
- lappend makeOpts USE_CRT_DLL=1 DYNAMIC_SHELL=1
- }
- --enable-session {
- lappend opts -DSQLITE_ENABLE_PREUPDATE_HOOK
- lappend opts -DSQLITE_ENABLE_SESSION
- }
- --enable-all {
- }
- --enable-debug {
- # lappend makeOpts OPTIMIZATIONS=0
- lappend opts -DSQLITE_DEBUG
- }
- default {
- error "Cannot translate $param for MSVC"
- }
- }
- } else {
- lappend configOpts $param
- }
-
- continue
- }
-
- if {[string range $param 0 0]=="-"} {
- if {$bMsvc && [regexp -- {^-O(\d+)$} $param -> level]} {
- lappend makeOpts OPTIMIZATIONS=$level
- } else {
- lappend cflags $param
- }
- continue
- }
-
- lappend makeOpts $param
- }
-
- # Some configurations specify -DHAVE_USLEEP=0. For all others, add
- # -DHAVE_USLEEP=1.
- #
- if {[lsearch $opts "-DHAVE_USLEEP=0"]<0} {
- lappend opts -DHAVE_USLEEP=1
- }
-
- if {$bMsvc==0} {
- puts {set -e}
- puts {}
- puts {if [ "$#" -ne 1 ] ; then}
- puts { echo "Usage: $0 <target>" }
- puts { exit -1 }
- puts {fi }
- puts "SRCDIR=\"$srcdir\""
- puts {}
- puts "TCL=\"[::tcl::pkgconfig get libdir,install]\""
-
- puts {if [ ! -f Makefile ] ; then}
- puts " \$SRCDIR/configure --with-tcl=\$TCL $configOpts"
- puts {fi}
- puts {}
- if {[info exists ::env(OPTS)]} {
- puts "# From environment variable:"
- puts "OPTS=$::env(OPTS)"
- puts ""
- }
- puts {OPTS="$OPTS -DSQLITE_NO_SYNC=1"}
- foreach o $opts {
- puts "OPTS=\"\$OPTS $o\""
- }
- puts {}
- puts "CFLAGS=\"$cflags\""
- puts {}
- puts "make \$1 \"CFLAGS=\$CFLAGS\" \"OPTS=\$OPTS\" $makeOpts"
- } else {
-
- set srcdir [file nativename [file normalize $srcdir]]
- # set srcdir [string map [list "\\" "\\\\"] $srcdir]
-
- puts {set TARGET=%1}
- set makecmd "nmake /f $srcdir\\Makefile.msc TOP=\"$srcdir\" %TARGET% "
- append makecmd "\"CFLAGS=$cflags\" \"OPTS=$opts\" $makeOpts"
-
- puts "set TMP=%CD%"
- puts $makecmd
- }
-}
-
-proc main_tests {args} {
- set bNodebug 0
- set nArg [llength $args]
- if {$nArg==2} {
- if {[is_prefix [lindex $args 0] -nodebug 2]} {
- set bNodebug 1
- } elseif {[is_prefix [lindex $args 0] -debug 2]} {
- set bNodebug 0
- } else usage
- } elseif {$nArg==0 || $nArg>2} {
- usage
- }
- set p [lindex $args end]
- if {![info exists ::Platforms($p)]} {
- puts stderr "No such platform: $p"
- exit 1
- }
-
- set lTest [list]
-
- foreach {config vars target} $::Platforms($p) {
- if {[string range $config end end]=="*"} {
- set config [string range $config 0 end-1]
- } elseif {$bNodebug==0} {
- set dtarget test
- if {[lsearch $target fuzztest]<0 && [lsearch $target test]<0} {
- set dtarget tcltest
- }
- if {$vars!=""} { set dtarget "$vars $dtarget" }
-
- if {[string first SQLITE_DEBUG $::Configs($config)]>=0
- || [string first --enable-debug $::Configs($config)]>=0
- } {
- lappend lTest "$config-ndebug \"$dtarget\""
- } else {
- lappend lTest "$config-debug \"$dtarget\""
- }
- }
-
- if {[llength $target]==1 && ([string match "*TEST_FAILURE*" $vars] || (
- [lsearch $target "valgrindtest"]<0
- && [lsearch $target "alltest"]<0
- && [lsearch $target "fulltestonly"]<0
- && ![string match Sanitize* $config]
- ))} {
- if {$vars!=""} { set target "$vars $target" }
- lappend lTest "$config \"$target\""
- } else {
- set idir -1
- foreach t $target {
- if {$t=="valgrindtest" || $t=="alltest" || $t=="fulltestonly"
- || [string match Sanitize* $config]
- } {
- if {$vars!=""} { set t "$vars $t" }
- for {set ii 1} {$ii<=4} {incr ii} {
- lappend lTest "$config-[incr idir] \"TCLTEST_PART=$ii/4 $t\""
- }
- } else {
- if {$vars!=""} { set t "$vars $t" }
- lappend lTest "$config-[incr idir] \"$t\""
- }
- }
- }
- }
-
- foreach l $lTest {
- puts $l
- }
-
-}
-
-if {[llength $argv]==0} { usage }
-set cmd [lindex $argv 0]
-set n [expr [llength $argv]-1]
-if {[string match ${cmd}* configurations] && $n==0} {
- main_configurations
-} elseif {[string match ${cmd}* script]} {
- main_script {*}[lrange $argv 1 end]
-} elseif {[string match ${cmd}* trscript]} {
- main_trscript {*}[lrange $argv 1 end]
-} elseif {[string match ${cmd}* platforms] && $n==0} {
- main_platforms
-} elseif {[string match ${cmd}* tests]} {
- main_tests {*}[lrange $argv 1 end]
-} else {
- usage
-}
diff --git a/test/snapshot_up.test b/test/snapshot_up.test
index de8e5afab..5f389e7be 100644
--- a/test/snapshot_up.test
+++ b/test/snapshot_up.test
@@ -27,6 +27,8 @@ if {[permutation]=="inmemory_journal"} {
return
}
+db timeout 1000
+
do_execsql_test 1.0 {
CREATE TABLE t1(a, b, c);
PRAGMA journal_mode = wal;
diff --git a/test/testrunner_data.tcl b/test/testrunner_data.tcl
index 9032ced4d..c4e24c438 100644
--- a/test/testrunner_data.tcl
+++ b/test/testrunner_data.tcl
@@ -267,6 +267,7 @@ namespace eval trd {
-DSQLITE_ENABLE_PERSIST_WAL=1
-DSQLITE_ENABLE_PURGEABLE_PCACHE=1
-DSQLITE_ENABLE_RTREE=1
+ -DSQLITE_ENABLE_SETLK_TIMEOUT=2
-DSQLITE_ENABLE_SNAPSHOT=1
-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1
-DSQLITE_MAX_LENGTH=2147483645
diff --git a/test/wapp.tcl b/test/wapp.tcl
deleted file mode 100644
index 53c21e892..000000000
--- a/test/wapp.tcl
+++ /dev/null
@@ -1,987 +0,0 @@
-# Copyright (c) 2017 D. Richard Hipp
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the Simplified BSD License (also
-# known as the "2-Clause License" or "FreeBSD License".)
-#
-# This program is distributed in the hope that it will be useful,
-# but without any warranty; without even the implied warranty of
-# merchantability or fitness for a particular purpose.
-#
-#---------------------------------------------------------------------------
-#
-# Design rules:
-#
-# (1) All identifiers in the global namespace begin with "wapp"
-#
-# (2) Indentifiers intended for internal use only begin with "wappInt"
-#
-package require Tcl 8.6
-
-# Add text to the end of the HTTP reply. No interpretation or transformation
-# of the text is performs. The argument should be enclosed within {...}
-#
-proc wapp {txt} {
- global wapp
- dict append wapp .reply $txt
-}
-
-# Add text to the page under construction. Do no escaping on the text.
-#
-# Though "unsafe" in general, there are uses for this kind of thing.
-# For example, if you want to return the complete, unmodified content of
-# a file:
-#
-# set fd [open content.html rb]
-# wapp-unsafe [read $fd]
-# close $fd
-#
-# You could do the same thing using ordinary "wapp" instead of "wapp-unsafe".
-# The difference is that wapp-safety-check will complain about the misuse
-# of "wapp", but it assumes that the person who write "wapp-unsafe" understands
-# the risks.
-#
-# Though occasionally necessary, the use of this interface should be minimized.
-#
-proc wapp-unsafe {txt} {
- global wapp
- dict append wapp .reply $txt
-}
-
-# Add text to the end of the reply under construction. The following
-# substitutions are made:
-#
-# %html(...) Escape text for inclusion in HTML
-# %url(...) Escape text for use as a URL
-# %qp(...) Escape text for use as a URI query parameter
-# %string(...) Escape text for use within a JSON string
-# %unsafe(...) No transformations of the text
-#
-# The substitutions above terminate at the first ")" character. If the
-# text of the TCL string in ... contains ")" characters itself, use instead:
-#
-# %html%(...)%
-# %url%(...)%
-# %qp%(...)%
-# %string%(...)%
-# %unsafe%(...)%
-#
-# In other words, use "%(...)%" instead of "(...)" to include the TCL string
-# to substitute.
-#
-# The %unsafe substitution should be avoided whenever possible, obviously.
-# In addition to the substitutions above, the text also does backslash
-# escapes.
-#
-# The wapp-trim proc works the same as wapp-subst except that it also removes
-# whitespace from the left margin, so that the generated HTML/CSS/Javascript
-# does not appear to be indented when delivered to the client web browser.
-#
-if {$tcl_version>=8.7} {
- proc wapp-subst {txt} {
- global wapp
- regsub -all -command \
- {%(html|url|qp|string|unsafe){1,1}?(|%)\((.+)\)\2} $txt wappInt-enc txt
- dict append wapp .reply [subst -novariables -nocommand $txt]
- }
- proc wapp-trim {txt} {
- global wapp
- regsub -all {\n\s+} [string trim $txt] \n txt
- regsub -all -command \
- {%(html|url|qp|string|unsafe){1,1}?(|%)\((.+)\)\2} $txt wappInt-enc txt
- dict append wapp .reply [subst -novariables -nocommand $txt]
- }
- proc wappInt-enc {all mode nu1 txt} {
- return [uplevel 2 "wappInt-enc-$mode \"$txt\""]
- }
-} else {
- proc wapp-subst {txt} {
- global wapp
- regsub -all {%(html|url|qp|string|unsafe){1,1}?(|%)\((.+)\)\2} $txt \
- {[wappInt-enc-\1 "\3"]} txt
- dict append wapp .reply [uplevel 1 [list subst -novariables $txt]]
- }
- proc wapp-trim {txt} {
- global wapp
- regsub -all {\n\s+} [string trim $txt] \n txt
- regsub -all {%(html|url|qp|string|unsafe){1,1}?(|%)\((.+)\)\2} $txt \
- {[wappInt-enc-\1 "\3"]} txt
- dict append wapp .reply [uplevel 1 [list subst -novariables $txt]]
- }
-}
-
-# There must be a wappInt-enc-NAME routine for each possible substitution
-# in wapp-subst. Thus there are routines for "html", "url", "qp", and "unsafe".
-#
-# wappInt-enc-html Escape text so that it is safe to use in the
-# body of an HTML document.
-#
-# wappInt-enc-url Escape text so that it is safe to pass as an
-# argument to href= and src= attributes in HTML.
-#
-# wappInt-enc-qp Escape text so that it is safe to use as the
-# value of a query parameter in a URL or in
-# post data or in a cookie.
-#
-# wappInt-enc-string Escape ", ', \, and < for using inside of a
-# javascript string literal. The < character
-# is escaped to prevent "</script>" from causing
-# problems in embedded javascript.
-#
-# wappInt-enc-unsafe Perform no encoding at all. Unsafe.
-#
-proc wappInt-enc-html {txt} {
- return [string map {& &amp; < &lt; > &gt; \" &quot; \\ &#92;} $txt]
-}
-proc wappInt-enc-unsafe {txt} {
- return $txt
-}
-proc wappInt-enc-url {s} {
- if {[regsub -all {[^-{}@~?=#_.:/a-zA-Z0-9]} $s {[wappInt-%HHchar {&}]} s]} {
- set s [subst -novar -noback $s]
- }
- if {[regsub -all {[{}]} $s {[wappInt-%HHchar \\&]} s]} {
- set s [subst -novar -noback $s]
- }
- return $s
-}
-proc wappInt-enc-qp {s} {
- if {[regsub -all {[^-{}_.a-zA-Z0-9]} $s {[wappInt-%HHchar {&}]} s]} {
- set s [subst -novar -noback $s]
- }
- if {[regsub -all {[{}]} $s {[wappInt-%HHchar \\&]} s]} {
- set s [subst -novar -noback $s]
- }
- return $s
-}
-proc wappInt-enc-string {s} {
- return [string map {\\ \\\\ \" \\\" ' \\' < \\u003c} $s]
-}
-
-# This is a helper routine for wappInt-enc-url and wappInt-enc-qp. It returns
-# an appropriate %HH encoding for the single character c. If c is a unicode
-# character, then this routine might return multiple bytes: %HH%HH%HH
-#
-proc wappInt-%HHchar {c} {
- if {$c==" "} {return +}
- return [regsub -all .. [binary encode hex [encoding convertto utf-8 $c]] {%&}]
-}
-
-
-# Undo the www-url-encoded format.
-#
-# HT: This code stolen from ncgi.tcl
-#
-proc wappInt-decode-url {str} {
- set str [string map [list + { } "\\" "\\\\" \[ \\\[ \] \\\]] $str]
- regsub -all -- \
- {%([Ee][A-Fa-f0-9])%([89ABab][A-Fa-f0-9])%([89ABab][A-Fa-f0-9])} \
- $str {[encoding convertfrom utf-8 [binary decode hex \1\2\3]]} str
- regsub -all -- \
- {%([CDcd][A-Fa-f0-9])%([89ABab][A-Fa-f0-9])} \
- $str {[encoding convertfrom utf-8 [binary decode hex \1\2]]} str
- regsub -all -- {%([0-7][A-Fa-f0-9])} $str {\\u00\1} str
- return [subst -novar $str]
-}
-
-# Reset the document back to an empty string.
-#
-proc wapp-reset {} {
- global wapp
- dict set wapp .reply {}
-}
-
-# Change the mime-type of the result document.
-#
-proc wapp-mimetype {x} {
- global wapp
- dict set wapp .mimetype $x
-}
-
-# Change the reply code.
-#
-proc wapp-reply-code {x} {
- global wapp
- dict set wapp .reply-code $x
-}
-
-# Set a cookie
-#
-proc wapp-set-cookie {name value} {
- global wapp
- dict lappend wapp .new-cookies $name $value
-}
-
-# Unset a cookie
-#
-proc wapp-clear-cookie {name} {
- wapp-set-cookie $name {}
-}
-
-# Add extra entries to the reply header
-#
-proc wapp-reply-extra {name value} {
- global wapp
- dict lappend wapp .reply-extra $name $value
-}
-
-# Specifies how the web-page under construction should be cached.
-# The argument should be one of:
-#
-# no-cache
-# max-age=N (for some integer number of seconds, N)
-# private,max-age=N
-#
-proc wapp-cache-control {x} {
- wapp-reply-extra Cache-Control $x
-}
-
-# Redirect to a different web page
-#
-proc wapp-redirect {uri} {
- wapp-reply-code {307 Redirect}
- wapp-reply-extra Location $uri
-}
-
-# Return the value of a wapp parameter
-#
-proc wapp-param {name {dflt {}}} {
- global wapp
- if {![dict exists $wapp $name]} {return $dflt}
- return [dict get $wapp $name]
-}
-
-# Return true if a and only if the wapp parameter $name exists
-#
-proc wapp-param-exists {name} {
- global wapp
- return [dict exists $wapp $name]
-}
-
-# Set the value of a wapp parameter
-#
-proc wapp-set-param {name value} {
- global wapp
- dict set wapp $name $value
-}
-
-# Return all parameter names that match the GLOB pattern, or all
-# names if the GLOB pattern is omitted.
-#
-proc wapp-param-list {{glob {*}}} {
- global wapp
- return [dict keys $wapp $glob]
-}
-
-# By default, Wapp does not decode query parameters and POST parameters
-# for cross-origin requests. This is a security restriction, designed to
-# help prevent cross-site request forgery (CSRF) attacks.
-#
-# As a consequence of this restriction, URLs for sites generated by Wapp
-# that contain query parameters will not work as URLs found in other
-# websites. You cannot create a link from a second website into a Wapp
-# website if the link contains query planner, by default.
-#
-# Of course, it is sometimes desirable to allow query parameters on external
-# links. For URLs for which this is safe, the application should invoke
-# wapp-allow-xorigin-params. This procedure tells Wapp that it is safe to
-# go ahead and decode the query parameters even for cross-site requests.
-#
-# In other words, for Wapp security is the default setting. Individual pages
-# need to actively disable the cross-site request security if those pages
-# are safe for cross-site access.
-#
-proc wapp-allow-xorigin-params {} {
- global wapp
- if {![dict exists $wapp .qp] && ![dict get $wapp SAME_ORIGIN]} {
- wappInt-decode-query-params
- }
-}
-
-# Set the content-security-policy.
-#
-# The default content-security-policy is very strict: "default-src 'self'"
-# The default policy prohibits the use of in-line javascript or CSS.
-#
-# Provide an alternative CSP as the argument. Or use "off" to disable
-# the CSP completely.
-#
-proc wapp-content-security-policy {val} {
- global wapp
- if {$val=="off"} {
- dict unset wapp .csp
- } else {
- dict set wapp .csp $val
- }
-}
-
-# Examine the bodys of all procedures in this program looking for
-# unsafe calls to various Wapp interfaces. Return a text string
-# containing warnings. Return an empty string if all is ok.
-#
-# This routine is advisory only. It misses some constructs that are
-# dangerous and flags others that are safe.
-#
-proc wapp-safety-check {} {
- set res {}
- foreach p [info procs] {
- set ln 0
- foreach x [split [info body $p] \n] {
- incr ln
- if {[regexp {^[ \t]*wapp[ \t]+([^\n]+)} $x all tail]
- && [string index $tail 0]!="\173"
- && [regexp {[[$]} $tail]
- } {
- append res "$p:$ln: unsafe \"wapp\" call: \"[string trim $x]\"\n"
- }
- if {[regexp {^[ \t]*wapp-(subst|trim)[ \t]+[^\173]} $x all cx]} {
- append res "$p:$ln: unsafe \"wapp-$cx\" call: \"[string trim $x]\"\n"
- }
- }
- }
- return $res
-}
-
-# Return a string that descripts the current environment. Applications
-# might find this useful for debugging.
-#
-proc wapp-debug-env {} {
- global wapp
- set out {}
- foreach var [lsort [dict keys $wapp]] {
- if {[string index $var 0]=="."} continue
- append out "$var = [list [dict get $wapp $var]]\n"
- }
- append out "\[pwd\] = [list [pwd]]\n"
- return $out
-}
-
-# Tracing function for each HTTP request. This is overridden by wapp-start
-# if tracing is enabled.
-#
-proc wappInt-trace {} {}
-
-# Start up a listening socket. Arrange to invoke wappInt-new-connection
-# for each inbound HTTP connection.
-#
-# port Listen on this TCP port. 0 means to select a port
-# that is not currently in use
-#
-# wappmode One of "scgi", "remote-scgi", "server", or "local".
-#
-# fromip If not {}, then reject all requests from IP addresses
-# other than $fromip
-#
-proc wappInt-start-listener {port wappmode fromip} {
- if {[string match *scgi $wappmode]} {
- set type SCGI
- set server [list wappInt-new-connection \
- wappInt-scgi-readable $wappmode $fromip]
- } else {
- set type HTTP
- set server [list wappInt-new-connection \
- wappInt-http-readable $wappmode $fromip]
- }
- if {$wappmode=="local" || $wappmode=="scgi"} {
- set x [socket -server $server -myaddr 127.0.0.1 $port]
- } else {
- set x [socket -server $server $port]
- }
- set coninfo [chan configure $x -sockname]
- set port [lindex $coninfo 2]
- if {$wappmode=="local"} {
- wappInt-start-browser http://127.0.0.1:$port/
- } elseif {$fromip!=""} {
- puts "Listening for $type requests on TCP port $port from IP $fromip"
- } else {
- puts "Listening for $type requests on TCP port $port"
- }
-}
-
-# Start a web-browser and point it at $URL
-#
-proc wappInt-start-browser {url} {
- global tcl_platform
- if {$tcl_platform(platform)=="windows"} {
- exec cmd /c start $url &
- } elseif {$tcl_platform(os)=="Darwin"} {
- exec open $url &
- } elseif {[catch {exec xdg-open $url}]} {
- exec firefox $url &
- }
-}
-
-# This routine is a "socket -server" callback. The $chan, $ip, and $port
-# arguments are added by the socket command.
-#
-# Arrange to invoke $callback when content is available on the new socket.
-# The $callback will process inbound HTTP or SCGI content. Reject the
-# request if $fromip is not an empty string and does not match $ip.
-#
-proc wappInt-new-connection {callback wappmode fromip chan ip port} {
- upvar #0 wappInt-$chan W
- if {$fromip!="" && ![string match $fromip $ip]} {
- close $chan
- return
- }
- set W [dict create REMOTE_ADDR $ip REMOTE_PORT $port WAPP_MODE $wappmode \
- .header {}]
- fconfigure $chan -blocking 0 -translation binary
- fileevent $chan readable [list $callback $chan]
-}
-
-# Close an input channel
-#
-proc wappInt-close-channel {chan} {
- if {$chan=="stdout"} {
- # This happens after completing a CGI request
- exit 0
- } else {
- unset ::wappInt-$chan
- close $chan
- }
-}
-
-# Process new text received on an inbound HTTP request
-#
-proc wappInt-http-readable {chan} {
- if {[catch [list wappInt-http-readable-unsafe $chan] msg]} {
- puts stderr "$msg\n$::errorInfo"
- wappInt-close-channel $chan
- }
-}
-proc wappInt-http-readable-unsafe {chan} {
- upvar #0 wappInt-$chan W wapp wapp
- if {![dict exists $W .toread]} {
- # If the .toread key is not set, that means we are still reading
- # the header
- set line [string trimright [gets $chan]]
- set n [string length $line]
- if {$n>0} {
- if {[dict get $W .header]=="" || [regexp {^\s+} $line]} {
- dict append W .header $line
- } else {
- dict append W .header \n$line
- }
- if {[string length [dict get $W .header]]>100000} {
- error "HTTP request header too big - possible DOS attack"
- }
- } elseif {$n==0} {
- # We have reached the blank line that terminates the header.
- global argv0
- set a0 [file normalize $argv0]
- dict set W SCRIPT_FILENAME $a0
- dict set W DOCUMENT_ROOT [file dir $a0]
- if {[wappInt-parse-header $chan]} {
- catch {close $chan}
- return
- }
- set len 0
- if {[dict exists $W CONTENT_LENGTH]} {
- set len [dict get $W CONTENT_LENGTH]
- }
- if {$len>0} {
- # Still need to read the query content
- dict set W .toread $len
- } else {
- # There is no query content, so handle the request immediately
- set wapp $W
- wappInt-handle-request $chan 0
- }
- }
- } else {
- # If .toread is set, that means we are reading the query content.
- # Continue reading until .toread reaches zero.
- set got [read $chan [dict get $W .toread]]
- dict append W CONTENT $got
- dict set W .toread [expr {[dict get $W .toread]-[string length $got]}]
- if {[dict get $W .toread]<=0} {
- # Handle the request as soon as all the query content is received
- set wapp $W
- wappInt-handle-request $chan 0
- }
- }
-}
-
-# Decode the HTTP request header.
-#
-# This routine is always running inside of a [catch], so if
-# any problems arise, simply raise an error.
-#
-proc wappInt-parse-header {chan} {
- upvar #0 wappInt-$chan W
- set hdr [split [dict get $W .header] \n]
- if {$hdr==""} {return 1}
- set req [lindex $hdr 0]
- dict set W REQUEST_METHOD [set method [lindex $req 0]]
- if {[lsearch {GET HEAD POST} $method]<0} {
- error "unsupported request method: \"[dict get $W REQUEST_METHOD]\""
- }
- set uri [lindex $req 1]
- set split_uri [split $uri ?]
- set uri0 [lindex $split_uri 0]
- if {![regexp {^/[-.a-z0-9_/]*$} $uri0]} {
- error "invalid request uri: \"$uri0\""
- }
- dict set W REQUEST_URI $uri0
- dict set W PATH_INFO $uri0
- set uri1 [lindex $split_uri 1]
- dict set W QUERY_STRING $uri1
- set n [llength $hdr]
- for {set i 1} {$i<$n} {incr i} {
- set x [lindex $hdr $i]
- if {![regexp {^(.+): +(.*)$} $x all name value]} {
- error "invalid header line: \"$x\""
- }
- set name [string toupper $name]
- switch -- $name {
- REFERER {set name HTTP_REFERER}
- USER-AGENT {set name HTTP_USER_AGENT}
- CONTENT-LENGTH {set name CONTENT_LENGTH}
- CONTENT-TYPE {set name CONTENT_TYPE}
- HOST {set name HTTP_HOST}
- COOKIE {set name HTTP_COOKIE}
- ACCEPT-ENCODING {set name HTTP_ACCEPT_ENCODING}
- default {set name .hdr:$name}
- }
- dict set W $name $value
- }
- return 0
-}
-
-# Decode the QUERY_STRING parameters from a GET request or the
-# application/x-www-form-urlencoded CONTENT from a POST request.
-#
-# This routine sets the ".qp" element of the ::wapp dict as a signal
-# that query parameters have already been decoded.
-#
-proc wappInt-decode-query-params {} {
- global wapp
- dict set wapp .qp 1
- if {[dict exists $wapp QUERY_STRING]} {
- foreach qterm [split [dict get $wapp QUERY_STRING] &] {
- set qsplit [split $qterm =]
- set nm [lindex $qsplit 0]
- if {[regexp {^[a-z][a-z0-9]*$} $nm]} {
- dict set wapp $nm [wappInt-decode-url [lindex $qsplit 1]]
- }
- }
- }
- if {[dict exists $wapp CONTENT_TYPE] && [dict exists $wapp CONTENT]} {
- set ctype [dict get $wapp CONTENT_TYPE]
- if {$ctype=="application/x-www-form-urlencoded"} {
- foreach qterm [split [string trim [dict get $wapp CONTENT]] &] {
- set qsplit [split $qterm =]
- set nm [lindex $qsplit 0]
- if {[regexp {^[a-z][-a-z0-9_]*$} $nm]} {
- dict set wapp $nm [wappInt-decode-url [lindex $qsplit 1]]
- }
- }
- } elseif {[string match multipart/form-data* $ctype]} {
- regexp {^(.*?)\r\n(.*)$} [dict get $wapp CONTENT] all divider body
- set ndiv [string length $divider]
- while {[string length $body]} {
- set idx [string first $divider $body]
- set unit [string range $body 0 [expr {$idx-3}]]
- set body [string range $body [expr {$idx+$ndiv+2}] end]
- if {[regexp {^Content-Disposition: form-data; (.*?)\r\n\r\n(.*)$} \
- $unit unit hdr content]} {
- if {[regexp {name="(.*)"; filename="(.*)"\r\nContent-Type: (.*?)$}\
- $hdr hr name filename mimetype]} {
- dict set wapp $name.filename \
- [string map [list \\\" \" \\\\ \\] $filename]
- dict set wapp $name.mimetype $mimetype
- dict set wapp $name.content $content
- } elseif {[regexp {name="(.*)"} $hdr hr name]} {
- dict set wapp $name $content
- }
- }
- }
- }
- }
-}
-
-# Invoke application-supplied methods to generate a reply to
-# a single HTTP request.
-#
-# This routine always runs within [catch], so handle exceptions by
-# invoking [error].
-#
-proc wappInt-handle-request {chan useCgi} {
- global wapp
- dict set wapp .reply {}
- dict set wapp .mimetype {text/html; charset=utf-8}
- dict set wapp .reply-code {200 Ok}
- dict set wapp .csp {default-src 'self'}
-
- # Set up additional CGI environment values
- #
- if {![dict exists $wapp HTTP_HOST]} {
- dict set wapp BASE_URL {}
- } elseif {[dict exists $wapp HTTPS]} {
- dict set wapp BASE_URL https://[dict get $wapp HTTP_HOST]
- } else {
- dict set wapp BASE_URL http://[dict get $wapp HTTP_HOST]
- }
- if {![dict exists $wapp REQUEST_URI]} {
- dict set wapp REQUEST_URI /
- } elseif {[regsub {\?.*} [dict get $wapp REQUEST_URI] {} newR]} {
- # Some servers (ex: nginx) append the query parameters to REQUEST_URI.
- # These need to be stripped off
- dict set wapp REQUEST_URI $newR
- }
- if {[dict exists $wapp SCRIPT_NAME]} {
- dict append wapp BASE_URL [dict get $wapp SCRIPT_NAME]
- } else {
- dict set wapp SCRIPT_NAME {}
- }
- if {![dict exists $wapp PATH_INFO]} {
- # If PATH_INFO is missing (ex: nginx) then construct it
- set URI [dict get $wapp REQUEST_URI]
- set skip [string length [dict get $wapp SCRIPT_NAME]]
- dict set wapp PATH_INFO [string range $URI $skip end]
- }
- if {[regexp {^/([^/]+)(.*)$} [dict get $wapp PATH_INFO] all head tail]} {
- dict set wapp PATH_HEAD $head
- dict set wapp PATH_TAIL [string trimleft $tail /]
- } else {
- dict set wapp PATH_INFO {}
- dict set wapp PATH_HEAD {}
- dict set wapp PATH_TAIL {}
- }
- dict set wapp SELF_URL [dict get $wapp BASE_URL]/[dict get $wapp PATH_HEAD]
-
- # Parse query parameters from the query string, the cookies, and
- # POST data
- #
- if {[dict exists $wapp HTTP_COOKIE]} {
- foreach qterm [split [dict get $wapp HTTP_COOKIE] {;}] {
- set qsplit [split [string trim $qterm] =]
- set nm [lindex $qsplit 0]
- if {[regexp {^[a-z][-a-z0-9_]*$} $nm]} {
- dict set wapp $nm [wappInt-decode-url [lindex $qsplit 1]]
- }
- }
- }
- set same_origin 0
- if {[dict exists $wapp HTTP_REFERER]} {
- set referer [dict get $wapp HTTP_REFERER]
- set base [dict get $wapp BASE_URL]
- if {$referer==$base || [string match $base/* $referer]} {
- set same_origin 1
- }
- }
- dict set wapp SAME_ORIGIN $same_origin
- if {$same_origin} {
- wappInt-decode-query-params
- }
-
- # Invoke the application-defined handler procedure for this page
- # request. If an error occurs while running that procedure, generate
- # an HTTP reply that contains the error message.
- #
- wapp-before-dispatch-hook
- wappInt-trace
- set mname [dict get $wapp PATH_HEAD]
- if {[catch {
- if {$mname!="" && [llength [info proc wapp-page-$mname]]>0} {
- wapp-page-$mname
- } else {
- wapp-default
- }
- } msg]} {
- if {[wapp-param WAPP_MODE]=="local" || [wapp-param WAPP_MODE]=="server"} {
- puts "ERROR: $::errorInfo"
- }
- wapp-reset
- wapp-reply-code "500 Internal Server Error"
- wapp-mimetype text/html
- wapp-trim {
- <h1>Wapp Application Error</h1>
- <pre>%html($::errorInfo)</pre>
- }
- dict unset wapp .new-cookies
- }
-
- # Transmit the HTTP reply
- #
- if {$chan=="stdout"} {
- puts $chan "Status: [dict get $wapp .reply-code]\r"
- } else {
- puts $chan "HTTP/1.1 [dict get $wapp .reply-code]\r"
- puts $chan "Server: wapp\r"
- puts $chan "Connection: close\r"
- }
- if {[dict exists $wapp .reply-extra]} {
- foreach {name value} [dict get $wapp .reply-extra] {
- puts $chan "$name: $value\r"
- }
- }
- if {[dict exists $wapp .csp]} {
- puts $chan "Content-Security-Policy: [dict get $wapp .csp]\r"
- }
- set mimetype [dict get $wapp .mimetype]
- puts $chan "Content-Type: $mimetype\r"
- if {[dict exists $wapp .new-cookies]} {
- foreach {nm val} [dict get $wapp .new-cookies] {
- if {[regexp {^[a-z][-a-z0-9_]*$} $nm]} {
- if {$val==""} {
- puts $chan "Set-Cookie: $nm=; HttpOnly; Path=/; Max-Age=1\r"
- } else {
- set val [wappInt-enc-url $val]
- puts $chan "Set-Cookie: $nm=$val; HttpOnly; Path=/\r"
- }
- }
- }
- }
- if {[string match text/* $mimetype]} {
- set reply [encoding convertto utf-8 [dict get $wapp .reply]]
- if {[regexp {\ygzip\y} [wapp-param HTTP_ACCEPT_ENCODING]]} {
- catch {
- set x [zlib gzip $reply]
- set reply $x
- puts $chan "Content-Encoding: gzip\r"
- }
- }
- } else {
- set reply [dict get $wapp .reply]
- }
- puts $chan "Content-Length: [string length $reply]\r"
- puts $chan \r
- puts -nonewline $chan $reply
- flush $chan
- wappInt-close-channel $chan
-}
-
-# This routine runs just prior to request-handler dispatch. The
-# default implementation is a no-op, but applications can override
-# to do additional transformations or checks.
-#
-proc wapp-before-dispatch-hook {} {return}
-
-# Process a single CGI request
-#
-proc wappInt-handle-cgi-request {} {
- global wapp env
- foreach key {
- CONTENT_LENGTH
- CONTENT_TYPE
- DOCUMENT_ROOT
- HTTP_ACCEPT_ENCODING
- HTTP_COOKIE
- HTTP_HOST
- HTTP_REFERER
- HTTP_USER_AGENT
- HTTPS
- PATH_INFO
- QUERY_STRING
- REMOTE_ADDR
- REQUEST_METHOD
- REQUEST_URI
- REMOTE_USER
- SCRIPT_FILENAME
- SCRIPT_NAME
- SERVER_NAME
- SERVER_PORT
- SERVER_PROTOCOL
- } {
- if {[info exists env($key)]} {
- dict set wapp $key $env($key)
- }
- }
- set len 0
- if {[dict exists $wapp CONTENT_LENGTH]} {
- set len [dict get $wapp CONTENT_LENGTH]
- }
- if {$len>0} {
- fconfigure stdin -translation binary
- dict set wapp CONTENT [read stdin $len]
- }
- dict set wapp WAPP_MODE cgi
- fconfigure stdout -translation binary
- wappInt-handle-request stdout 1
-}
-
-# Process new text received on an inbound SCGI request
-#
-proc wappInt-scgi-readable {chan} {
- if {[catch [list wappInt-scgi-readable-unsafe $chan] msg]} {
- puts stderr "$msg\n$::errorInfo"
- wappInt-close-channel $chan
- }
-}
-proc wappInt-scgi-readable-unsafe {chan} {
- upvar #0 wappInt-$chan W wapp wapp
- if {![dict exists $W .toread]} {
- # If the .toread key is not set, that means we are still reading
- # the header.
- #
- # An SGI header is short. This implementation assumes the entire
- # header is available all at once.
- #
- dict set W .remove_addr [dict get $W REMOTE_ADDR]
- set req [read $chan 15]
- set n [string length $req]
- scan $req %d:%s len hdr
- incr len [string length "$len:,"]
- append hdr [read $chan [expr {$len-15}]]
- foreach {nm val} [split $hdr \000] {
- if {$nm==","} break
- dict set W $nm $val
- }
- set len 0
- if {[dict exists $W CONTENT_LENGTH]} {
- set len [dict get $W CONTENT_LENGTH]
- }
- if {$len>0} {
- # Still need to read the query content
- dict set W .toread $len
- } else {
- # There is no query content, so handle the request immediately
- dict set W SERVER_ADDR [dict get $W .remove_addr]
- set wapp $W
- wappInt-handle-request $chan 0
- }
- } else {
- # If .toread is set, that means we are reading the query content.
- # Continue reading until .toread reaches zero.
- set got [read $chan [dict get $W .toread]]
- dict append W CONTENT $got
- dict set W .toread [expr {[dict get $W .toread]-[string length $got]}]
- if {[dict get $W .toread]<=0} {
- # Handle the request as soon as all the query content is received
- dict set W SERVER_ADDR [dict get $W .remove_addr]
- set wapp $W
- wappInt-handle-request $chan 0
- }
- }
-}
-
-# Start up the wapp framework. Parameters are a list passed as the
-# single argument.
-#
-# -server $PORT Listen for HTTP requests on this TCP port $PORT
-#
-# -local $PORT Listen for HTTP requests on 127.0.0.1:$PORT
-#
-# -scgi $PORT Listen for SCGI requests on 127.0.0.1:$PORT
-#
-# -remote-scgi $PORT Listen for SCGI requests on TCP port $PORT
-#
-# -cgi Handle a single CGI request
-#
-# With no arguments, the behavior is called "auto". In "auto" mode,
-# if the GATEWAY_INTERFACE environment variable indicates CGI, then run
-# as CGI. Otherwise, start an HTTP server bound to the loopback address
-# only, on an arbitrary TCP port, and automatically launch a web browser
-# on that TCP port.
-#
-# Additional options:
-#
-# -fromip GLOB Reject any incoming request where the remote
-# IP address does not match the GLOB pattern. This
-# value defaults to '127.0.0.1' for -local and -scgi.
-#
-# -nowait Do not wait in the event loop. Return immediately
-# after all event handlers are established.
-#
-# -trace "puts" each request URL as it is handled, for
-# debugging
-#
-# -lint Run wapp-safety-check on the application instead
-# of running the application itself
-#
-# -Dvar=value Set TCL global variable "var" to "value"
-#
-#
-proc wapp-start {arglist} {
- global env
- set mode auto
- set port 0
- set nowait 0
- set fromip {}
- set n [llength $arglist]
- for {set i 0} {$i<$n} {incr i} {
- set term [lindex $arglist $i]
- if {[string match --* $term]} {set term [string range $term 1 end]}
- switch -glob -- $term {
- -server {
- incr i;
- set mode "server"
- set port [lindex $arglist $i]
- }
- -local {
- incr i;
- set mode "local"
- set fromip 127.0.0.1
- set port [lindex $arglist $i]
- }
- -scgi {
- incr i;
- set mode "scgi"
- set fromip 127.0.0.1
- set port [lindex $arglist $i]
- }
- -remote-scgi {
- incr i;
- set mode "remote-scgi"
- set port [lindex $arglist $i]
- }
- -cgi {
- set mode "cgi"
- }
- -fromip {
- incr i
- set fromip [lindex $arglist $i]
- }
- -nowait {
- set nowait 1
- }
- -trace {
- proc wappInt-trace {} {
- set q [wapp-param QUERY_STRING]
- set uri [wapp-param BASE_URL][wapp-param PATH_INFO]
- if {$q!=""} {append uri ?$q}
- puts $uri
- }
- }
- -lint {
- set res [wapp-safety-check]
- if {$res!=""} {
- puts "Potential problems in this code:"
- puts $res
- exit 1
- } else {
- exit
- }
- }
- -D*=* {
- if {[regexp {^.D([^=]+)=(.*)$} $term all var val]} {
- set ::$var $val
- }
- }
- default {
- error "unknown option: $term"
- }
- }
- }
- if {$mode=="auto"} {
- if {[info exists env(GATEWAY_INTERFACE)]
- && [string match CGI/1.* $env(GATEWAY_INTERFACE)]} {
- set mode cgi
- } else {
- set mode local
- }
- }
- if {$mode=="cgi"} {
- wappInt-handle-cgi-request
- } else {
- wappInt-start-listener $port $mode $fromip
- if {!$nowait} {
- vwait ::forever
- }
- }
-}
-
-# Call this version 1.0
-package provide wapp 1.0
diff --git a/test/wapptest.tcl b/test/wapptest.tcl
deleted file mode 100755
index d37b2e48c..000000000
--- a/test/wapptest.tcl
+++ /dev/null
@@ -1,909 +0,0 @@
-#!/bin/sh
-# \
-exec wapptclsh "$0" ${1+"$@"}
-
-# package required wapp
-source [file join [file dirname [info script]] wapp.tcl]
-
-# Variables set by the "control" form:
-#
-# G(platform) - User selected platform.
-# G(cfgglob) - Glob pattern that all configurations must match
-# G(test) - Set to "Normal", "Veryquick", "Smoketest" or "Build-Only".
-# G(keep) - Boolean. True to delete no files after each test.
-# G(msvc) - Boolean. True to use MSVC as the compiler.
-# G(tcl) - Use Tcl from this directory for builds.
-# G(jobs) - How many sub-processes to run simultaneously.
-#
-set G(platform) $::tcl_platform(os)-$::tcl_platform(machine)
-set G(cfgglob) *
-set G(test) Normal
-set G(keep) 1
-set G(msvc) 0
-set G(tcl) [::tcl::pkgconfig get libdir,install]
-set G(jobs) 3
-set G(debug) 0
-
-set G(noui) 0
-set G(stdout) 0
-
-
-proc wapptest_init {} {
- global G
-
- set lSave [list platform test keep msvc tcl jobs debug noui stdout cfgglob]
- foreach k $lSave { set A($k) $G($k) }
- array unset G
- foreach k $lSave { set G($k) $A($k) }
-
- # The root of the SQLite source tree.
- set G(srcdir) [file dirname [file dirname [info script]]]
-
- set G(sqlite_version) "unknown"
-
- # Either "config", "running" or "stopped":
- set G(state) "config"
-
- set G(hostname) "(unknown host)"
- catch { set G(hostname) [exec hostname] }
- set G(host) $G(hostname)
- append G(host) " $::tcl_platform(os) $::tcl_platform(osVersion)"
- append G(host) " $::tcl_platform(machine) $::tcl_platform(byteOrder)"
-}
-
-proc wapptest_run {} {
- global G
- set_test_array
- set G(state) "running"
-
- wapptest_openlog
-
- wapptest_output "Running the following for $G(platform). $G(jobs) jobs."
- foreach t $G(test_array) {
- set config [dict get $t config]
- set target [dict get $t target]
- wapptest_output [format " %-25s%s" $config $target]
- }
- wapptest_output [string repeat * 70]
-}
-
-proc releasetest_data {args} {
- global G
- set rtd [file join $G(srcdir) test releasetest_data.tcl]
- set fd [open "|[info nameofexecutable] $rtd $args" r+]
- set ret [read $fd]
- close $fd
- return $ret
-}
-
-# Generate the text for the box at the top of the UI. The current SQLite
-# version, according to fossil, along with a warning if there are
-# uncommitted changes in the checkout.
-#
-proc generate_fossil_info {} {
- global G
- set pwd [pwd]
- cd $G(srcdir)
- set rc [catch {
- set r1 [exec fossil info]
- set r2 [exec fossil changes]
- }]
- cd $pwd
- if {$rc} return
-
- foreach line [split $r1 "\n"] {
- if {[regexp {^checkout: *(.*)$} $line -> co]} {
- wapp-trim { <br> %html($co) }
- }
- }
-
- if {[string trim $r2]!=""} {
- wapp-trim {
- <br><span class=warning>
- WARNING: Uncommitted changes in checkout
- </span>
- }
- }
-}
-
-# If the application is in "config" state, set the contents of the
-# ::G(test_array) global to reflect the tests that will be run. If the
-# app is in some other state ("running" or "stopped"), this command
-# is a no-op.
-#
-proc set_test_array {} {
- global G
- if { $G(state)=="config" } {
- set G(test_array) [list]
- set debug "-debug"
- if {$G(debug)==0} { set debug "-nodebug"}
- foreach {config target} [releasetest_data tests $debug $G(platform)] {
-
- # All configuration names must match $g(cfgglob), which defaults to *
- #
- if {![string match -nocase $G(cfgglob) $config]} continue
-
- # If using MSVC, do not run sanitize or valgrind tests. Or the
- # checksymbols test.
- if {$G(msvc) && (
- "Sanitize" == $config
- || "checksymbols" in $target
- || "valgrindtest" in $target
- )} {
- continue
- }
-
- # If the test mode is not "Normal", override the target.
- #
- if {$target!="checksymbols" && $G(platform)!="Failure-Detection"} {
- switch -- $G(test) {
- Veryquick { set target quicktest }
- Smoketest { set target smoketest }
- Build-Only {
- set target testfixture
- if {$::tcl_platform(platform)=="windows"} {
- set target testfixture.exe
- }
- }
- }
- }
-
- lappend G(test_array) [dict create config $config target $target]
- }
- }
-}
-
-proc count_tests_and_errors {name logfile} {
- global G
-
- set fd [open $logfile rb]
- set seen 0
- while {![eof $fd]} {
- set line [gets $fd]
- if {[regexp {(\d+) errors out of (\d+) tests} $line all nerr ntest]} {
- incr G(test.$name.nError) $nerr
- incr G(test.$name.nTest) $ntest
- set seen 1
- if {$nerr>0} {
- set G(test.$name.errmsg) $line
- }
- }
- if {[regexp {runtime error: +(.*)} $line all msg]} {
- # skip over "value is outside range" errors
- if {[regexp {.* is outside the range of representable} $line]} {
- # noop
- } else {
- incr G(test.$name.nError)
- if {$G(test.$name.errmsg)==""} {
- set G(test.$name.errmsg) $msg
- }
- }
- }
- if {[regexp {fatal error +(.*)} $line all msg]} {
- incr G(test.$name.nError)
- if {$G(test.$name.errmsg)==""} {
- set G(test.$name.errmsg) $msg
- }
- }
- if {[regexp {ERROR SUMMARY: (\d+) errors.*} $line all cnt] && $cnt>0} {
- incr G(test.$name.nError)
- if {$G(test.$name.errmsg)==""} {
- set G(test.$name.errmsg) $all
- }
- }
- if {[regexp {^VERSION: 3\.\d+.\d+} $line]} {
- set v [string range $line 9 end]
- if {$G(sqlite_version) eq "unknown"} {
- set G(sqlite_version) $v
- } elseif {$G(sqlite_version) ne $v} {
- set G(test.$name.errmsg) "version conflict: {$G(sqlite_version)} vs. {$v}"
- }
- }
- }
- close $fd
- if {$G(test) == "Build-Only"} {
- incr G(test.$name.nTest)
- if {$G(test.$name.nError)>0} {
- set errmsg "Build failed"
- }
- } elseif {!$seen} {
- set G(test.$name.errmsg) "Test did not complete"
- if {[file readable core]} {
- append G(test.$name.errmsg) " - core file exists"
- }
- }
-}
-
-proc wapptest_output {str} {
- global G
- if {$G(stdout)} { puts $str }
- if {[info exists G(log)]} {
- puts $G(log) $str
- flush $G(log)
- }
-}
-proc wapptest_openlog {} {
- global G
- set G(log) [open wapptest-out.txt w+]
-}
-proc wapptest_closelog {} {
- global G
- close $G(log)
- unset G(log)
-}
-
-proc format_seconds {seconds} {
- set min [format %.2d [expr ($seconds / 60) % 60]]
- set hr [format %.2d [expr $seconds / 3600]]
- set sec [format %.2d [expr $seconds % 60]]
- return "$hr:$min:$sec"
-}
-
-# This command is invoked once a slave process has finished running its
-# tests, successfully or otherwise. Parameter $name is the name of the
-# test, $rc the exit code returned by the slave process.
-#
-proc slave_test_done {name rc} {
- global G
- set G(test.$name.done) [clock seconds]
- set G(test.$name.nError) 0
- set G(test.$name.nTest) 0
- set G(test.$name.errmsg) ""
- if {$rc} {
- incr G(test.$name.nError)
- }
- if {[file exists $G(test.$name.log)]} {
- count_tests_and_errors $name $G(test.$name.log)
- }
-
- # If the "keep files" checkbox is clear, delete all files except for
- # the executables and test logs. And any core file that is present.
- if {$G(keep)==0} {
- set keeplist {
- testfixture testfixture.exe
- sqlite3 sqlite3.exe
- test.log test-out.txt
- core
- wapptest_make.sh
- wapptest_configure.sh
- wapptest_run.tcl
- }
- foreach f [glob -nocomplain [file join $G(test.$name.dir) *]] {
- set t [file tail $f]
- if {[lsearch $keeplist $t]<0} {
- catch { file delete -force $f }
- }
- }
- }
-
- # Format a message regarding the success or failure of hte test.
- set t [format_seconds [expr $G(test.$name.done) - $G(test.$name.start)]]
- set res "OK"
- if {$G(test.$name.nError)} { set res "FAILED" }
- set dots [string repeat . [expr 60 - [string length $name]]]
- set msg "$name $dots $res ($t)"
-
- wapptest_output $msg
- if {[info exists G(test.$name.errmsg)] && $G(test.$name.errmsg)!=""} {
- wapptest_output " $G(test.$name.errmsg)"
- }
-}
-
-# This is a fileevent callback invoked each time a file-descriptor that
-# connects this process to a slave process is readable.
-#
-proc slave_fileevent {name} {
- global G
- set fd $G(test.$name.channel)
-
- if {[eof $fd]} {
- fconfigure $fd -blocking 1
- set rc [catch { close $fd }]
- unset G(test.$name.channel)
- slave_test_done $name $rc
- } else {
- set line [gets $fd]
- if {[string trim $line] != ""} { puts "Trace : $name - \"$line\"" }
- }
-
- do_some_stuff
-}
-
-# Return the contents of the "slave script" - the script run by slave
-# processes to actually perform the test. All it does is execute the
-# test script already written to disk (wapptest_cmd.sh or wapptest_cmd.bat).
-#
-proc wapptest_slave_script {} {
- global G
- if {$G(msvc)==0} {
- set dir [file join .. $G(srcdir)]
- set res [subst -nocommands {
- set rc [catch "exec sh wapptest_cmd.sh {$dir} >>& test.log" ]
- exit [set rc]
- }]
- } else {
- set dir [file nativename [file normalize $G(srcdir)]]
- set dir [string map [list "\\" "\\\\"] $dir]
- set res [subst -nocommands {
- set rc [catch "exec wapptest_cmd.bat {$dir} >>& test.log" ]
- exit [set rc]
- }]
- }
-
- set res
-}
-
-
-# Launch a slave process to run a test.
-#
-proc slave_launch {name target dir} {
- global G
-
- catch { file mkdir $dir } msg
- foreach f [glob -nocomplain [file join $dir *]] {
- catch { file delete -force $f }
- }
- set G(test.$name.dir) $dir
-
- # Write the test command to wapptest_cmd.sh|bat.
- #
- set ext sh
- if {$G(msvc)} { set ext bat }
- set fd1 [open [file join $dir wapptest_cmd.$ext] w]
- if {$G(msvc)} {
- puts $fd1 [releasetest_data script -msvc $name $target]
- } else {
- puts $fd1 [releasetest_data script $name $target]
- }
- close $fd1
-
- # Write the wapptest_run.tcl script to the test directory. To run the
- # commands in the other two files.
- #
- set fd3 [open [file join $dir wapptest_run.tcl] w]
- puts $fd3 [wapptest_slave_script]
- close $fd3
-
- set pwd [pwd]
- cd $dir
- set fd [open "|[info nameofexecutable] wapptest_run.tcl" r+]
- cd $pwd
-
- set G(test.$name.channel) $fd
- fconfigure $fd -blocking 0
- fileevent $fd readable [list slave_fileevent $name]
-}
-
-proc do_some_stuff {} {
- global G
-
- # Count the number of running jobs. A running job has an entry named
- # "channel" in its dictionary.
- set nRunning 0
- set bFinished 1
- foreach j $G(test_array) {
- set name [dict get $j config]
- if { [info exists G(test.$name.channel)]} { incr nRunning }
- if {![info exists G(test.$name.done)]} { set bFinished 0 }
- }
-
- if {$bFinished} {
- set nError 0
- set nTest 0
- set nConfig 0
- foreach j $G(test_array) {
- set name [dict get $j config]
- incr nError $G(test.$name.nError)
- incr nTest $G(test.$name.nTest)
- incr nConfig
- }
- set G(result) "$nError errors from $nTest tests in $nConfig configurations."
- wapptest_output [string repeat * 70]
- wapptest_output $G(result)
- catch {
- append G(result) " SQLite version $G(sqlite_version)"
- wapptest_output " SQLite version $G(sqlite_version)"
- }
- set G(state) "stopped"
- wapptest_closelog
- if {$G(noui)} { exit 0 }
- } else {
- set nLaunch [expr $G(jobs) - $nRunning]
- foreach j $G(test_array) {
- if {$nLaunch<=0} break
- set name [dict get $j config]
- if { ![info exists G(test.$name.channel)]
- && ![info exists G(test.$name.done)]
- } {
-
- set target [dict get $j target]
- set dir [string tolower [string map {" " _ "-" _} $name]]
- set G(test.$name.start) [clock seconds]
- set G(test.$name.log) [file join $dir test.log]
-
- slave_launch $name $target $dir
-
- incr nLaunch -1
- }
- }
- }
-}
-
-proc generate_select_widget {label id lOpt opt} {
- wapp-trim {
- <label> %string($label) </label>
- <select id=%string($id) name=%string($id)>
- }
- foreach o $lOpt {
- set selected ""
- if {$o==$opt} { set selected " selected=1" }
- wapp-subst "<option $selected>$o</option>"
- }
- wapp-trim { </select> }
-}
-
-proc generate_main_page {{extra {}}} {
- global G
- set_test_array
-
- set hostname $G(hostname)
- wapp-trim {
- <html>
- <head>
- <title> %html($hostname): wapptest.tcl </title>
- <link rel="stylesheet" type="text/css" href="style.css"/>
- </head>
- <body>
- }
-
- set host $G(host)
- wapp-trim {
- <div class="border">%string($host)
- }
- generate_fossil_info
- wapp-trim {
- </div>
- <div class="border" id=controls>
- <form action="control" method="post" name="control">
- }
-
- # Build the "platform" select widget.
- set lOpt [releasetest_data platforms]
- generate_select_widget Platform control_platform $lOpt $G(platform)
-
- # Build the "test" select widget.
- set lOpt [list Normal Veryquick Smoketest Build-Only]
- generate_select_widget Test control_test $lOpt $G(test)
-
- # Build the "jobs" select widget. Options are 1 to 8.
- generate_select_widget Jobs control_jobs {1 2 3 4 5 6 7 8 12 16} $G(jobs)
-
- switch $G(state) {
- config {
- set txt "Run Tests!"
- set id control_run
- }
- running {
- set txt "STOP Tests!"
- set id control_stop
- }
- stopped {
- set txt "Reset!"
- set id control_reset
- }
- }
- wapp-trim {
- <div class=right>
- <input id=%string($id) name=%string($id) type=submit value="%string($txt)">
- </input>
- </div>
- }
-
- wapp-trim {
- <br><br>
- <label> Tcl: </label>
- <input id="control_tcl" name="control_tcl"></input>
- <label> Keep files: </label>
- <input id="control_keep" name="control_keep" type=checkbox value=1>
- </input>
- <label> Use MSVC: </label>
- <input id="control_msvc" name="control_msvc" type=checkbox value=1>
- <label> Debug tests: </label>
- <input id="control_debug" name="control_debug" type=checkbox value=1>
- </input>
- }
- wapp-trim {
- </form>
- }
- wapp-trim {
- </div>
- <div id=tests>
- }
- wapp-page-tests
-
- set script "script/$G(state).js"
- wapp-trim {
- </div>
- <script src=%string($script)></script>
- </body>
- </html>
- }
-}
-
-proc wapp-default {} {
- generate_main_page
-}
-
-proc wapp-page-tests {} {
- global G
- wapp-trim { <table class="border" width=100%> }
- foreach t $G(test_array) {
- set config [dict get $t config]
- set target [dict get $t target]
-
- set class "testwait"
- set seconds ""
-
- if {[info exists G(test.$config.log)]} {
- if {[info exists G(test.$config.channel)]} {
- set class "testrunning"
- set seconds [expr [clock seconds] - $G(test.$config.start)]
- } elseif {[info exists G(test.$config.done)]} {
- if {$G(test.$config.nError)>0} {
- set class "testfail"
- } else {
- set class "testdone"
- }
- set seconds [expr $G(test.$config.done) - $G(test.$config.start)]
- }
- set seconds [format_seconds $seconds]
- }
-
- wapp-trim {
- <tr class=%string($class)>
- <td class="nowrap"> %html($config)
- <td class="padleft nowrap"> %html($target)
- <td class="padleft nowrap"> %html($seconds)
- <td class="padleft nowrap">
- }
- if {[info exists G(test.$config.log)]} {
- set log $G(test.$config.log)
- set uri "log/$log"
- wapp-trim {
- <a href=%url($uri)> %html($log) </a>
- }
- }
- if {[info exists G(test.$config.errmsg)] && $G(test.$config.errmsg)!=""} {
- set errmsg $G(test.$config.errmsg)
- wapp-trim {
- <tr class=testfail>
- <td> <td class="padleft" colspan=3> %html($errmsg)
- }
- }
- }
-
- wapp-trim { </table> }
-
- if {[info exists G(result)]} {
- set res $G(result)
- wapp-trim {
- <div class=border id=result> %string($res) </div>
- }
- }
-}
-
-# URI: /control
-#
-# Whenever the form at the top of the application page is submitted, it
-# is submitted here.
-#
-proc wapp-page-control {} {
- global G
- if {$::G(state)=="config"} {
- set lControls [list platform test tcl jobs keep msvc debug]
- set G(msvc) 0
- set G(keep) 0
- set G(debug) 0
- } else {
- set lControls [list jobs]
- }
- foreach v $lControls {
- if {[wapp-param-exists control_$v]} {
- set G($v) [wapp-param control_$v]
- }
- }
-
- if {[wapp-param-exists control_run]} {
- # This is a "run test" command.
- wapptest_run
- }
-
- if {[wapp-param-exists control_stop]} {
- # A "STOP tests" command.
- set G(state) "stopped"
- set G(result) "Test halted by user"
- foreach j $G(test_array) {
- set name [dict get $j config]
- if { [info exists G(test.$name.channel)] } {
- close $G(test.$name.channel)
- unset G(test.$name.channel)
- slave_test_done $name 1
- }
- }
- wapptest_closelog
- }
-
- if {[wapp-param-exists control_reset]} {
- # A "reset app" command.
- set G(state) "config"
- wapptest_init
- }
-
- if {$::G(state) == "running"} {
- do_some_stuff
- }
- wapp-redirect /
-}
-
-# URI: /style.css
-#
-# Return the stylesheet for the application main page.
-#
-proc wapp-page-style.css {} {
- wapp-subst {
-
- /* The boxes with black borders use this class */
- .border {
- border: 3px groove #444444;
- padding: 1em;
- margin-top: 1em;
- margin-bottom: 1em;
- }
-
- /* Float to the right (used for the Run/Stop/Reset button) */
- .right { float: right; }
-
- /* Style for the large red warning at the top of the page */
- .warning {
- color: red;
- font-weight: bold;
- }
-
- /* Styles used by cells in the test table */
- .padleft { padding-left: 5ex; }
- .nowrap { white-space: nowrap; }
-
- /* Styles for individual tests, depending on the outcome */
- .testwait { }
- .testrunning { color: blue }
- .testdone { color: green }
- .testfail { color: red }
- }
-}
-
-# URI: /script/${state}.js
-#
-# The last part of this URI is always "config.js", "running.js" or
-# "stopped.js", depending on the state of the application. It returns
-# the javascript part of the front-end for the requested state to the
-# browser.
-#
-proc wapp-page-script {} {
- regexp {[^/]*$} [wapp-param REQUEST_URI] script
-
- set tcl $::G(tcl)
- set keep $::G(keep)
- set msvc $::G(msvc)
- set debug $::G(debug)
-
- wapp-subst {
- var lElem = \["control_platform", "control_test", "control_msvc",
- "control_jobs", "control_debug"
- \];
- lElem.forEach(function(e) {
- var elem = document.getElementById(e);
- elem.addEventListener("change", function() { control.submit() } );
- })
-
- elem = document.getElementById("control_tcl");
- elem.value = "%string($tcl)"
-
- elem = document.getElementById("control_keep");
- elem.checked = %string($keep);
-
- elem = document.getElementById("control_msvc");
- elem.checked = %string($msvc);
-
- elem = document.getElementById("control_debug");
- elem.checked = %string($debug);
- }
-
- if {$script != "config.js"} {
- wapp-subst {
- var lElem = \["control_platform", "control_test",
- "control_tcl", "control_keep", "control_msvc",
- "control_debug"
- \];
- lElem.forEach(function(e) {
- var elem = document.getElementById(e);
- elem.disabled = true;
- })
- }
- }
-
- if {$script == "running.js"} {
- wapp-subst {
- function reload_tests() {
- fetch('tests')
- .then( data => data.text() )
- .then( data => {
- document.getElementById("tests").innerHTML = data;
- })
- .then( data => {
- if( document.getElementById("result") ){
- document.location = document.location;
- } else {
- setTimeout(reload_tests, 1000)
- }
- });
- }
-
- setTimeout(reload_tests, 1000)
- }
- }
-}
-
-# URI: /env
-#
-# This is for debugging only. Serves no other purpose.
-#
-proc wapp-page-env {} {
- wapp-allow-xorigin-params
- wapp-trim {
- <h1>Wapp Environment</h1>\n<pre>
- <pre>%html([wapp-debug-env])</pre>
- }
-}
-
-# URI: /log/dirname/test.log
-#
-# This URI reads file "dirname/test.log" from disk, wraps it in a <pre>
-# block, and returns it to the browser. Use for viewing log files.
-#
-proc wapp-page-log {} {
- set log [string range [wapp-param REQUEST_URI] 5 end]
- set fd [open $log]
- set data [read $fd]
- close $fd
- wapp-trim {
- <pre>
- %html($data)
- </pre>
- }
-}
-
-# Print out a usage message. Then do [exit 1].
-#
-proc wapptest_usage {} {
- puts stderr {
-This Tcl script is used to test various configurations of SQLite. By
-default it uses "wapp" to provide an interactive interface. Supported
-command line options (all optional) are:
-
- --platform PLATFORM (which tests to run)
- --config GLOB (only run configurations matching GLOB)
- --smoketest (run "make smoketest" only)
- --veryquick (run veryquick.test only)
- --buildonly (build executables, do not run tests)
- --jobs N (number of concurrent jobs)
- --tcl DIR (where to find tclConfig.sh)
- --deletefiles (delete extra files after each test)
- --msvc (Use MS Visual C)
- --debug (Also run [n]debugging versions of tests)
- --noui (do not use wapp)
- }
- exit 1
-}
-
-# Sort command line arguments into two groups: those that belong to wapp,
-# and those that belong to the application.
-set WAPPARG(-server) 1
-set WAPPARG(-local) 1
-set WAPPARG(-scgi) 1
-set WAPPARG(-remote-scgi) 1
-set WAPPARG(-fromip) 1
-set WAPPARG(-nowait) 0
-set WAPPARG(-cgi) 0
-set lWappArg [list]
-set lTestArg [list]
-for {set i 0} {$i < [llength $argv]} {incr i} {
- set arg [lindex $argv $i]
- if {[string range $arg 0 1]=="--"} {
- set arg [string range $arg 1 end]
- }
- if {[info exists WAPPARG($arg)]} {
- lappend lWappArg $arg
- if {$WAPPARG($arg)} {
- incr i
- lappend lWappArg [lindex $argv $i]
- }
- } else {
- lappend lTestArg $arg
- }
-}
-
-wapptest_init
-for {set i 0} {$i < [llength $lTestArg]} {incr i} {
- set opt [lindex $lTestArg $i]
- if {[string range $opt 0 1]=="--"} {
- set opt [string range $opt 1 end]
- }
- switch -- $opt {
- -platform {
- if {$i==[llength $lTestArg]-1} { wapptest_usage }
- incr i
- set arg [lindex $lTestArg $i]
- set lPlatform [releasetest_data platforms]
- if {[lsearch $lPlatform $arg]<0} {
- puts stderr "No such platform: $arg. Platforms are: $lPlatform"
- exit -1
- }
- set G(platform) $arg
- }
-
- -smoketest { set G(test) Smoketest }
- -veryquick { set G(test) Veryquick }
- -buildonly { set G(test) Build-Only }
- -jobs {
- if {$i==[llength $lTestArg]-1} { wapptest_usage }
- incr i
- set G(jobs) [lindex $lTestArg $i]
- }
-
- -tcl {
- if {$i==[llength $lTestArg]-1} { wapptest_usage }
- incr i
- set G(tcl) [lindex $lTestArg $i]
- }
-
- -deletefiles {
- set G(keep) 0
- }
-
- -msvc {
- set G(msvc) 1
- }
-
- -debug {
- set G(debug) 1
- }
-
- -noui {
- set G(noui) 1
- set G(stdout) 1
- }
-
- -config {
- if {$i==[llength $lTestArg]-1} { wapptest_usage }
- incr i
- set G(cfgglob) [lindex $lTestArg $i]
- }
-
- -stdout {
- set G(stdout) 1
- }
-
- default {
- puts stderr "Unrecognized option: [lindex $lTestArg $i]"
- wapptest_usage
- }
- }
-}
-
-if {$G(noui)==0} {
- wapp-start $lWappArg
-} else {
- wapptest_run
- do_some_stuff
- vwait forever
-}
diff --git a/tool/srctree-check.tcl b/tool/srctree-check.tcl
index f44e3ceec..51226cda4 100644
--- a/tool/srctree-check.tcl
+++ b/tool/srctree-check.tcl
@@ -36,6 +36,17 @@ set TCLSH [info nameofexe]
#
set NERR 0
+######################### configure ###########################################
+
+set conf [readfile $ROOT/configure]
+set vers [readfile $ROOT/VERSION]
+if {[string first $vers $conf]<=0} {
+ puts "ERROR: ./configure does not agree with ./VERSION"
+ puts "...... Fix: run autoconf"
+ incr NERR
+}
+unset conf
+
######################### autoconf/tea/configure.ac ###########################
set confac [readfile $ROOT/autoconf/tea/configure.ac]
@@ -45,9 +56,11 @@ append pattern [string trim $vers]
append pattern {])}
if {[string first $pattern $confac]<=0} {
puts "ERROR: ./autoconf/tea/configure.ac does not agree with ./VERSION"
- puts "...... Fix: manually edit ./autoconf/tea/configure.ac to"
+ puts "...... Fix: manually edit ./autoconf/tea/configure.ac and put the"
+ puts "...... correct version number in AC_INIT()"
incr NERR
}
+unset confac
######################### autoconf/Makefile.msc ###############################