Compare commits

..

196 Commits

Author SHA1 Message Date
stephan d1ce2755f0 Make all native JNI sqlite3_...() bindings package-private as a foot-shooting protective measure (higher-level pre-native-call argument validation is largely pending). Move SQLTester.java and TesterFts5.java into the org.sqlite.jni package so that they can access the being-tested methods.
FossilOrigin-Name: ec82f7251acab7df40755ef5f456f36fe49b59e63a20be59bd610fc4280ba8cd
2023-09-30 10:31:56 +00:00
stephan ebce46f02f More work towards the new pointer-passing mechanism in JNI, and code-adjacent cleanups.
FossilOrigin-Name: 6c63987e893357dc8b10decaa96c30fb37b75481640a303e77a0d8224354491e
2023-09-30 09:41:58 +00:00
stephan 83f62816f8 JNI: correct a NullPointerException triggered via SQLTester.
FossilOrigin-Name: 0a873de76c0cbcd8e2eda3f0508e427f1dcb32b01798687c0545acfe10102179
2023-09-28 20:34:28 +00:00
stephan 0dabcd139d Adapted the JNI bindings for the sqlite3_value_...() and (pre)update hook APIs to the new pointer-passing mechanism.
FossilOrigin-Name: 4182f0275d5d65e04a130eeef4d44642a5ffeeb4b84430d240ea2605345f1404
2023-09-28 11:19:37 +00:00
stephan 5d1448d08a Convert the remaining macro-generated JNI bindings to the new pointer-passing mechanism.
FossilOrigin-Name: 250fd6ae806cf705c0f29ad30ad8fb885b12590848e7adae63bc21d874c6d3bd
2023-09-28 10:50:26 +00:00
stephan 50b2a41330 Convert the macro-generated JNI bindings which take a db pointer to the new pointer-passing mechanism.
FossilOrigin-Name: 5f47fb77db4ee77afc541e680559ad88e66ba7fd04b830e70f8be92cf8d0a60c
2023-09-28 10:27:01 +00:00
stephan 1d74432fef Adapted JNI sqlite3_blob_...() and sqlite3_close...() to the new pointer-passing mechanism.
FossilOrigin-Name: 0b22c8ef93e5ccd45316099fb8575e27620158b1992c0c70fe0348cfc10147f8
2023-09-27 14:41:49 +00:00
stephan a917ee7056 Adapt JNI sqlite3_bind_...() bindings to the new pointer-passing method and correct the mapping of sqlite3_bind_zeroblob64() to use zeroblob64() instead of zeroblob(). Related internal API renaming.
FossilOrigin-Name: 980d559fff6e55d1e2ef39f242a8a09313a936cfb141807db475bcceff924869
2023-09-27 11:01:32 +00:00
stephan 9afd67cce7 Add missing license headers to JNI annotation classes and extend the definition of @NotNull to include 0L as the equivalent of null to account for the current rework of how pointers are passed from Java to C.
FossilOrigin-Name: bccdfeb9efde20410bde545893fb98ce5c0c4d04a8e7797b868ba8994120e1db
2023-09-27 10:29:07 +00:00
stephan 4632f91403 Rework the JNI sqlite3_backup APIs to the new pointer-passing approach.
FossilOrigin-Name: 48aeb1e2cdeb4aec85c4f63a1f809215cd4b473791169e72b5ddf6d6bdc2f7b7
2023-09-27 09:58:36 +00:00
stephan 36f60e6c1a Start reworking JNI methods such that they pass void pointers from Java to C instead of passing their strongly-typed wrappers, as that is reportedly significantly faster than passing the wrapper objects to C and extracting the pointers there. There are still many, many functions left to rework for this.
FossilOrigin-Name: 66c814dd473731703ee00e1ff610acfdccf09d1b87dd2355795ed697d4ed5d3e
2023-09-26 21:37:52 +00:00
drh 61df55d0be Fix harmless compiler warnings in the testfixture testing utility.
FossilOrigin-Name: 5a39a02d2dfd9ed6171cd0bd434b2bc268d0ed8ead6e1a396d1603266d9493ef
2023-09-26 21:22:46 +00:00
stephan e81d229fb7 JNI: implement AutoCloseable for the sqlite3 and sqlite3_stmt classes, and adjust a few tests to use it. Override Object.finalize() for sqlite3 class but not sqlite3_stmt (where it triggers a JVM crash for as-yet-unknown reasons).
FossilOrigin-Name: b633d8bbdbd43464ee03058c273e5188117b9538ed5d5148a1a3851e4adf6410
2023-09-26 19:49:35 +00:00
drh 0e224d93ea Fix yet another assert() in the partial-index constant value optimization.
FossilOrigin-Name: f459d0806cf044fd07743e4c91d0a5a6ddf45b3b41004bde4278f190d99a4cf5
2023-09-26 19:38:24 +00:00
dan 1c67749166 Remove a NEVER() from a condition in the partial-index constant value optimization that can sometimes be true.
FossilOrigin-Name: 63aea8c3e0605edcf49ad1e5ce1a56d2690dcb8c4857043997545daab46ca035
2023-09-26 19:26:47 +00:00
drh a6e7202677 Remove unreachable code from the partial-index constant value optimization
([8d4160910d651246]).

FossilOrigin-Name: 7ad38254c37153efa72291d09800693ca60894359548eda877d59defa8c70d49
2023-09-26 14:57:30 +00:00
stephan 6f737371c0 Remove some outdated JNI docs.
FossilOrigin-Name: 2ca5cbbb2a8ee94fe9f4888dea695a4429aefa17611fd9ebe59bf4866290ab0c
2023-09-26 09:46:04 +00:00
stephan efa8369214 Minor JNI doc updates. No code changes.
FossilOrigin-Name: 63687cc659f06a1f6aa9d4496ef01d9f0adbee6184b8e265fb8feec1ad30449b
2023-09-26 07:43:51 +00:00
dan 47b427423d In partial index scans, if the WHERE clause implies a constant value for a table column, replace occurences of that table column with the constant. This increases the likelihood of the partial index being a covering index.
FossilOrigin-Name: 8d4160910d6512469cb0a060b89c2509dde1c8e33d9358034d7fb39cd494eabe
2023-09-25 17:46:02 +00:00
dan 1b96f9167f Another very minor tweak to code on this branch to save cycles.
FossilOrigin-Name: 2c805dc68112db7f06aea56a731a33e046e876851bdda3e725ba96f6da1bf2ee
2023-09-25 17:21:53 +00:00
dan cf0906575d Change the order of an if..else... in new code to make it slightly faster.
FossilOrigin-Name: f80dc64483a54a488e507da140836261ae1b864577cc95d8e869919d5a25e73c
2023-09-25 15:25:49 +00:00
drh cc11526e22 Enable -Wall for the compiler on --enable-debug builds.
FossilOrigin-Name: 63a7b521390001939909d43d908af78f7df7cb92272d2b74f038da918c2bef05
2023-09-25 14:20:07 +00:00
dan 873849b81e Fix a bad interaction between LEFT JOIN, bloom filters and partial indexes.
FossilOrigin-Name: 5775f92b31f0539c66d37e50689bec17a82c0e857a781331c03e34bc18157087
2023-09-25 11:12:39 +00:00
stephan a2ead23868 Resolve the JNI FTS5 test5() failure and remove some nearby dead code.
FossilOrigin-Name: 5655c51f103c6dfade799ffb5ca2eb21b4360ada598741f373c952ae732535aa
2023-09-24 19:13:31 +00:00
dan af7cce98c5 Add extra tests for changes on this branch.
FossilOrigin-Name: cb45f5b070cfdbb4358e8e909b0b34d658b5925db06e282b38046298a159c545
2023-09-23 21:31:40 +00:00
dan 7a9bbfe1fa Avoid an error when parsing a schema that contains indexes with WHERE clauses containing unknown collation sequences.
FossilOrigin-Name: d5dd39e37ab994ce5d75688a40c6494c17807431529e85eb2b11d58f367fe232
2023-09-23 19:25:03 +00:00
dan c18c7523a4 Fix a failing assert() caused by changes on this branch.
FossilOrigin-Name: d0e21f20bd8643d21c4b88fb75b83939d68e9b7118f7289a164656f11d2ee555
2023-09-23 18:52:22 +00:00
drh 9f811a35b8 Fix a harmless compiler warning.
FossilOrigin-Name: 7c4210253b660b385d14de3ae7ab30f038036308e1164ec17b40e2805b9b2235
2023-09-23 18:49:30 +00:00
dan 2d3c5385bf Add extra tests for the fts5 xPhraseQuery API.
FossilOrigin-Name: b5edea16317ccc896394011f45442af44d5ea1041b6abfe152c90412c78bfc5b
2023-09-23 15:34:02 +00:00
stephan 6c248201b1 Resolve the JNI FTS5 test5() crash, caused by two unrelated typos in JNI callback signature strings. That test now fails with an expected-vs-got mismatch but no longer dies in the native code.
FossilOrigin-Name: 0233f310bf6bbb84f76b25205af160951e0bf6d8b2f1bca4537384af4fe47e58
2023-09-23 06:50:19 +00:00
stephan 8a67a46340 Remove an invalid optimization from JNI code and add a missing check check for Java-side exception.
FossilOrigin-Name: b8151f879718f7e30520f7e5ae04bd6724ae98af067be097b1a00df2175a038a
2023-09-23 06:37:18 +00:00
dan bd42642431 In partial index scans, if the WHERE clause implies a constant value for a table column, replace occurences of that table column with the constant. This increases the likelihood of the partial index being a covering index.
FossilOrigin-Name: 66ed7abdfa228abde2052e3988589371f0e49b11582b1b4a83255d2df3a0aefa
2023-09-22 20:21:27 +00:00
dan 8aaf63c6ac Add extra tests for java Fts5ExtensionApi API.
FossilOrigin-Name: f9d62b853ce8bfbfdc9f137e984e7a1b51d70e88c38b136b4fad1e8ae6ee8913
2023-09-18 20:42:06 +00:00
dan 876f09b560 Extra tests for java Fts5ExtensionApi interface.
FossilOrigin-Name: c923893f3604b278277de1bb919ef713bf7a4296b7ff71451cfe19bc2ff03190
2023-09-18 18:12:03 +00:00
drh 38119058aa Remove a NEVER() in btree.c that could actually be reached, with the approriate
database corruption.

FossilOrigin-Name: d1fd00e0875f881cacdc581a75215ec1872a81969ac07b8ebe3aaa707a9a3640
2023-09-17 16:36:22 +00:00
dan f99a27fab9 Fix resolution of "rowid" and similar identifiers in queries that use nested joins.
FossilOrigin-Name: 37ec43d92bde13efc71fa57ff5e59c4a95b9054c298f844aefeb06d4a91ad0d4
2023-09-16 18:18:57 +00:00
dan 2599705105 Enable "OR IGNORE" support via SQLITE_VTAB_CONSTRAINT_SUPPORT for internal-content (not contentless or external-content) fts5 tables.
FossilOrigin-Name: c362bde4f4b8489947f080154d7fddcfd6e8e21d742a483c496fb7fbe59969d2
2023-09-16 17:11:44 +00:00
drh a91fe45339 Add a NEVER() to an unreachable branch.
FossilOrigin-Name: 6b6eb38979d68c06e382620c8813d6b67a3de02c4a7a029c84f924b9a2e380c6
2023-09-16 16:39:27 +00:00
drh 5bd96b2446 Fix minor code indentation inconsistencies. No function changes.
FossilOrigin-Name: 05107a0ae1456b746d3119af68d39532fad23a7eef88c09a9ef46ab7f8da4b9d
2023-09-16 16:23:20 +00:00
drh 5ddf0cbe25 Fix a harmless compiler warning.
FossilOrigin-Name: 9e9ff4ca3a205d0b599634782a5ad389fbe4823663550f48c54ca35dbc660dc7
2023-09-16 16:12:10 +00:00
dan 2e8edc1d45 Further tests and assert() statements for the change on this branch.
FossilOrigin-Name: 1c202d540ac362bfc747a9f8472e83c9d7614e38467f8b48787a669fb34664ba
2023-09-16 14:42:18 +00:00
dan 63702bccaa Fix resolution of unqualified "rowid" identifiers in queries with nested joins.
FossilOrigin-Name: bbcbd3d537d6790373d97f59386b8ce7fa2177db572af0f1babe058a76e25cc3
2023-09-15 20:57:05 +00:00
drh 124a6aa7d5 Simplifications and performance optimizations for the RTree extension.
FossilOrigin-Name: 04a333f5faf6b90592f1f69889ac6c28949955e186f39037cd639480b06feae8
2023-09-15 20:28:27 +00:00
drh ef2056e533 Drop support for the view-scan optimization (check-in [609fbb94b8f01d67]) as
it was causing multiple performance regressions.  In its place, reduce the
estimated row count for DISTINCT subsqueries by a factor of 8.

FossilOrigin-Name: f911f1c4977fbcae041243955cf2b98d8cc8baa337885a69be0f2b9bd2efa6f3
2023-09-15 19:51:18 +00:00
drh 58c10d9469 Minor simplification to the DISTINCT output row count change.
FossilOrigin-Name: 0738386d7c505a98768ddaff7ff1fc76f115882d92d6f84bf136b4aa2caad8b8
2023-09-15 19:27:36 +00:00
drh c09701db9b Do not reduce subquery output row count estimates due to DISTINCT until
after the decision of whether or not to use an index for ORDER BY has been
made.

FossilOrigin-Name: 27390051e86ad86fb35219329d359be9e83073f59782631af7fc519225e10565
2023-09-15 19:00:47 +00:00
dan 81b70d97eb Allow expressions like "<tbl>.rowid" to refer to implicit rowid columns of tables in nested FROM clauses.
FossilOrigin-Name: 59a1bbc69f5dbb33418fa4b383393fb13a46bc1e531577da8ad54ae2fad5a10e
2023-09-15 18:36:51 +00:00
drh 20220d4682 Experimental: Assume that a DISTINCT clause on a subquery reduces the number
of rows returned by that subquery by a factor of 8.

FossilOrigin-Name: 5a940e47d501f23347eaf084f4820e064e93665d19c8938c80715b71006ede8b
2023-09-15 16:15:33 +00:00
drh 658096e124 The view-scan optimization was added to enhance the performance of one specific
query, but it causes performance regressions on a host of others.  Disable it
(at least temporarily) in order to try to find an alternative way of fixing the
one specific query that it was created to fix - an alternative way that does
not cause performance problems for other unrelated queries.

FossilOrigin-Name: d9625a9eafe88859c3255849ee750cc6322d5a13ddad45107f9b2085c20eff50
2023-09-15 14:06:44 +00:00
drh 581b229362 Do not try to convert a double into an unsigned 64-bit integer, as that does
not work on all platforms.  A double can only be converted into a signed
64-bit integer.

FossilOrigin-Name: ce339046416e9ddeffe77f71a8bfb8c5b302160c3207f919aebd62902618ed59
2023-09-15 10:24:29 +00:00
dan c9823e3960 Tests for java versions of Fts5ExtensionApi.xSetAuxdata() and xGetAuxdata().
FossilOrigin-Name: 858fa236f8b673eb03cf2242e8202cd33ae1f13a43b59bdbb2eecdb21d42ef55
2023-09-14 20:56:42 +00:00
stephan a0f878959f JNI: move XTokenizeCallback interface out of the Fts5 object - that level of indirection is a holdover from when those classes were in the core package.
FossilOrigin-Name: 385ec08570451c0be212677d1b681c13d225dc690965effa2293e64cb3d56023
2023-09-14 20:02:49 +00:00
dan 93717c2306 Add a few more tests for Fts5ExtensionApi methods.
FossilOrigin-Name: 4ce2a7e8ed0f2859efc1038fd0812bc2b667be1479f45c081deccc2cf188c807
2023-09-14 20:01:18 +00:00
dan 9ba2d13ed3 Add a test for the java version of Fts5ExtensionApi.xRowid().
FossilOrigin-Name: 227facf879d2ade348cdb51f5b50ba2f95b8621acc0cf7a5fed4a310b4c76baa
2023-09-14 17:42:34 +00:00
drh e744f66008 According to [forum:/forumpost/9f6db917e1|forum post 9f6db917e1], older
MSVC compilers are unable to convert a double directly into an unsigned
long long int, but must first go through a signed long long int.  Work
around this by restricting the range of doubles that are converted into
long long integers so that only the lower 63 bits are used.

FossilOrigin-Name: 0ab05871ba36e2a0eddfbc700c1d39891c54c453727a4effcdac3327efec163b
2023-09-14 16:45:46 +00:00
drh e68decd54c Document the use of OPFLAG_ISNOOP as the P2 argument on OP_Delete. No
functional code changes.

FossilOrigin-Name: d88f41b2cf2b721170d2428a50b717976091389d9a07d0ffa15c3323b0eaad37
2023-09-14 16:02:56 +00:00
dan 82cdf4987d Avoid a segfault in test program showdb.c that could occur when analyzing a corrupt db.
FossilOrigin-Name: 6bf3e90687d48243544cc07baa01cb1b25ee0b77c59437f96c8da8e5d8b8095e
2023-09-14 14:05:51 +00:00
drh 60e53c57cf Improve the sqlite3IntFloatCompare() routine so that it is testable.
FossilOrigin-Name: bb221381fa38db5be1222d89c567a9bca0e6c441e26a8c797bd326b263556afb
2023-09-14 13:10:17 +00:00
drh dc03af67a4 Do not set the global bUseLongDouble flag based purely on sizeof(long double)
being more than 8.  That is a necessary but not sufficient condition to use
the long double routines.  Instead, do a run-time test to verify that long
double really does provide extra precision.

FossilOrigin-Name: aa999d490b743f455f2fb6c41009098330380f0bc2e9b1c28fbc82141a9138ce
2023-09-14 10:46:50 +00:00
drh 647e3ed2ba Reduce the number of calls to sqlite3_mprintf() made by RTree.
FossilOrigin-Name: f158b7d4917e0951fbb86a6f438abcb618d8602566fa54bf04c05a37f3a73513
2023-09-14 01:46:57 +00:00
stephan 595c3ecc94 JNI: enable the fts5-customization bits by default but elide them from the docs until it's clearer where they're headed. Unrelated minor doc tweaks.
FossilOrigin-Name: 21adb1aef24398fba918a5e1479ce2b6fd7e86d87eb9f2bea86a0eb43e8809c2
2023-09-13 21:32:20 +00:00
drh 23164c984e Improved comments on the hasHighPrecisionDouble() routine. No changes to
the underlying code.

FossilOrigin-Name: 810c635ce063d873e969bf83339c654f6008e84ce8a61f0ffc61806e98d13dde
2023-09-13 20:35:04 +00:00
drh 5b5d4492f2 Determine at start time whether or not the underlying hardware supports
high-precision long double computations.

FossilOrigin-Name: 9a854b919667e0e679a259542b2ee444ee416dbd73ecd9458f6ced35d9d3f264
2023-09-13 20:06:46 +00:00
drh 7de8ae22f7 Omit the Reinsert algorithm from RTree. This causes most benchmarks to run
faster, at the expense of having a slightly less dense and hence larger index
(example: 33 entries/node versus 34 entries/node).

FossilOrigin-Name: b3049a1d3dbdd63c471499c2f6b417655defe9ad90228e7cc722f5be877aae01
2023-09-13 17:30:12 +00:00
stephan 1eca330a08 Doc corrections for the previous checkin.
FossilOrigin-Name: 9ea0a9f39d03707d251af5af08d1ba94958704ba74019d7e8823ccd21936023c
2023-09-13 17:16:25 +00:00
stephan 181063d477 Add JNI sqlite3_prepare_multi(), based on feedback.
FossilOrigin-Name: fa1c1534724b03debc83ae35c2fadab83faf4b4e62b91981fed103888de41396
2023-09-13 17:11:32 +00:00
drh f9967cb302 Performance optimization to cellContains() in RTREE.
FossilOrigin-Name: 43cde22bf3f36687df231eddf642581d1d4f4102ad8568d31f5b2ff0302ca800
2023-09-13 14:07:07 +00:00
drh cdae00b3e8 In the ChooseLeaf algorithm of RTREE, do an initial pass through the cells
of a node looking for solutions that involve no cell enlargement.  Only look
for the minimum cell enlargement if the enlargement is non-zero for all cells.
This results in a performance improvement by reducing the number of calls
to cellUnion().

FossilOrigin-Name: 59f0e239d19393190911ea3bd89b5a24be0d85d64ccf51906efc9537ad5d7298
2023-09-13 13:12:08 +00:00
drh 386e359044 Reduce the number of calls to cellArea() in the ChooseLeaf() algorithm of
rtree.

FossilOrigin-Name: c633fe6be0edc07a0aa1ac819d58b6c629b7c95b47b63516ae1b0a937f6a2b89
2023-09-13 12:06:02 +00:00
drh f4ae37ca44 Remove unused variable from the ChooseLeaf algorithm in r-tree.
FossilOrigin-Name: fb2e4a8067edc29ba64973820f265b21dfee0d32f4d675d32100d5eef93c2830
2023-09-13 11:36:44 +00:00
dan fef1c11f92 Replace an if() condition in fts5 that is always true with an assert().
FossilOrigin-Name: 2170312c8d7f076cbb8319227de3fac981432dae186bc1928cd217e41119b580
2023-09-13 11:24:58 +00:00
larrybr 111815609c CLI .import to accept EOF in lieu of record terminator on last field of CSV (with multiple field records), per RFC 4180. [forum:5b21c25bdfa|forum post 5b21c25bdfa]
FossilOrigin-Name: 6f7842f577a28df1f809cd4bae9e8eafa26f2b54a25a1362ebbdebf5026be57c
2023-09-12 23:21:39 +00:00
dan 12439f9c16 Fix a use-after-free error in fts5 that could occur when querying the "rank" column immediately after another connection changes its definition. [forum:a2dd636330|forum post a2dd636330].
FossilOrigin-Name: cb54c2da52d3175844127ead41cfed18793e91ea52e02af2d57c1d17b4312437
2023-09-12 18:36:46 +00:00
stephan 0148013416 Clarify some metrics output in Tester1.java.
FossilOrigin-Name: 4ae7199a82f0b6e0f5575e39d8bd454e527ab949f0c59b773a44a078c65c5676
2023-09-12 17:49:25 +00:00
stephan 18e143d1d5 Re-add the fts5 decls to sqlite3-jni.h, removed in a rebuild without fts5 enabled. Need a better solution for this problem.
FossilOrigin-Name: 6f269790a09350f49103bb0925e2a78a1f9b7a8aad84408eabbb8d64ef79d551
2023-09-12 17:06:51 +00:00
stephan 09d72e4a6e Rename JNI sqlite3_errmsg() to sqlite3_errmsg16() for overall constency with the text()/text16() family of functions.
FossilOrigin-Name: c4a8d8a5711bd2abeaf1c31046784d1bccff4b98e96522250866d1ee8ec6a9fe
2023-09-12 17:03:40 +00:00
stephan 1110f8ffcd JNI readme fix.
FossilOrigin-Name: 7dde000efcf170c33701947d1aab71bb45b2b0ae8646ee41bb2b2adbee56d1e6
2023-09-12 16:33:57 +00:00
drh 50925d23ab 64-bit builds on Windows default to using UTF-8 output (as if the -utf8
command-line option had been specified.)  32-bit builds continue to use
Windows code pages.  There is also a new -no-utf8 command-line option to
force the use of legacy code pages.

FossilOrigin-Name: d8c7560e8822f336e8c4b300eb1f2e55203fdf9e756d2e8af719563235f79ebc
2023-09-12 14:36:07 +00:00
drh 09e96ea731 Show the "(utf8)" tag on the CLI banner in Windows if in UTF8 mode.
FossilOrigin-Name: 822699a4d9756ec68f17d6e06da7f994730bcf5c7d69ca15e9131721c4ff3b19
2023-09-12 14:09:45 +00:00
drh ceeb9eeaac Add the -no-utf8 option to the Windows CLI to cause UTF-8 mode to be omitted.
FossilOrigin-Name: 4cb799c690986b8bd38d07461998824fa53418f7fe31f59d0bf38cae328d9b89
2023-09-11 20:09:05 +00:00
drh 17ca53da26 Make the -utf8 option the default behavior in the CLI on Windows for 64-bit
builds.

FossilOrigin-Name: 8b3c306def81546c5987f0f245d2bf3960218254c2833e9c27cd242e9118db62
2023-09-11 20:02:38 +00:00
drh 833ed7ee64 Refuse to build amalgamation-tarball and snapshot-tarball if the TEA version
number disagrees with ./VERSION.

FossilOrigin-Name: 3308fdda4b81c110ba4a66d0b325e7653c2f8155e7864aeb78991ed1da061836
2023-09-11 15:27:27 +00:00
stephan 5c3a25b450 Merge in JNI's significant divergence since it adopted sqlite3_client_data().
FossilOrigin-Name: e8e07dbbeaa7799eb0b90726e18e994aaf8c8d316ab4bdb06da732bc51cf0919
2023-09-11 15:20:15 +00:00
drh a9d788f08f Add support for the sqlite3_get_clientdata() and sqlite3_set_clientdata()
interfaces, to better support JNI.

FossilOrigin-Name: 9806c0dd2802d68b67c25c4f3347ed532f9a98b051e775d34e9182dd2f099891
2023-09-11 14:55:05 +00:00
stephan 7fa8d65539 Roll back [84e38341aeab] because a direct pointer comparison is ill-advised when the client-data string is dynamically allocated.
FossilOrigin-Name: 7b884832b71c23e62ba3c0d53f7c89199734c351f909d84ac19ac18c7ddccbd8
2023-09-10 10:56:28 +00:00
dan ef2e43304e Remove out-of-date comment regarding use of Parse.pConstExpr.
FossilOrigin-Name: 71548f72ad578db3d6ee409956386ace23ab2258d37cdc29ef2fb126e48f1ee3
2023-09-09 17:53:55 +00:00
stephan 8f825a7a1b In the JNI build, emit a reminder to not check in the javac-generated sqlite3-jni.h when FTS5 is disabled because changes in that feature flag result in unnecessary and large diffs in checked-in generated code.
FossilOrigin-Name: caceb52fb58f924e7cbbebac05e7fd7822b7f6f56a3f834392d2be801bf04633
2023-09-09 11:20:35 +00:00
stephan 8ea10ea520 Disable FTS5 in the JNI build by default until it can be thoroughly tested. This is mainly so that the public-facing javadoc does not include it.
FossilOrigin-Name: 6f25f02b70f561864264a71a85cb5efa05550743371fd655804033ea08d00f33
2023-09-09 07:52:05 +00:00
dan 1b2d170a8f Add extra tests for [4b60a1c3].
FossilOrigin-Name: 6357491bd1b97f7abddf1a83860f3763cd870033bddc74bd9994a6440fe8c762
2023-09-08 19:18:02 +00:00
dan 410acf6944 Fix a problem with fts5 secure-delete mode causing fts5 to corrupt its records.
FossilOrigin-Name: 4b60a1c358c966f161601e749ccb0a7c5f4126e20350f4d6c83b6f35841fbe0a
2023-09-08 18:34:42 +00:00
dan fb77b3ed3f Add -DSQLITE_OMIT_LOOKASIDE=1 to the "Sanitize" configuration in testrunner_data.tcl.
FossilOrigin-Name: a917ebf17b27951d1c6906e9068e816ef29861701f2e8f4f1a13acce5737f680
2023-09-07 17:54:12 +00:00
drh 8525e2d931 Enhance the ./configure script and its associated Makefile.in so that the
--with-linenoise=DIR argument cause the linenoise command-line editing
library located in directory DIR to be linked with the sqlite3 CLI.

FossilOrigin-Name: 6c7822b554c01f01e3e02a385b3370313022b16ba8d932c2ba5d3c3be61361fe
2023-09-07 16:53:30 +00:00
dan 09f1652f36 Fix a buffer overread in the sessions extension that could occur when processing a corrupt changeset.
FossilOrigin-Name: 0e4e7a05c4204b47a324d67e18e76d2a98e26b2723d19d5c655ec9fd2e41f4b7
2023-09-07 13:53:09 +00:00
drh 0a4c31d87b Fix a harmless compiler warning in the sqldiff.c utility.
FossilOrigin-Name: e6390a656713b855258277b65066d2a701cedd05981f1672c9acc2d6dc37d032
2023-09-07 13:48:42 +00:00
drh 6e2b63893c Add the "tool-zip" makefile target with the intention of using it to build
the "sqlite-tool-*.zip" deliverables.
deliverables.

FossilOrigin-Name: 06e4af492d669bb45c00b1b876cd82ec8813ac9ed580eb1dddd18147f7fbe7f6
2023-09-07 13:43:46 +00:00
drh dd69129f2a Fix a problem with the new xIntegrity method for virtual tables, and also fix
a bad assert() in FTS3 that was found by the new xIntegrity method.

FossilOrigin-Name: 52bbf44f2d9addc2b5f68b0fe33542470852310ce3a283e2c7ff4c52831d0ed1
2023-09-07 02:13:01 +00:00
drh 93b461ff00 Add documentation to sqlite3_get/set_clientdata() to make it clear that these
are security-sensitive interfaces that should not be exposed to potential
attackers.

FossilOrigin-Name: 2837061d66672a0a73c3fd2ff35ce0e1a66ee6d5365be25d4cc0ccba170c3665
2023-09-06 17:07:53 +00:00
drh d4bb0896d4 Add the xIntegrity method to the sqlite3_module object, thus enabling
PRAGMA integrity_check to operate on virtual tables.  Make use of this
new method in the FTS3/4, FTS5, and RTREE virtual tables.

FossilOrigin-Name: 2f08e7d233b6ddc28b61f0d17ae9b815a91c3c6e080862ce009dc0ad73c64cf5
2023-09-06 16:51:13 +00:00
drh 9e5d21092d Fix unreachable branches.
FossilOrigin-Name: f00fc16394427f25249dd58c25a9115fef1ae389e232d574b4e91db2e7f8c04d
2023-09-06 16:22:48 +00:00
drh d5ab4dd9e4 Change the xIntegrity virtual table method signature so that it returns
an integer error code and writes the error message into a parameter.

FossilOrigin-Name: f1d4024a8ca06cf954aaf1f612684d1a5d28492bde757695db3f22c50c649709
2023-09-06 14:00:01 +00:00
drh 961c2a9f36 Add the xIntegrity method to the sqlite3_module object. Implement this
method in RTREE, FTS3/4, and FTS5 so that "PRAGMA integrity_check" also
verifies the correctness of shadow tables associated with those virtual
tables.

FossilOrigin-Name: 17bede8cdefd968210dd8a5a2617acbe12ba2c99fdd5e88c5def8665e7bec2d7
2023-09-06 12:52:00 +00:00
stephan 7c1588b3ad Move Java-side FTS5 bits into the fts5 subpackage to (A) make it easy to optionally bundle it (or not) and (B) set a precedent for puting extension APIs in their own package.
FossilOrigin-Name: 4c773e1f331cd5854d4c6e8825197933c0bb6827b7437e56ac0ad650d1ddf55e
2023-09-06 07:39:25 +00:00
stephan f31b9fcf86 Java FTS5 API renaming, for consistency.
FossilOrigin-Name: 5e4cc9f386364858c6457c40f13f01e1826a59086aa7f0fa10244ea846f5acfd
2023-09-06 06:54:25 +00:00
drh 5a05a68315 Fix a few SQLITE_MISUSE returns so that they call sqlite3MisuseError().
FossilOrigin-Name: 93f74490faf8cc07e107afdab6737c6e5141ae1f01a05142bfcede2dd1b2ba4e
2023-09-05 15:03:23 +00:00
dan dd24b1c8a1 Add tests for, and source code comments to, fts5. No changes to code.
FossilOrigin-Name: b12afff4efe4de84388d102060e8c312abd2f9eca8270b3c0f01ac6d1f2e329a
2023-09-04 16:48:31 +00:00
drh 25a6e6ec8b Do not make assumptions about the byteorder of PowerPC processors.
FossilOrigin-Name: 4a2498fed4c5436fbcd4179db85e2741fdab37d42b0eebf12f41ec4573ce2c61
2023-09-04 12:50:17 +00:00
stephan 0c072b698b In JNI routines which set an OutputPointer.value, always do so, even on error, for consistency.
FossilOrigin-Name: d0df62f0af19a69f7ceb3ad1dceda0d8cc2ca5016a6e2a3a1e36356424c3ca49
2023-09-04 06:15:37 +00:00
stephan 3aeb58c90c Remove several unnececessary functions.
FossilOrigin-Name: 84433c72b0b08e7906ef87758caffe98f95df812945a7aea675bbc20791e33c0
2023-09-04 05:58:13 +00:00
stephan 4eaf5883b3 Move an internal utility class out of the public Java API.
FossilOrigin-Name: 8e1ae3197a62796565b7260b230057bdb3f136edd00a83f8a63532d44d386243
2023-09-04 05:34:30 +00:00
stephan 411d9e7b2e Expose sqlite3_bind_parameter_name() to JNI. Extend the definition of null for the @NotNull annotation, to consider closed/finalized Java-side handles wrapping C-side resources as null for its purposes.
FossilOrigin-Name: fc34e2bfa4a2255f019a2514367e58596837289de49f7fab87279c8b40c7f119
2023-09-04 04:23:31 +00:00
stephan 14412e9526 Add the missing sqlite3_blob_read() to [9a9945c405cf]. Elaborate on the definition of the @NotNull annotation.
FossilOrigin-Name: 85952b4a52a09e9da35754c1a1ec70b1bf277296011a2d37ac536b33437223d7
2023-09-03 14:41:13 +00:00
stephan 805e8c96b9 Expose the sqlite3_blob_...() APIs to JNI.
FossilOrigin-Name: 9a9945c405cfe9b6bf399655f498457da66b06c8b92014510ab9fb83fbb1c7d4
2023-09-03 14:13:29 +00:00
stephan 7dfacc9f96 Expose sqlite3_system_errno() to JNI.
FossilOrigin-Name: bac54da29c19ef5e0a76599b5d10b377ecd7b391a651af99526529df16a83c7d
2023-09-03 12:46:09 +00:00
stephan 286f30f85c Expose sqlite3_stmt_status() to JNI.
FossilOrigin-Name: d266acc23ecb7e76c8c68c6e89a76e6f3054f33f0262e60e06b258db5a5e2ccd
2023-09-03 12:32:09 +00:00
stephan 1943356268 Expose sqlite3_randomness() to JNI.
FossilOrigin-Name: de9692242132b8f2c92ef4acb08dd3063327b18666cbb17c4f1153dee9146eaf
2023-09-03 12:17:34 +00:00
stephan 83a8b6dd82 Expose sqlite3_get/set_auxdata() to JNI.
FossilOrigin-Name: 0de3271717e0298070097d7ea0ecb996d2e95cf65384e494515a554d0a1dffed
2023-09-03 11:58:33 +00:00
stephan e30436b605 Expose sqlite3_get_autocommit() to JNI.
FossilOrigin-Name: 67391c6588c71af411767d1e4b63c5041a8a9e669102da318e3482ccfc9d9bb9
2023-09-03 11:16:48 +00:00
stephan ce9902f8c2 Remove some unnecessary code.
FossilOrigin-Name: c7f9bbbbf0252c9749450e3ec994bf5ac945bd62428657ddff469291a0fd4b0d
2023-09-03 11:08:31 +00:00
stephan 54021c3bd0 Add a couple more sqlite3_backup tests.
FossilOrigin-Name: 5c2cf89d2c3ae5fc31b5e311bc2f2c1fef0037bdb0dff48c0a2a6bb6610e0215
2023-09-03 10:36:21 +00:00
stephan 24a689cf01 Export the sqlite3_backup_...() APIs to JNI.
FossilOrigin-Name: 865a3df5d550f6f4edbf95854750706e4e747d8f1825ae73d300b8e31934959b
2023-09-03 10:23:03 +00:00
stephan 2966b85df3 Export the sqlite3_keyword_...() family of functions to JNI.
FossilOrigin-Name: bd554db39159d8a538ce913d55285e3c417db8823c717a3e34bfa0678de42be7
2023-09-03 09:28:45 +00:00
drh d3c5471a9a Performance optimization to the OP_MakeRecord opcode.
FossilOrigin-Name: 2aef9af3dd493a82ba090ccb8ab1b8974349392ec3f1c35db7a3dbbec41b5c51
2023-09-02 19:35:15 +00:00
stephan de4d1c357f Add sqlite3_config(SQLITE_CONFIG_LOG) support to Java and correct clearing of SQLITE_CONFIG_SQLLOG. Correct missing names in propagated UDF exception messages.
FossilOrigin-Name: 652e2e82100ac3d8238d15ef6d43de4162fd02d695db4936ad5706fe4f2d7dc3
2023-09-02 14:36:41 +00:00
stephan 7d31de600b Expose sqlite3_complete() to JNI. Slightly widen the definition of "canonical" for the purposes of the @Canonical annotation. Add a few new tests.
FossilOrigin-Name: a59fe0cd9552128ca4186f4e66f0d188f2a57c7647f43828e42cb95801f350ce
2023-09-02 12:53:25 +00:00
stephan 216433636d Duplicate code consolidation.
FossilOrigin-Name: a1872ae959cca11d5d8bc224aaeb0b53e772d999df9924b2e9ea84ceedb99148
2023-09-02 11:26:36 +00:00
stephan 70b58fc119 Remove a superfluous level of indirection in the JNI internals.
FossilOrigin-Name: 8dca6f7660c15eacbda20da1c66c9ef1de36864f78750658226b1a7baf22b726
2023-09-02 10:18:10 +00:00
stephan 63f4a9c4db Further JNI cleanups.
FossilOrigin-Name: 30e38173c3ece0c9f8e7a9710f46cb5e8e8ef101c04531318a7adb070242f5dd
2023-09-02 08:51:14 +00:00
stephan 21d4f0cc7b Optimize sqlite3_get/set_clientdata() for the presumably common case of a static string pointer.
FossilOrigin-Name: 84e38341aeab4fd51690e8536d0101d00e3fb11cc50ebcff05ed219c98328872
2023-09-02 08:04:56 +00:00
stephan 49a40ef6d2 Add a timer to the JS SQLTester app to warn if it appears that loading the module has failed, which is commonly caused by the test scripts not being compiled.
FossilOrigin-Name: f9c1f9cad1ad22f689a4afa13d59bf9969ddaed6624cbc16cb1bf5d1fd0c8a5b
2023-09-01 17:51:09 +00:00
stephan a5f425cc6e Correct a Tester1.java test which inadvertently created a spurious db file in the current dir. Unrelated minor cleanups.
FossilOrigin-Name: 016bbdadbf9f95cef185d2606c23e3b3c724586fcca470e11e6fbdbaefd75c79
2023-09-01 16:28:46 +00:00
stephan 4ea63e5609 Make the JNI-internal metrics opt-in rather than opt-out so client builds won't have them by default. Unrelated doc tweaks.
FossilOrigin-Name: aa72d25a22c80a7a376127266b8923f0661147c8bc483baa327230364851a255
2023-09-01 14:20:39 +00:00
stephan d086065644 Make JNI interfaces of sqlite3_column/value_text() match the C ones better. Internal JNI cleanups and simplifications.
FossilOrigin-Name: eb24f97253d9c8e7c728c2ac67a0824b10ca67362ac4f8abb94d5d3c54c58c5b
2023-09-01 13:27:59 +00:00
stephan dba0ceea60 Second half of [1c532e80].
FossilOrigin-Name: 8ca1d815a57be1fade59fb8ea5705c27b10294e4959c8a9c624f1623df6a5f63
2023-09-01 11:10:09 +00:00
stephan f551aa4e80 In the OPFS VFS' importDb() methods, overwrite the header bytes 18 and 19 with 1 instead of 0. Both seem to work, but 1 is correct.
FossilOrigin-Name: 1c532e807bf8466b67d9600ab9630a6736de77259e9a71ac435641715ee0e04b
2023-09-01 11:05:22 +00:00
stephan 7862e19468 Remove some newly-dead code.
FossilOrigin-Name: dc0fa76b395c5e352116dc33cc1b455e5e9c9f01c285af52ccba4d37e4453b87
2023-09-01 10:37:15 +00:00
stephan 03ca59afb9 Expose sqlite3_limit() to JNI and remove the all SQLITE_ macros from the Java interface which are not directly exposed to client-level C code (SQLITE_MAX_... and SQLITE_THREADSAFE_...).
FossilOrigin-Name: 5fe5b0a585dbfa06cfca1fea40aea5ea3ccfe8295f41306ad2d5b851ace27203
2023-09-01 10:32:31 +00:00
stephan 55171a75ef JNI build improvements. Rename a Java class. Get the jar target working again.
FossilOrigin-Name: d086b7844cace5c997261c97565aeef62aaeeef727ccc7e83f17c54d6217b779
2023-09-01 09:43:56 +00:00
stephan a4b47b034c Expose sqlite3_stmt_explain(), sqlite3_stmt_isexplain(), and sqlite3_stmt_readonly() to JNI.
FossilOrigin-Name: 208b786afe16eafaf0ce791f319a5e05f733a7b71ce1c542e1b83481b013ec38
2023-09-01 06:50:17 +00:00
stephan 0d3f0a9c11 Export sqlite3_txn_state() to JNI.
FossilOrigin-Name: d732f71d0a292dbb493f79f7c6ecd8a4effbfbc91453b1c54bdd9becf2d75bdb
2023-08-31 19:35:59 +00:00
stephan 63e7ed3b44 Minor JNI cleanups.
FossilOrigin-Name: 2e2bb841ef8b53266ec48d8c3408d6396f29f17922e858eac1b4f12f4adeb05e
2023-08-31 18:34:43 +00:00
dan 8628c10e27 Do not disable unused columns in a UNION ALL sub-query if any component of the sub-query is DISTINCT.
FossilOrigin-Name: c84d5602ac9bfb4f12c3cf62033af616e51913c26877d1a0761363a625295f53
2023-08-31 18:00:10 +00:00
stephan 062bafb851 Add convenience overloads of JNI sqlite3_table_column_metadata() to simplify usage.
FossilOrigin-Name: faf4e6d398f444d970be8b1364c984c67ccbc5f8a27f3aa93f05b8a8de7bec9b
2023-08-31 15:24:46 +00:00
stephan 0c2ba994d2 Export sqlite3_(db_)free_memory() and sqlite3_table_column_metadata() to JNI. Further internals renaming for consistency and legibility.
FossilOrigin-Name: 7c86aa3400ed591d38c1828f366f4b5de97954c2b301919d3f06d9c2d3d7d1f2
2023-08-31 14:57:01 +00:00
drh 3823208d5b Use mutexes to make sqlite3_set_clientdata() and sqlite3_get_clientdata()
threadsafe.

FossilOrigin-Name: 443ea20ddb0f3bf5d77ef59cd4678f0e32d7da328002bb44d6fc080a53a37e29
2023-08-30 18:51:26 +00:00
stephan 7fc4223411 Replace JNI's per-db-instance state tracking with the new sqlite3_set/get_clientdata().
FossilOrigin-Name: e7c11d34ee2eebdca4d9db1496bbb4152e4c62745c083ad5e0337733e8d1254e
2023-08-30 18:45:42 +00:00
drh a7ec1f9a1b Fix a bug in json_array_length() introduced in version 3.43.0 by
check-in [df099ad713011b67].  If the JSON input comes from json_remove(),
the removed array entries are still counted as part of the array length.

FossilOrigin-Name: 69a635958a4a14187e88dd297df8978a4805b1b0c7bff3ec29d5632c0f68d889
2023-08-30 18:19:40 +00:00
drh d25d938877 Remove another unreachable branch in the sqlite3_set_clientdata() logic.
FossilOrigin-Name: 57e31e6b10b2aa68b6039914d191819bae0aa60999a99d5ef23ca18128b72e0e
2023-08-30 17:41:55 +00:00
drh a5af4a6614 Remove an unreachable branch, and improve documentation of
sqlite3_set_clientdata().

FossilOrigin-Name: 565c68adee64b6c838f2fcb162485b6cfe9a976adb79de4370be98fcd4af2baf
2023-08-30 17:14:12 +00:00
drh 7bb5a6db40 Fix the AVG() and TOTAL() functions (after the SUM() fix of [77d3dcd283595c52])
so that they work with infinitites.  Fixes a bug introduced by check-in.
[c63e26e705f5e967].  Bug reported by
[forum:/forumpost/8960fb40cc|forum post 8960fb40cc].

FossilOrigin-Name: 6df6f17ccb404c648076ccff4200d0eb5437f0e3e82424bf3da5ea682b107bb4
2023-08-30 16:03:27 +00:00
stephan 85a05d895a Further tweaking in both SQLTesters.
FossilOrigin-Name: 46b0eb98e7a56cc188836c46f4289de6281f80e6bc6eb33e0aa9133a0f86f00c
2023-08-30 15:51:42 +00:00
drh adf54de51e Fix to the sqlite3_get_clientdata() interface.
FossilOrigin-Name: 937cd1762b36d48d9e7bafe42e99e524c9b5528d39f64521bce2f81d9c887db8
2023-08-30 15:42:41 +00:00
drh 10deb35995 New experimental API for attaching client data to a database connection.
FossilOrigin-Name: d542837fdb42ebe810fc99225860d2cc7e6dd829a635bde820a09beff6bcb481
2023-08-30 15:20:15 +00:00
stephan 8dd07389ac When a JS SQLTester script throws, report the exception details back to the UI regardless of whether it's fatal.
FossilOrigin-Name: 273d3b05f630d399d42914e95c416b107b4746bbef129cfba9d00fd921666261
2023-08-30 14:20:02 +00:00
stephan 24c32c2e39 Add a JS implementation of Java's SQLTester.
FossilOrigin-Name: b530792a514d95c4e8f93cf2170d9fc4de367055fa1704fc171551c946024fa9
2023-08-30 13:07:35 +00:00
stephan e621556724 Add a UI, of sorts, to the JS SQLTester.
FossilOrigin-Name: 249e82b9917ea47c56ee1cbd3345a977d335fd3fc0d67a1ef157813ef4571c7c
2023-08-30 11:54:43 +00:00
stephan ac5e1f82ce Add a mechanism with which to import external SQLTester scripts into the JS testing tool.
FossilOrigin-Name: bb08ba020ce1d86ca6aa92f43d5ae915f67d08fa73120e1f603d150e76166624
2023-08-30 00:22:54 +00:00
stephan 4f1387e9ab Add directives support to JS SQLTester comparable to the Java impl. This brings the two to feature parity.
FossilOrigin-Name: 7cef4a8300826adbdcb3b205e134a4272b12b4aa7dbee97731ac12282a4a9f06
2023-08-29 21:30:37 +00:00
stephan 267c44771f More fleshing out of JS SQLTester.
FossilOrigin-Name: 8c503dfb9fa15389613a819fcc1792e23d3c05f99a9f450f82eac5125298726f
2023-08-29 20:44:40 +00:00
stephan aa15047796 JS SQLTestRunner can now run the Java impl's core-most sanity tests, missing only support for directives.
FossilOrigin-Name: 5e798369375ce1b0c9cdf831f835d931fbd562ff7b4db09a06d1bdca2ac1b975
2023-08-29 20:01:01 +00:00
drh a910f3f1c5 New conversion letters added to strftime(): %e, %k, %I, %l, %R, %P, %p, %T, %u.
FossilOrigin-Name: 058722b2d0b995195a8ce3effe9722ae1c18cb1e7a520b481030da0bd579fe41
2023-08-29 18:28:08 +00:00
stephan 69a55ca17d Get the JS SQLTester command handlers in place sans those which have to run SQL.
FossilOrigin-Name: d21b1217964a53f33b7ba3958b34aa8560dff8ede33e66f54aa0afbab7099ec3
2023-08-29 15:39:57 +00:00
drh 9d6acd960f Change the version number to 3.44.0 to begin the next development
cycle.  (Should have done this a few check-ins ago.)

FossilOrigin-Name: 253fe4a45decdcc4180c5aeccaf4cd0d1325a0f6df0913b0e6721ea3e26b4fe2
2023-08-29 15:38:54 +00:00
drh e1e67abc5c Add support for the CONCAT() and CONCAT_WS() SQL functions, modeled after
the PostgreSQL behavior.

FossilOrigin-Name: 0b434ca7aa19eff4ad134a8c6f88f6a7ccab88864faa55e93579a2462d8ac3bc
2023-08-29 15:24:41 +00:00
stephan 0fc20a32c0 Get the basic parsing pieces and command dispatching in place in the JS SQLTester.
FossilOrigin-Name: 8fcc2a553c1e26734902bbdee0c38183ee22b7b5c75f07405529bb79db34145a
2023-08-29 13:28:36 +00:00
stephan 524ddc940d Init bits of a port of Java's SQLTester to JS. Far from complete.
FossilOrigin-Name: 60eec5ceda80c64870713df8e9aeabeef933c007f2010792225a07d5ef36baef
2023-08-29 11:22:45 +00:00
dan b44b802378 Change a variable from "int" to "i64" to make it easier to prove that it cannot overflow.
FossilOrigin-Name: 00a8b3a263f3537588063ce42fad6e21fa343dad850b086d0929ed1617eb44fc
2023-08-29 10:50:11 +00:00
stephan d10ed826eb Remove some dead code. Add a skeleton SQLTester script for fts5.
FossilOrigin-Name: 6c83e31fa96f65b61377c0c801cc32b3c8ca27a0c8442f860364bec258c003cb
2023-08-29 00:10:31 +00:00
stephan 4e379078e7 Factor out a superfluous struct member.
FossilOrigin-Name: 76d3911c370b3dc02d119045003f87ad20a6efd9f7a44d4feb59b7c801ac8981
2023-08-28 23:18:19 +00:00
stephan 924c4545d3 Further minor internal JNI simplifications.
FossilOrigin-Name: 1808d12ee0d1f1e5ee49d48c699ca10c4f822989ac9b4ac08f2b861513ee5997
2023-08-28 22:52:04 +00:00
stephan fa23b4fc61 Improve threadability of the JNI collation-related bindings and add infrastructure for similar cases.
FossilOrigin-Name: f02dad66b965b9e3c504001e9603af8f74977f151bede9db369f88e86a4aeb00
2023-08-28 21:27:32 +00:00
stephan a08f737503 Correct JNI mapping of collations to be 1-db-to-many-collations.
FossilOrigin-Name: b927b0f5a68684b0a9799396d153bf1e2306351e8039c2bacb3d5b2056a0634f
2023-08-28 20:21:56 +00:00
dan 35c7f538e9 Updates to testrunner.tcl so that it runs fuzztest using multiple jobs.
FossilOrigin-Name: ceeabe9f8b31a30c65147fd270b92d43c7842247548cee9de165113991f6c2cf
2023-08-28 20:14:19 +00:00
stephan ece2d7f2be Remove a pair of what are arguably unnecessary mutex locks (and often hit). More JNI-internal cleanups.
FossilOrigin-Name: ecf07a0144dc6402b1e0924b1775d99dc465b27aa86a2718cac60a9b4c974312
2023-08-28 16:22:31 +00:00
stephan 8d64c12a10 Update tool/mkctimec.tcl to account for [fe7365254d343e].
FossilOrigin-Name: 349aac7e8d513bc420e8948b84cf715e454443439fdcd5aff0f2b13815a9cbb3
2023-08-28 16:05:19 +00:00
drh 57b1800773 Add support for the -DSQLITE_EXTRA_AUTOEXT=name compile-time option.
[forum:/forumpost/00829394c74a670f|forum thread 00829394c74a670f].

FossilOrigin-Name: fe7365254d343ed03e11a4e9cad7f0e5d5182b9220c6fde6d30e434ebdaba2af
2023-08-28 15:58:00 +00:00
stephan 25e7d8af75 Duplicate code consolidation.
FossilOrigin-Name: 71f239747c7934310dedf9fc0cbf84fbeeed53808234067147335c12396849a1
2023-08-28 14:59:27 +00:00
stephan bdb149d063 Move a metrics counter so that it can be used to indirectly witness the race condition fix from [f5274e00f17d58e0] by recording varying final values for that metric across consecutive multi-threaded test runs.
FossilOrigin-Name: 54d2209d24547dbb05e7c12daa27211593c34de8005e8adba0989b8d219f5f3b
2023-08-28 13:18:46 +00:00
stephan 4e9b48cfc4 Resolve a JNI-side race condition. Removed a now-extraneous struct member. Internal API renaming for consistency.
FossilOrigin-Name: f5274e00f17d58e075f90ae5c1d4b38933da315e51592171fa35bcbd67b40b2a
2023-08-28 13:06:26 +00:00
drh bd953dfcad Fix an issue with infinity handling by the SUM() function that goes back
to the extended-precision SUM() enhancement of
[check-in c63e26e705f5e967].  Problem reported by
[forum:/forumpost/1c06ddcacc86032a|forum post 1c06ddcacc86032a].

FossilOrigin-Name: 77d3dcd283595c52f24c07fc59ba60c9133b71c440cf3f799cf48c907c6fae3e
2023-08-28 12:20:18 +00:00
stephan 0d066bc8a6 Resolve a condition which could cause an is-interrupted db to call its progress callback.
FossilOrigin-Name: a0d0f1aafc6086726131dff5e6628f2771c20db3122a53bdbb82945ab5d326d1
2023-08-28 12:06:38 +00:00
stephan f0e9e59d8d Further JNI cleanups and javadoc additions.
FossilOrigin-Name: c393b1c9f5639c259406e587b3e08efdc1962203001823747681379ffc54fc10
2023-08-28 11:10:13 +00:00
stephan 2597ec6385 Lots of javadoc-related tweaks.
FossilOrigin-Name: cfe06f90e2c0231efded98767ef3cc646d3a7daa34d77b62b7c04b5aae9448fd
2023-08-28 07:28:36 +00:00
stephan 23dfa67c1e Correct a package renaming bug in the previous check-in and start applying the @Canonical annotation to functions to distinguish canonical API functions and Java-specific APIs.
FossilOrigin-Name: 8a016006805b08b72bfc4093c795d8cd8d7fe72f8ae234c175a8b7be3a841cbf
2023-08-28 05:48:34 +00:00
stephan b041f8ffdd Move the JNI annotations into the annotation subpackage.
FossilOrigin-Name: e4dedf90a92a069daef967dfe975469bf8ec7883c44c95e73345d4eded48e996
2023-08-28 05:39:05 +00:00
stephan 56d3b21317 Resolve a makefile bug which causes sqlite3-worker1-bundler-friendly.mjs to be built incorrectly. Reported in [forum:a874e435cf4690c1|forum post a874e435cf4690c1].
FossilOrigin-Name: 9d68c7ef103b9b51ed35fb91e62351eb6c62ad63ab03ac456ff9787cc83f10ab
2023-08-28 04:59:16 +00:00
stephan 5020ddc243 Reimplement JNI's sqlite3_value_text() as a native instead of a Java-side proxy. Unrelated minor simplifications.
FossilOrigin-Name: 401dd6ee283a4e7da2e50175a1b58e6a83afb0ea2d3b6cf80c7c621e17bcce98
2023-08-27 15:15:46 +00:00
stephan 0639c58836 Generic cleanups in Tester1.java.
FossilOrigin-Name: fbc1b6310b01f4f79439bb6ce59b2aaabd126da7201f36d4fc5d7ef95118a494
2023-08-27 14:47:45 +00:00
stephan 5575d6421b Remove JNI sqlite3_column_java_object(), as the protection rules of sqlite3_values makes it impossible to implement safely. Add JNI sqlite3_bind_java_object().
FossilOrigin-Name: 29bd4a23a4afd96b2cc06d2b91a4f30c0bbf2347af0b0d18f8d4cf8aafa63160
2023-08-27 13:43:45 +00:00
stephan 88bd53dfd0 Correct the signature mismatch between JNI sqlite3_column/value_text16() and add related tests.
FossilOrigin-Name: 77f6e70f17c0cb6d031f983c458c9ec2e88d92b4716397533a029af39da2d128
2023-08-27 11:28:57 +00:00
stephan 95f5d85d4a Make JNI sqlite3_trace_v2() thread-safe. Re-add a piece removed in [bae4d022aad9b] to work around a JVM crash which is unpredictably triggered by its substitute. Fix the THREADMODE=0 JNI build. Further internal API simplifications.
FossilOrigin-Name: 3f9f7a9cb08b0687ad206605a5109306762df9ae8bdeab2d8d60bf9373c9ad32
2023-08-27 10:40:00 +00:00
stephan 32a79760b5 Factor out an unnecessary struct member. JNI sqlite3_shutdown() now frees up the various object-recycling bins. Doc touchups.
FossilOrigin-Name: bae4d022aad9bbeb78cb027ecad799af87afe331e697add44ec22297c873141d
2023-08-27 09:12:50 +00:00
stephan 60aca33a8b Factor out a superfluous JNI class. Doc and code style cleanups.
FossilOrigin-Name: 0f37f27148dfa93ecc42381ad3455a9059285d1af2df027429044942dc4d861b
2023-08-27 08:10:59 +00:00
stephan 0f4bf3435a JNI code reorgs and simplify the failing-alloc interface a bit.
FossilOrigin-Name: deed5797de65a25896e991a441f0d05eb92662536296485920fb081e84ad5d32
2023-08-27 07:26:33 +00:00
stephan c7e7c88873 Apply the JNI OOM checks to memory returned by JDK APIs, as distinct from our APIs.
FossilOrigin-Name: 1ff78582bfd934e0c76464b5f23ed9bf09a3491b145e0ca34acb6e59c4f53995
2023-08-26 22:34:26 +00:00
stephan 0deaea218d Eliminate a superfluous JNI-internal middle-man class.
FossilOrigin-Name: daede0f801f59d6501a863c4688e4635b34171e98b56b8ab4432c779113f1997
2023-08-26 21:13:57 +00:00
stephan 95484726f6 Add SQLITE_EXTRA_AUTOEXT, similar to SQLITE_EXTRA_INIT but adds a builtin auto-extension provided by the client. Suggestion from [forum:00829394c74a670f| forum post 00829394c74a670f].
FossilOrigin-Name: 423e77277a61d7febf4c3fc737981fa22a82b5c774a8ada5375a01a0611535b2
2023-08-17 09:49:53 +00:00
146 changed files with 10584 additions and 3551 deletions
+6
View File
@@ -57,6 +57,7 @@ LIBTCL = @TCL_LIB_SPEC@
#
READLINE_FLAGS = -DHAVE_READLINE=@TARGET_HAVE_READLINE@ @TARGET_READLINE_INC@
READLINE_FLAGS += -DHAVE_EDITLINE=@TARGET_HAVE_EDITLINE@
READLINE_FLAGS += -DHAVE_LINENOISE=@TARGET_HAVE_LINENOISE@
# The library that programs using readline() must link against.
#
@@ -1468,6 +1469,11 @@ amalgamation-tarball: sqlite3.c sqlite3rc.h
snapshot-tarball: sqlite3.c sqlite3rc.h
TOP=$(TOP) sh $(TOP)/tool/mkautoconfamal.sh --snapshot
# Build a ZIP archive containing various command-line tools.
#
tool-zip: testfixture sqlite3 sqldiff sqlite3_analyzer tool/mktoolzip.tcl
./testfixture $(TOP)/tool/mktoolzip.tcl
# The next two rules are used to support the "threadtest" target. Building
# threadtest runs a few thread-safety tests that are implemented in C. This
# target is invoked by the releasetest.tcl script.
+3
View File
@@ -2466,6 +2466,9 @@ extensiontest: testfixture.exe testloadext.dll
@set PATH=$(LIBTCLPATH);$(PATH)
.\testfixture.exe $(TOP)\test\loadext.test $(TESTOPTS)
tool-zip: testfixture.exe sqlite3.exe sqldiff.exe sqlite3_analyzer.exe tool\mktoolzip.tcl
.\testfixture.exe $(TOP)\tool\mktoolzip.tcl
coretestprogs: $(TESTPROGS)
testprogs: coretestprogs srcck1.exe fuzzcheck.exe sessionfuzz.exe
+1 -1
View File
@@ -1 +1 @@
3.43.0
3.44.0
+1 -1
View File
@@ -19,7 +19,7 @@ dnl to configure the system for the local environment.
# so that we create the export library with the dll.
#-----------------------------------------------------------------------
AC_INIT([sqlite],[3.43.0])
AC_INIT([sqlite],[3.44.0])
#--------------------------------------------------------------------
# Call TEA_INIT as the first TEA_ macro to set up initial vars.
Vendored
+48 -24
View File
@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.69 for sqlite 3.43.0.
# Generated by GNU Autoconf 2.69 for sqlite 3.44.0.
#
#
# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
@@ -726,8 +726,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='sqlite'
PACKAGE_TARNAME='sqlite'
PACKAGE_VERSION='3.43.0'
PACKAGE_STRING='sqlite 3.43.0'
PACKAGE_VERSION='3.44.0'
PACKAGE_STRING='sqlite 3.44.0'
PACKAGE_BUGREPORT=''
PACKAGE_URL=''
@@ -776,6 +776,7 @@ OPT_FEATURE_FLAGS
HAVE_ZLIB
USE_AMALGAMATION
TARGET_DEBUG
TARGET_HAVE_LINENOISE
TARGET_HAVE_EDITLINE
TARGET_HAVE_READLINE
TARGET_READLINE_INC
@@ -903,6 +904,7 @@ enable_editline
enable_readline
with_readline_lib
with_readline_inc
with_linenoise
enable_debug
enable_amalgamation
enable_load_extension
@@ -1470,7 +1472,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
\`configure' configures sqlite 3.43.0 to adapt to many kinds of systems.
\`configure' configures sqlite 3.44.0 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1535,7 +1537,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of sqlite 3.43.0:";;
short | recursive ) echo "Configuration of sqlite 3.44.0:";;
esac
cat <<\_ACEOF
@@ -1587,6 +1589,7 @@ Optional Packages:
(tclConfig.sh)
--with-readline-lib specify readline library
--with-readline-inc specify readline include paths
--with-linenoise=DIR source directory for linenoise library
Some influential environment variables:
CC C compiler command
@@ -1665,7 +1668,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
sqlite configure 3.43.0
sqlite configure 3.44.0
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2084,7 +2087,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by sqlite $as_me 3.43.0, which was
It was created by sqlite $as_me 3.44.0, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
@@ -3942,13 +3945,13 @@ if ${lt_cv_nm_interface+:} false; then :
else
lt_cv_nm_interface="BSD nm"
echo "int some_variable = 0;" > conftest.$ac_ext
(eval echo "\"\$as_me:3945: $ac_compile\"" >&5)
(eval echo "\"\$as_me:3948: $ac_compile\"" >&5)
(eval "$ac_compile" 2>conftest.err)
cat conftest.err >&5
(eval echo "\"\$as_me:3948: $NM \\\"conftest.$ac_objext\\\"\"" >&5)
(eval echo "\"\$as_me:3951: $NM \\\"conftest.$ac_objext\\\"\"" >&5)
(eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out)
cat conftest.err >&5
(eval echo "\"\$as_me:3951: output\"" >&5)
(eval echo "\"\$as_me:3954: output\"" >&5)
cat conftest.out >&5
if $GREP 'External.*some_variable' conftest.out > /dev/null; then
lt_cv_nm_interface="MS dumpbin"
@@ -5154,7 +5157,7 @@ ia64-*-hpux*)
;;
*-*-irix6*)
# Find out which ABI we are using.
echo '#line 5157 "configure"' > conftest.$ac_ext
echo '#line 5160 "configure"' > conftest.$ac_ext
if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
(eval $ac_compile) 2>&5
ac_status=$?
@@ -6679,11 +6682,11 @@ else
-e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
-e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
-e 's:$: $lt_compiler_flag:'`
(eval echo "\"\$as_me:6682: $lt_compile\"" >&5)
(eval echo "\"\$as_me:6685: $lt_compile\"" >&5)
(eval "$lt_compile" 2>conftest.err)
ac_status=$?
cat conftest.err >&5
echo "$as_me:6686: \$? = $ac_status" >&5
echo "$as_me:6689: \$? = $ac_status" >&5
if (exit $ac_status) && test -s "$ac_outfile"; then
# The compiler can only warn and ignore the option if not recognized
# So say no if there are warnings other than the usual output.
@@ -7018,11 +7021,11 @@ else
-e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
-e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
-e 's:$: $lt_compiler_flag:'`
(eval echo "\"\$as_me:7021: $lt_compile\"" >&5)
(eval echo "\"\$as_me:7024: $lt_compile\"" >&5)
(eval "$lt_compile" 2>conftest.err)
ac_status=$?
cat conftest.err >&5
echo "$as_me:7025: \$? = $ac_status" >&5
echo "$as_me:7028: \$? = $ac_status" >&5
if (exit $ac_status) && test -s "$ac_outfile"; then
# The compiler can only warn and ignore the option if not recognized
# So say no if there are warnings other than the usual output.
@@ -7123,11 +7126,11 @@ else
-e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
-e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
-e 's:$: $lt_compiler_flag:'`
(eval echo "\"\$as_me:7126: $lt_compile\"" >&5)
(eval echo "\"\$as_me:7129: $lt_compile\"" >&5)
(eval "$lt_compile" 2>out/conftest.err)
ac_status=$?
cat out/conftest.err >&5
echo "$as_me:7130: \$? = $ac_status" >&5
echo "$as_me:7133: \$? = $ac_status" >&5
if (exit $ac_status) && test -s out/conftest2.$ac_objext
then
# The compiler can only warn and ignore the option if not recognized
@@ -7178,11 +7181,11 @@ else
-e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
-e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
-e 's:$: $lt_compiler_flag:'`
(eval echo "\"\$as_me:7181: $lt_compile\"" >&5)
(eval echo "\"\$as_me:7184: $lt_compile\"" >&5)
(eval "$lt_compile" 2>out/conftest.err)
ac_status=$?
cat out/conftest.err >&5
echo "$as_me:7185: \$? = $ac_status" >&5
echo "$as_me:7188: \$? = $ac_status" >&5
if (exit $ac_status) && test -s out/conftest2.$ac_objext
then
# The compiler can only warn and ignore the option if not recognized
@@ -9558,7 +9561,7 @@ else
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
lt_status=$lt_dlunknown
cat > conftest.$ac_ext <<_LT_EOF
#line 9561 "configure"
#line 9564 "configure"
#include "confdefs.h"
#if HAVE_DLFCN_H
@@ -9654,7 +9657,7 @@ else
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
lt_status=$lt_dlunknown
cat > conftest.$ac_ext <<_LT_EOF
#line 9657 "configure"
#line 9660 "configure"
#include "confdefs.h"
#if HAVE_DLFCN_H
@@ -11245,6 +11248,27 @@ fi
fi
fi
# Check whether --with-linenoise was given.
if test "${with_linenoise+set}" = set; then :
withval=$with_linenoise; with_linenoise=$withval
else
with_linenoise="no"
fi
if test "x$with_linenoise" != "xno"; then
TARGET_HAVE_READLINE=0
TARGET_HAVE_EDITLINE=0
TARGET_HAVE_LINENOISE=1
TARGET_READLINE_INC="-I${with_linenoise}"
TARGET_READLINE_LIBS="${with_linenoise}/linenoise.c"
echo "using linenoise source code at ${with_linenoise}"
else
TARGET_HAVE_LINENOISE=0
echo "not using linenoise"
fi
@@ -11321,7 +11345,7 @@ fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build type" >&5
$as_echo_n "checking build type... " >&6; }
if test "${enable_debug}" = "yes" ; then
TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0"
TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0 -Wall"
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: debug" >&5
$as_echo "debug" >&6; }
else
@@ -12457,7 +12481,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
This file was extended by sqlite $as_me 3.43.0, which was
This file was extended by sqlite $as_me 3.44.0, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -12523,7 +12547,7 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
sqlite config.status 3.43.0
sqlite config.status 3.44.0
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
+18 -1
View File
@@ -598,11 +598,28 @@ if test x"$with_readline" != xno; then
TARGET_HAVE_READLINE=1
fi
fi
AC_ARG_WITH([linenoise],
[AS_HELP_STRING([--with-linenoise=DIR],[source directory for linenoise library])],
[with_linenoise=$withval],
[with_linenoise="no"])
if test "x$with_linenoise" != "xno"; then
TARGET_HAVE_READLINE=0
TARGET_HAVE_EDITLINE=0
TARGET_HAVE_LINENOISE=1
TARGET_READLINE_INC="-I${with_linenoise}"
TARGET_READLINE_LIBS="${with_linenoise}/linenoise.c"
echo "using linenoise source code at ${with_linenoise}"
else
TARGET_HAVE_LINENOISE=0
echo "not using linenoise"
fi
AC_SUBST(TARGET_READLINE_LIBS)
AC_SUBST(TARGET_READLINE_INC)
AC_SUBST(TARGET_HAVE_READLINE)
AC_SUBST(TARGET_HAVE_EDITLINE)
AC_SUBST(TARGET_HAVE_LINENOISE)
##########
# Figure out what C libraries are required to compile programs
@@ -615,7 +632,7 @@ AC_SEARCH_LIBS(fdatasync, [rt])
AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug],[enable debugging & verbose explain]))
AC_MSG_CHECKING([build type])
if test "${enable_debug}" = "yes" ; then
TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0"
TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0 -Wall"
AC_MSG_RESULT([debug])
else
TARGET_DEBUG="-DNDEBUG"
+26 -1
View File
@@ -3980,8 +3980,32 @@ static int fts3ShadowName(const char *zName){
return 0;
}
/*
** Implementation of the xIntegrity() method on the FTS3/FTS4 virtual
** table.
*/
static int fts3Integrity(sqlite3_vtab *pVtab, char **pzErr){
Fts3Table *p = (Fts3Table*)pVtab;
char *zSql;
int rc;
zSql = sqlite3_mprintf(
"INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');",
p->zDb, p->zName, p->zName);
rc = sqlite3_exec(p->db, zSql, 0, 0, 0);
sqlite3_free(zSql);
if( (rc&0xff)==SQLITE_CORRUPT ){
*pzErr = sqlite3_mprintf("malformed inverted index for FTS%d table %s.%s",
p->bFts4 ? 4 : 3, p->zDb, p->zName);
rc = SQLITE_OK;
}
return rc;
}
static const sqlite3_module fts3Module = {
/* iVersion */ 3,
/* iVersion */ 4,
/* xCreate */ fts3CreateMethod,
/* xConnect */ fts3ConnectMethod,
/* xBestIndex */ fts3BestIndexMethod,
@@ -4005,6 +4029,7 @@ static const sqlite3_module fts3Module = {
/* xRelease */ fts3ReleaseMethod,
/* xRollbackTo */ fts3RollbackToMethod,
/* xShadowName */ fts3ShadowName,
/* xIntegrity */ fts3Integrity,
};
/*
+1 -1
View File
@@ -5218,7 +5218,7 @@ static u64 fts3ChecksumIndex(
int rc;
u64 cksum = 0;
assert( *pRc==SQLITE_OK );
if( *pRc ) return 0;
memset(&filter, 0, sizeof(filter));
memset(&csr, 0, sizeof(csr));
+106 -76
View File
@@ -3272,7 +3272,7 @@ static Fts5Iter *fts5MultiIterAlloc(
int nSeg
){
Fts5Iter *pNew;
int nSlot; /* Power of two >= nSeg */
i64 nSlot; /* Power of two >= nSeg */
for(nSlot=2; nSlot<nSeg; nSlot=nSlot*2);
pNew = fts5IdxMalloc(p,
@@ -5048,7 +5048,6 @@ static void fts5DoSecureDelete(
int iPgIdx = pSeg->pLeaf->szLeaf;
u64 iDelta = 0;
u64 iNextDelta = 0;
int iNextOff = 0;
int iOff = 0;
int nIdx = 0;
@@ -5056,8 +5055,6 @@ static void fts5DoSecureDelete(
int bLastInDoclist = 0;
int iIdx = 0;
int iStart = 0;
int iKeyOff = 0;
int iPrevKeyOff = 0;
int iDelKeyOff = 0; /* Offset of deleted key, if any */
nIdx = nPg-iPgIdx;
@@ -5082,10 +5079,21 @@ static void fts5DoSecureDelete(
** This block sets the following variables:
**
** iStart:
** The offset of the first byte of the rowid or delta-rowid
** value for the doclist entry being removed.
**
** iDelta:
** The value of the rowid or delta-rowid value for the doclist
** entry being removed.
**
** iNextOff:
** The offset of the next entry following the position list
** for the one being removed. If the position list for this
** entry overflows onto the next leaf page, this value will be
** greater than pLeaf->szLeaf.
*/
{
int iSOP;
int iSOP; /* Start-Of-Position-list */
if( pSeg->iLeafPgno==pSeg->iTermLeafPgno ){
iStart = pSeg->iTermLeafOffset;
}else{
@@ -5121,14 +5129,20 @@ static void fts5DoSecureDelete(
}
iOff = iStart;
/* Set variable bLastInDoclist to true if this entry happens to be
** the last rowid in the doclist for its term. */
if( iNextOff>=iPgIdx ){
int pgno = pSeg->iLeafPgno+1;
fts5SecureDeleteOverflow(p, pSeg->pSeg, pgno, &bLastInDoclist);
iNextOff = iPgIdx;
}else{
/* Set bLastInDoclist to true if the entry being removed is the last
/* Loop through the page-footer. If iNextOff (offset of the
** entry following the one we are removing) is equal to the
** offset of a key on this page, then the entry is the last
** in its doclist. */
for(iIdx=0, iKeyOff=0; iIdx<nIdx; /* no-op */){
int iKeyOff = 0;
for(iIdx=0; iIdx<nIdx; /* no-op */){
u32 iVal = 0;
iIdx += fts5GetVarint32(&aIdx[iIdx], iVal);
iKeyOff += iVal;
@@ -5138,30 +5152,47 @@ static void fts5DoSecureDelete(
}
}
if( fts5GetU16(&aPg[0])==iStart && (bLastInDoclist||iNextOff==iPgIdx) ){
/* If this is (a) the first rowid on a page and (b) is not followed by
** another position list on the same page, set the "first-rowid" field
** of the header to 0. */
if( fts5GetU16(&aPg[0])==iStart && (bLastInDoclist || iNextOff==iPgIdx) ){
fts5PutU16(&aPg[0], 0);
}
if( bLastInDoclist==0 ){
if( iNextOff!=iPgIdx ){
u64 iNextDelta = 0;
iNextOff += fts5GetVarint(&aPg[iNextOff], &iNextDelta);
iOff += sqlite3Fts5PutVarint(&aPg[iOff], iDelta + iNextDelta);
}
}else if(
iStart==pSeg->iTermLeafOffset && pSeg->iLeafPgno==pSeg->iTermLeafPgno
pSeg->iLeafPgno==pSeg->iTermLeafPgno
&& iStart==pSeg->iTermLeafOffset
){
/* The entry being removed was the only position list in its
** doclist. Therefore the term needs to be removed as well. */
int iKey = 0;
for(iIdx=0, iKeyOff=0; iIdx<nIdx; iKey++){
int iKeyOff = 0;
/* Set iKeyOff to the offset of the term that will be removed - the
** last offset in the footer that is not greater than iStart. */
for(iIdx=0; iIdx<nIdx; iKey++){
u32 iVal = 0;
iIdx += fts5GetVarint32(&aIdx[iIdx], iVal);
if( (iKeyOff+iVal)>(u32)iStart ) break;
iKeyOff += iVal;
}
assert_nc( iKey>=1 );
/* Set iDelKeyOff to the value of the footer entry to remove from
** the page. */
iDelKeyOff = iOff = iKeyOff;
if( iNextOff!=iPgIdx ){
/* This is the only position-list associated with the term, and there
** is another term following it on this page. So the subsequent term
** needs to be moved to replace the term associated with the entry
** being removed. */
int nPrefix = 0;
int nSuffix = 0;
int nPrefix2 = 0;
@@ -5198,80 +5229,79 @@ static void fts5DoSecureDelete(
}
}
}else if( iStart==4 ){
int iPgno;
int iPgno;
assert_nc( pSeg->iLeafPgno>pSeg->iTermLeafPgno );
/* The entry being removed may be the only position list in
** its doclist. */
for(iPgno=pSeg->iLeafPgno-1; iPgno>pSeg->iTermLeafPgno; iPgno-- ){
Fts5Data *pPg = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno));
int bEmpty = (pPg && pPg->nn==4);
fts5DataRelease(pPg);
if( bEmpty==0 ) break;
}
assert_nc( pSeg->iLeafPgno>pSeg->iTermLeafPgno );
/* The entry being removed may be the only position list in
** its doclist. */
for(iPgno=pSeg->iLeafPgno-1; iPgno>pSeg->iTermLeafPgno; iPgno-- ){
Fts5Data *pPg = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno));
int bEmpty = (pPg && pPg->nn==4);
fts5DataRelease(pPg);
if( bEmpty==0 ) break;
}
if( iPgno==pSeg->iTermLeafPgno ){
i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno);
Fts5Data *pTerm = fts5DataRead(p, iId);
if( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){
u8 *aTermIdx = &pTerm->p[pTerm->szLeaf];
int nTermIdx = pTerm->nn - pTerm->szLeaf;
int iTermIdx = 0;
int iTermOff = 0;
if( iPgno==pSeg->iTermLeafPgno ){
i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno);
Fts5Data *pTerm = fts5DataRead(p, iId);
if( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){
u8 *aTermIdx = &pTerm->p[pTerm->szLeaf];
int nTermIdx = pTerm->nn - pTerm->szLeaf;
int iTermIdx = 0;
int iTermOff = 0;
while( 1 ){
u32 iVal = 0;
int nByte = fts5GetVarint32(&aTermIdx[iTermIdx], iVal);
iTermOff += iVal;
if( (iTermIdx+nByte)>=nTermIdx ) break;
iTermIdx += nByte;
}
nTermIdx = iTermIdx;
memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx);
fts5PutU16(&pTerm->p[2], iTermOff);
fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx);
if( nTermIdx==0 ){
fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno);
}
while( 1 ){
u32 iVal = 0;
int nByte = fts5GetVarint32(&aTermIdx[iTermIdx], iVal);
iTermOff += iVal;
if( (iTermIdx+nByte)>=nTermIdx ) break;
iTermIdx += nByte;
}
fts5DataRelease(pTerm);
nTermIdx = iTermIdx;
memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx);
fts5PutU16(&pTerm->p[2], iTermOff);
fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx);
if( nTermIdx==0 ){
fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno);
}
}
fts5DataRelease(pTerm);
}
}
if( p->rc==SQLITE_OK ){
const int nMove = nPg - iNextOff; /* Number of bytes to move */
int nShift = iNextOff - iOff; /* Distance to move them */
int iPrevKeyOut = 0;
int iKeyIn = 0;
memmove(&aPg[iOff], &aPg[iNextOff], nMove);
iPgIdx -= nShift;
nPg = iPgIdx;
fts5PutU16(&aPg[2], iPgIdx);
for(iIdx=0; iIdx<nIdx; /* no-op */){
u32 iVal = 0;
iIdx += fts5GetVarint32(&aIdx[iIdx], iVal);
iKeyIn += iVal;
if( iKeyIn!=iDelKeyOff ){
int iKeyOut = (iKeyIn - (iKeyIn>iOff ? nShift : 0));
nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOut - iPrevKeyOut);
iPrevKeyOut = iKeyOut;
}
}
if( p->rc==SQLITE_OK ){
const int nMove = nPg - iNextOff;
int nShift = 0;
memmove(&aPg[iOff], &aPg[iNextOff], nMove);
iPgIdx -= (iNextOff - iOff);
nPg = iPgIdx;
fts5PutU16(&aPg[2], iPgIdx);
nShift = iNextOff - iOff;
for(iIdx=0, iKeyOff=0, iPrevKeyOff=0; iIdx<nIdx; /* no-op */){
u32 iVal = 0;
iIdx += fts5GetVarint32(&aIdx[iIdx], iVal);
iKeyOff += iVal;
if( iKeyOff!=iDelKeyOff ){
if( iKeyOff>iOff ){
iKeyOff -= nShift;
nShift = 0;
}
nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOff - iPrevKeyOff);
iPrevKeyOff = iKeyOff;
}
}
if( iPgIdx==nPg && nIdx>0 && pSeg->iLeafPgno!=1 ){
fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iLeafPgno);
}
assert_nc( nPg>4 || fts5GetU16(aPg)==0 );
fts5DataWrite(p, FTS5_SEGMENT_ROWID(iSegid,pSeg->iLeafPgno), aPg,nPg);
if( iPgIdx==nPg && nIdx>0 && pSeg->iLeafPgno!=1 ){
fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iLeafPgno);
}
sqlite3_free(aIdx);
assert_nc( nPg>4 || fts5GetU16(aPg)==0 );
fts5DataWrite(p, FTS5_SEGMENT_ROWID(iSegid,pSeg->iLeafPgno), aPg, nPg);
}
sqlite3_free(aIdx);
}
/*
+38 -4
View File
@@ -405,6 +405,10 @@ static int fts5InitVtab(
pConfig->pzErrmsg = 0;
}
if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){
rc = sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, (int)1);
}
if( rc!=SQLITE_OK ){
fts5FreeVtab(pTab);
pTab = 0;
@@ -1329,6 +1333,9 @@ static int fts5FilterMethod(
pCsr->iFirstRowid = fts5GetRowidLimit(pRowidGe, SMALLEST_INT64);
}
rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex);
if( rc!=SQLITE_OK ) goto filter_out;
if( pTab->pSortCsr ){
/* If pSortCsr is non-NULL, then this call is being made as part of
** processing for a "... MATCH <expr> ORDER BY rank" query (ePlan is
@@ -1351,6 +1358,7 @@ static int fts5FilterMethod(
pCsr->pExpr = pTab->pSortCsr->pExpr;
rc = fts5CursorFirst(pTab, pCsr, bDesc);
}else if( pCsr->pExpr ){
assert( rc==SQLITE_OK );
rc = fts5CursorParseRank(pConfig, pCsr, pRank);
if( rc==SQLITE_OK ){
if( bOrderByRank ){
@@ -1685,7 +1693,7 @@ static int fts5UpdateMethod(
assert( nArg!=1 || eType0==SQLITE_INTEGER );
/* Filter out attempts to run UPDATE or DELETE on contentless tables.
** This is not suported. Except - DELETE is supported if the CREATE
** This is not suported. Except - they are both supported if the CREATE
** VIRTUAL TABLE statement contained "contentless_delete=1". */
if( eType0==SQLITE_INTEGER
&& pConfig->eContent==FTS5_CONTENT_NONE
@@ -1714,7 +1722,8 @@ static int fts5UpdateMethod(
}
else if( eType0!=SQLITE_INTEGER ){
/* If this is a REPLACE, first remove the current entry (if any) */
/* An INSERT statement. If the conflict-mode is REPLACE, first remove
** the current entry (if any). */
if( eConflict==SQLITE_REPLACE && eType1==SQLITE_INTEGER ){
i64 iNew = sqlite3_value_int64(apVal[1]); /* Rowid to delete */
rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0);
@@ -2850,9 +2859,33 @@ static int fts5ShadowName(const char *zName){
return 0;
}
/*
** Run an integrity check on the FTS5 data structures. Return a string
** if anything is found amiss. Return a NULL pointer if everything is
** OK.
*/
static int fts5Integrity(sqlite3_vtab *pVtab, char **pzErr){
Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
Fts5Config *pConfig = pTab->p.pConfig;
char *zSql;
int rc;
zSql = sqlite3_mprintf(
"INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');",
pConfig->zDb, pConfig->zName, pConfig->zName);
rc = sqlite3_exec(pConfig->db, zSql, 0, 0, 0);
sqlite3_free(zSql);
if( (rc&0xff)==SQLITE_CORRUPT ){
*pzErr = sqlite3_mprintf("malformed inverted index for FTS5 table %s.%s",
pConfig->zDb, pConfig->zName);
rc = SQLITE_OK;
}
return rc;
}
static int fts5Init(sqlite3 *db){
static const sqlite3_module fts5Mod = {
/* iVersion */ 3,
/* iVersion */ 4,
/* xCreate */ fts5CreateMethod,
/* xConnect */ fts5ConnectMethod,
/* xBestIndex */ fts5BestIndexMethod,
@@ -2875,7 +2908,8 @@ static int fts5Init(sqlite3 *db){
/* xSavepoint */ fts5SavepointMethod,
/* xRelease */ fts5ReleaseMethod,
/* xRollbackTo */ fts5RollbackToMethod,
/* xShadowName */ fts5ShadowName
/* xShadowName */ fts5ShadowName,
/* xIntegrity */ fts5Integrity
};
int rc;
+5 -2
View File
@@ -65,7 +65,9 @@ foreach w {a b c d e f} {
do_execsql_test 2.4 {
INSERT INTO t1(t1) VALUES('integrity-check');
}
PRAGMA integrity_check;
PRAGMA integrity_check(t1);
} {ok ok}
#-------------------------------------------------------------------------
@@ -88,6 +90,7 @@ foreach {i x y} {
} {
do_execsql_test 3.$i.1 { INSERT INTO t1 VALUES($x, $y) }
do_execsql_test 3.$i.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
do_execsql_test 3.$i.3 { PRAGMA integrity_check(t1) } ok
if {[set_test_counter errors]} break
}
@@ -135,7 +138,7 @@ foreach {i x y} {
10 {ddd abcde dddd dd c} {dddd c c d abcde}
} {
do_execsql_test 5.$i.1 { INSERT INTO t1 VALUES($x, $y) }
do_execsql_test 5.$i.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
do_execsql_test 5.$i.2 { PRAGMA integrity_check(t1) } ok
if {[set_test_counter errors]} break
}
+27 -1
View File
@@ -307,5 +307,31 @@ do_catchsql_test 10.1.4 {
SELECT group_concat(firstcol(t1), '.') FROM t1 GROUP BY rowid
} {1 {unable to use function firstcol in the requested context}}
finish_test
#-------------------------------------------------------------------------
# Test that xInstCount() works from within an xPhraseQuery() callback.
#
reset_db
proc xCallback {cmd} {
incr ::hitcount [$cmd xInstCount]
return SQLITE_OK
}
proc fts5_hitcount {cmd} {
set ::hitcount 0
$cmd xQueryPhrase 0 xCallback
return $::hitcount
}
sqlite3_fts5_create_function db fts5_hitcount fts5_hitcount
do_execsql_test 11.1 {
CREATE VIRTUAL TABLE x1 USING fts5(z);
INSERT INTO x1 VALUES('one two three');
INSERT INTO x1 VALUES('one two one three one');
INSERT INTO x1 VALUES('one two three');
}
do_execsql_test 11.2 {
SELECT fts5_hitcount(x1) FROM x1('one') LIMIT 1;
} {5}
finish_test
+40
View File
@@ -65,4 +65,44 @@ do_execsql_test 2.1 {
INSERT INTO fts_idx(fts_idx) VALUES('integrity-check');
}
#-------------------------------------------------------------------------
# Tests for OR IGNORE conflict handling.
#
reset_db
foreach_detail_mode $::testprefix {
do_execsql_test 3.0 {
CREATE VIRTUAL TABLE t1 USING fts5(xyz, detail=%DETAIL%);
BEGIN;
INSERT INTO t1(rowid, xyz) VALUES(13, 'thirteen documents');
INSERT INTO t1(rowid, xyz) VALUES(14, 'fourteen documents');
INSERT INTO t1(rowid, xyz) VALUES(15, 'fifteen documents');
COMMIT;
}
set db_cksum [cksum]
foreach {tn sql} {
1 {
INSERT OR IGNORE INTO t1(rowid, xyz) VALUES(14, 'new text');
}
2 {
UPDATE OR IGNORE t1 SET rowid=13 WHERE rowid=15;
}
3 {
INSERT OR IGNORE INTO t1(rowid, xyz)
SELECT 13, 'some text'
UNION ALL
SELECT 14, 'some text'
UNION ALL
SELECT 15, 'some text'
}
} {
do_execsql_test 3.1.$tn.1 $sql
do_test 3.1.$tn.2 { cksum } $db_cksum
}
}
finish_test
-1
View File
@@ -294,4 +294,3 @@ do_catchsql_test 7.2.5 {
} {1 {recursively defined fts5 content table}}
finish_test
-1
View File
@@ -268,4 +268,3 @@ do_execsql_test 8.2 {
} {}
finish_test
-1
View File
@@ -205,4 +205,3 @@ foreach {tn step} {
finish_test
-1
View File
@@ -193,4 +193,3 @@ do_execsql_test 3.7 {
finish_test
-1
View File
@@ -245,4 +245,3 @@ do_execsql_test 4.3 {
}
finish_test
-1
View File
@@ -56,4 +56,3 @@ foreach {tn up err} {
}
finish_test
+4
View File
@@ -48,6 +48,10 @@ do_test 1.3 {
}
catchsql { INSERT INTO t1(t1) VALUES('integrity-check') }
} {1 {database disk image is malformed}}
do_execsql_test 1.3b {
PRAGMA integrity_check(t1);
} {{malformed inverted index for FTS5 table main.t1}}
do_test 1.4 {
db_restore_and_reopen
+3
View File
@@ -167,6 +167,9 @@ foreach {tn hdr} {
do_test 3.$tn.$tn2.2 {
catchsql { INSERT INTO x3(x3) VALUES('integrity-check') }
} {1 {database disk image is malformed}}
do_execsql_test 3.$tn.$tn2.3 {
PRAGMA integrity_check(x3);
} {{malformed inverted index for FTS5 table main.x3}}
}
execsql ROLLBACK
+2 -2
View File
@@ -44,12 +44,12 @@ do_catchsql_test 1.2.2 {
do_catchsql_test 1.3.1 {
SELECT highlight(t1, 4, '<b>', '</b>') FROM t1('*reads');
} {1 {no such cursor: 1}}
} {1 {no such cursor: 2}}
do_catchsql_test 1.3.2 {
SELECT a FROM t1
WHERE rank = (SELECT highlight(t1, 4, '<b>', '</b>') FROM t1('*reads'));
} {1 {no such cursor: 1}}
} {1 {no such cursor: 2}}
db close
sqlite3 db test.db
+24
View File
@@ -180,4 +180,28 @@ do_execsql_test 6.1 {
{table table table} {the table names.} {rank on an fts5 table}
}
#-------------------------------------------------------------------------
# forum post: https://sqlite.org/forum/forumpost/a2dd636330
#
reset_db
do_execsql_test 1.0 {
CREATE VIRTUAL TABLE t USING fts5 (a, b);
INSERT INTO t (a, b) VALUES ('data1', 'sentence1'), ('data2', 'sentence2');
INSERT INTO t(t, rank) VALUES ('rank', 'bm25(10.0,1.0)');
}
sqlite3 db2 test.db
do_execsql_test -db db2 1.1 {
SELECT *, rank<0.0 FROM t('data*') ORDER BY RANK;
} {data1 sentence1 1 data2 sentence2 1}
do_execsql_test 1.2 {
INSERT INTO t(t, rank) VALUES ('rank', 'bm25(10.0,1.0)');
}
do_execsql_test -db db2 1.3 {
SELECT *, rank<0.0 FROM t('data*') ORDER BY RANK;
} {data1 sentence1 1 data2 sentence2 1}
db2 close
finish_test
+70
View File
@@ -273,6 +273,76 @@ do_execsql_test 5.3 {
do_execsql_test 5.4 { SELECT rowid FROM t1('abc'); } 2
do_execsql_test 5.5 { SELECT rowid FROM t1('aa'); } 2
#-------------------------------------------------------------------------
# Tests for the bug fixed by https://sqlite.org/src/info/4b60a1c3
#
reset_db
do_execsql_test 6.0 {
CREATE VIRTUAL TABLE fts USING fts5(content);
INSERT INTO fts(fts, rank) VALUES ('secure-delete', 1);
INSERT INTO fts(rowid, content) VALUES
(3407, 'profile profile profile profile profile profile profile profile pull pulling pulling really');
DELETE FROM fts WHERE rowid IS 3407;
INSERT INTO fts(fts) VALUES ('integrity-check');
}
foreach {tn detail} {
1 full
2 column
3 none
} {
do_execsql_test 6.1.$detail "
DROP TABLE IF EXISTS t1;
CREATE VIRTUAL TABLE t1 USING fts5(x, detail=$detail);
"
do_execsql_test 6.2.$detail {
INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
}
for {set ii 1} {$ii < 100} {incr ii} {
do_execsql_test 6.3.$detail.$ii.1 {
BEGIN;
INSERT INTO t1(rowid, x) VALUES(10, 'word1');
WITH s(i) AS (
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<CAST($ii AS integer)
)
INSERT INTO t1(x) SELECT 'word3' FROM s;
COMMIT;
INSERT INTO t1(t1) VALUES('optimize');
}
do_execsql_test 6.3.$detail.$ii.2 {
DELETE FROM t1 WHERE rowid=10;
INSERT INTO t1(t1) VALUES ('integrity-check');
}
do_execsql_test 6.3.$detail.$ii.3 {
DELETE FROM t1;
}
do_execsql_test 6.3.$detail.$ii.4 {
BEGIN;
INSERT INTO t1(rowid, x) VALUES(10, 'tokenA');
WITH s(i) AS (
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<CAST($ii AS integer)
)
INSERT INTO t1(x) SELECT group_concat('tokenB ') FROM s;
COMMIT;
INSERT INTO t1(t1) VALUES('optimize');
}
do_execsql_test 6.3.$detail.$ii.5 {
DELETE FROM t1 WHERE rowid=10;
INSERT INTO t1(t1) VALUES ('integrity-check');
}
do_execsql_test 6.3.$detail.$ii.6 {
DELETE FROM t1;
}
}
}
finish_test
+21
View File
@@ -70,5 +70,26 @@ do_execsql_test 2.2 {
SELECT rowid FROM t1('def')
} {-100000 -99999 9223372036854775800}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 2.0 {
CREATE VIRTUAL TABLE t1 USING fts5(x);
INSERT INTO t1(t1, rank) VALUES('secure-delete', $sd)
}
do_execsql_test 2.1 {
BEGIN;
INSERT INTO t1(rowid, x)
VALUES(51869, 'when whenever where weress what turn'),
(51871, 'to were');
COMMIT;
}
do_execsql_test 3.2 {
DELETE FROM t1 WHERE rowid=51871;
INSERT INTO t1(t1) VALUES('integrity-check');
}
finish_test
+116
View File
@@ -0,0 +1,116 @@
# 2023 Feb 17
#
# 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.
#
#*************************************************************************
#
# TESTRUNNER: slow
#
source [file join [file dirname [info script]] fts5_common.tcl]
ifcapable !fts5 { finish_test ; return }
set ::testprefix fts5secure7
set NVOCAB 500
set NDOC [expr 1000]
set NREP 100
set nDeletePerRep [expr 5]
set VOCAB [list]
proc select_one {list} {
set n [llength $list]
lindex $list [expr {abs(int(rand()*$n))}]
}
proc init_vocab {} {
set L [split "abcdefghijklmnopqrstuvwxyz" {}]
set nL [llength $L]
for {set i 0} {$i < $::NVOCAB} {incr i} {
set n [expr {6 + int(rand()*8)}]
set word ""
for {set j 0} {$j < $n} {incr j} {
append word [select_one $L]
}
lappend ::VOCAB $word
}
}
proc get_word {} {
select_one $::VOCAB
}
proc get_document {nWord} {
set ret [list]
for {set i 0} {$i < $nWord} {incr i} {
lappend ret [get_word]
}
return $ret
}
init_vocab
db func document [list get_document 12]
do_execsql_test 1.0 {
CREATE VIRTUAL TABLE t1 USING fts5(body);
INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
}
do_execsql_test 1.1 {
WITH s(i) AS (
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<$NDOC
)
INSERT INTO t1 SELECT document() FROM s;
}
for {set iRep 0} {$iRep < $NREP} {incr iRep} {
set lRowid [db eval {SELECT rowid FROM t1}]
for {set iDel 0} {$iDel < $nDeletePerRep} {incr iDel} {
set idx [select_one $lRowid]
db eval {
DELETE FROM t1 WHERE rowid=$idx
}
}
db eval {
WITH s(i) AS (
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<$nDeletePerRep
)
INSERT INTO t1 SELECT document() FROM s;
}
do_execsql_test 1.2.$iRep {
INSERT INTO t1(t1) VALUES('integrity-check');
}
}
reset_db
db func document [list get_document 12]
do_execsql_test 2.0 {
CREATE VIRTUAL TABLE t1 USING fts5(body);
INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
INSERT INTO t1(t1, rank) VALUES('pgsz', 128);
}
do_execsql_test 2.1 {
WITH s(i) AS (
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<$NDOC
)
INSERT INTO t1 SELECT document() FROM s;
}
for {set ii 0} {$ii < $NDOC} {incr ii} {
set lRowid [db eval {SELECT rowid FROM t1}]
set idx [select_one $lRowid]
db eval { DELETE FROM t1 WHERE rowid=$idx }
do_execsql_test 2.2.$ii {
INSERT INTO t1(t1) VALUES('integrity-check');
}
}
finish_test
+144 -73
View File
@@ -26,11 +26,19 @@ dir.src.c := $(dir.src)/c
dir.bld := $(dir.jni)/bld
dir.bld.c := $(dir.bld)
dir.src.jni := $(dir.src)/org/sqlite/jni
dir.src.jni.tester := $(dir.src.jni)/tester
mkdir := mkdir -p
dir.src.fts5 := $(dir.src.jni)/fts5
dir.tests := $(dir.src)/tests
mkdir ?= mkdir -p
$(dir.bld.c):
$(mkdir) $@
javac.flags ?= -Xlint:unchecked -Xlint:deprecation
java.flags ?=
jnicheck ?= 1
ifeq (1,$(jnicheck))
java.flags += -Xcheck:jni
endif
classpath := $(dir.src)
CLEAN_FILES := $(package.jar)
DISTCLEAN_FILES := $(dir.jni)/*~ $(dir.src.c)/*~ $(dir.src.jni)/*~
@@ -38,16 +46,27 @@ DISTCLEAN_FILES := $(dir.jni)/*~ $(dir.src.c)/*~ $(dir.src.jni)/*~
sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h
.NOTPARALLEL: $(sqlite3-jni.h)
SQLite3Jni.java := src/org/sqlite/jni/SQLite3Jni.java
SQLTester.java := src/org/sqlite/jni/tester/SQLTester.java
SQLTester.java := src/org/sqlite/jni/SQLTester.java
SQLite3Jni.class := $(SQLite3Jni.java:.java=.class)
SQLTester.class := $(SQLTester.java:.java=.class)
########################################################################
# The future of FTS5 customization in this API is as yet unclear.
# It would be a real doozy to bind to JNI.
# The pieces are all in place, and are all thin proxies so not much
# complexity, but some semantic changes were required in porting
# which are largely untested.
#
# Reminder: this flag influences the contents of $(sqlite3-jni.h),
# which is checked in. Please do not check in changes to that file in
# which the fts5 APIs have been stripped unless that feature is
# intended to be stripped for good.
enable.fts5 ?= 1
# If enable.tester is 0, the org/sqlite/jni/tester/* bits are elided.
enable.tester ?= 1
ifeq (,$(wildcard $(dir.tests)/*))
enable.tester := 0
else
enable.tester := 1
endif
# bin.version-info = binary to output various sqlite3 version info
# building the distribution zip file.
@@ -59,6 +78,10 @@ $(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile
# Be explicit about which Java files to compile so that we can work on
# in-progress files without requiring them to be in a compilable statae.
JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/%,\
annotation/Canonical.java \
annotation/NotNull.java \
annotation/Nullable.java \
AbstractCollationCallback.java \
AggregateFunction.java \
AuthorizerCallback.java \
AutoExtensionCallback.java \
@@ -66,71 +89,73 @@ JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/%,\
CollationCallback.java \
CollationNeededCallback.java \
CommitHookCallback.java \
ConfigLogCallback.java \
ConfigSqllogCallback.java \
NativePointerHolder.java \
NotNull.java \
Nullable.java \
OutputPointer.java \
PrepareMultiCallback.java \
PreupdateHookCallback.java \
ProgressHandlerCallback.java \
ResultCode.java \
RollbackHookCallback.java \
ScalarFunction.java \
SQLFunction.java \
CallbackProxy.java \
SQLite3Jni.java \
Tester1.java \
TableColumnMetadata.java \
TraceV2Callback.java \
UpdateHookCallback.java \
ValueHolder.java \
WindowFunction.java \
XDestroyCallback.java \
package-info.java \
sqlite3.java \
sqlite3_context.java \
sqlite3_stmt.java \
sqlite3_value.java \
)
JAVA_FILES.unittest := $(patsubst %,$(dir.src.jni)/%,\
Tester1.java \
)
ifeq (1,$(enable.fts5))
JAVA_FILES.main += $(patsubst %,$(dir.src.jni)/%,\
JAVA_FILES.unittest += $(patsubst %,$(dir.src.jni)/%,\
TesterFts5.java \
)
JAVA_FILES.main += $(patsubst %,$(dir.src.fts5)/%,\
fts5_api.java \
fts5_extension_function.java \
fts5_tokenizer.java \
Fts5.java \
Fts5Context.java \
Fts5ExtensionApi.java \
Fts5Function.java \
Fts5PhraseIter.java \
Fts5Tokenizer.java \
TesterFts5.java \
XTokenizeCallback.java \
)
endif
JAVA_FILES.tester := $(dir.src.jni.tester)/SQLTester.java
JAVA_FILES.tester := $(SQLTester.java)
JAVA_FILES.package.info := \
$(dir.src.jni)/package-info.java \
$(dir.src.jni)/annotation/package-info.java
CLASS_FILES.main := $(JAVA_FILES.main:.java=.class)
CLASS_FILES.unittest := $(JAVA_FILES.unittest:.java=.class)
CLASS_FILES.tester := $(JAVA_FILES.tester:.java=.class)
JAVA_FILES += $(JAVA_FILES.main)
JAVA_FILES += $(JAVA_FILES.main) $(JAVA_FILES.unittest)
ifeq (1,$(enable.tester))
JAVA_FILES += $(JAVA_FILES.tester)
endif
CLASS_FILES :=
define DOTCLASS_DEPS
$(1).class: $(1).java $(MAKEFILE)
define CLASSFILE_DEPS
all: $(1).class
CLASS_FILES += $(1).class
endef
$(foreach B,$(basename $(JAVA_FILES)),$(eval $(call DOTCLASS_DEPS,$(B))))
$(CLASS_FILES.tester): $(CLASS_FILES.main)
javac.flags ?= -Xlint:unchecked -Xlint:deprecation
java.flags ?=
jnicheck ?= 1
ifeq (1,$(jnicheck))
java.flags += -Xcheck:jni
endif
$(SQLite3Jni.class): $(JAVA_FILES)
$(foreach B,$(basename \
$(JAVA_FILES.main) $(JAVA_FILES.unittest) $(JAVA_FILES.tester)),\
$(eval $(call CLASSFILE_DEPS,$(B))))
$(CLASS_FILES): $(JAVA_FILES) $(MAKEFILE)
$(bin.javac) $(javac.flags) -h $(dir.bld.c) -cp $(classpath) $(JAVA_FILES)
all: $(SQLite3Jni.class)
#.PHONY: classfiles
########################################################################
@@ -165,6 +190,9 @@ $(sqlite3.h):
$(sqlite3.c): $(sqlite3.h)
opt.threadsafe ?= 1
opt.fatal-oom ?= 1
opt.debug ?= 1
opt.metrics ?= 1
SQLITE_OPT = \
-DSQLITE_ENABLE_RTREE \
-DSQLITE_ENABLE_EXPLAIN_COMMENTS \
@@ -182,10 +210,14 @@ SQLITE_OPT = \
-DSQLITE_TEMP_STORE=2 \
-DSQLITE_USE_URI=1 \
-DSQLITE_C=$(sqlite3.c) \
-DSQLITE_JNI_FATAL_OOM=0 \
-DSQLITE_DEBUG
-DSQLITE_JNI_FATAL_OOM=$(opt.fatal-oom) \
-DSQLITE_JNI_ENABLE_METRICS=$(opt.metrics)
SQLITE_OPT += -g -DDEBUG -UNDEBUG
ifeq (1,$(opt.debug))
SQLITE_OPT += -DSQLITE_DEBUG -g -DDEBUG -UNDEBUG
else
SQLITE_OPT += -O2
endif
ifeq (1,$(enable.fts5))
SQLITE_OPT += -DSQLITE_ENABLE_FTS5
@@ -194,25 +226,29 @@ endif
sqlite3-jni.c := $(dir.src.c)/sqlite3-jni.c
sqlite3-jni.o := $(dir.bld.c)/sqlite3-jni.o
sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h
sqlite3-jni.dll := $(dir.bld.c)/libsqlite3-jni.so
package.dll := $(dir.bld.c)/libsqlite3-jni.so
# All javac-generated .h files must be listed in $(sqlite3-jni.h.in):
sqlite3-jni.h.in :=
# $(java.with.jni) lists all Java files which contain JNI decls:
java.with.jni :=
define ADD_JNI_H
sqlite3-jni.h.in += $$(dir.bld.c)/org_sqlite_jni_$(1).h
$$(dir.bld.c)/org_sqlite_jni_$(1).h: $$(dir.src.jni)/$(1).java
sqlite3-jni.h.in += $$(dir.bld.c)/org_sqlite_jni$(3)_$(2).h
java.with.jni += $(1)/$(2).java
$$(dir.bld.c)/org_sqlite_jni$(3)_$(2).h: $(1)/$(2).java
endef
$(eval $(call ADD_JNI_H,SQLite3Jni))
# Invoke ADD_JNI_H once for each Java file which includes JNI
# declarations:
$(eval $(call ADD_JNI_H,$(dir.src.jni),SQLite3Jni,))
$(eval $(call ADD_JNI_H,$(dir.src.jni),SQLTester,_tester))
ifeq (1,$(enable.fts5))
$(eval $(call ADD_JNI_H,Fts5ExtensionApi))
$(eval $(call ADD_JNI_H,fts5_api))
$(eval $(call ADD_JNI_H,fts5_tokenizer))
$(eval $(call ADD_JNI_H,$(dir.src.fts5),Fts5ExtensionApi,_fts5))
$(eval $(call ADD_JNI_H,$(dir.src.fts5),fts5_api,_fts5))
$(eval $(call ADD_JNI_H,$(dir.src.fts5),fts5_tokenizer,_fts5))
endif
ifeq (1,$(enable.tester))
sqlite3-jni.h.in += $(dir.bld.c)/org_sqlite_jni_tester_SQLTester.h
$(dir.bld.c)/org_sqlite_jni_tester_SQLTester.h: $(dir.src.jni.tester)/SQLTester.java
endif
#sqlite3-jni.dll.cfiles := $(dir.src.c)
sqlite3-jni.dll.cflags = \
$(sqlite3-jni.h.in): $(dir.bld.c)
#package.dll.cfiles :=
package.dll.cflags = \
-fPIC \
-I. \
-I$(dir $(sqlite3.h)) \
@@ -220,31 +256,44 @@ sqlite3-jni.dll.cflags = \
-I$(JDK_HOME)/include \
$(patsubst %,-I%,$(patsubst %.h,,$(wildcard $(JDK_HOME)/include/*))) \
-Wall
# Using (-Wall -Wextra) triggers an untennable number of
# gcc warnings from sqlite3.c for mundane things like
# unused parameters.
#
# The gross $(patsubst...) above is to include the platform-specific
# subdir which lives under $(JDK_HOME)/include and is a required
# include path for client-level code.
#
# Using (-Wall -Wextra) triggers an untennable number of
# gcc warnings from sqlite3.c for mundane things like
# unused parameters.
########################################################################
ifeq (1,$(enable.tester))
sqlite3-jni.dll.cflags += -DSQLITE_JNI_ENABLE_SQLTester
package.dll.cflags += -DSQLITE_JNI_ENABLE_SQLTester
endif
$(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE)
cat $(sqlite3-jni.h.in) > $@
$(sqlite3-jni.dll): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h)
$(sqlite3-jni.dll): $(dir.bld.c) $(sqlite3-jni.c) $(SQLite3Jni.java) $(MAKEFILE)
$(CC) $(sqlite3-jni.dll.cflags) $(SQLITE_OPT) \
@cat $(sqlite3-jni.h.in) > $@.tmp
@if cmp $@ $@.tmp >/dev/null; then \
rm -f $@.tmp; \
echo "$@ not modified"; \
else \
mv $@.tmp $@; \
echo "Updated $@"; \
fi
@if [ x1 != x$(enable.fts5) ]; then \
echo "*** REMINDER:"; \
echo "*** enable.fts5=0, so please do not check in changes to $@."; \
fi
$(package.dll): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h)
$(package.dll): $(sqlite3-jni.c) $(MAKEFILE)
$(CC) $(package.dll.cflags) $(SQLITE_OPT) \
$(sqlite3-jni.c) -shared -o $@
all: $(sqlite3-jni.dll)
all: $(package.dll)
.PHONY: test test-one
test.flags ?=
test.main.flags = -ea -Djava.library.path=$(dir.bld.c) \
$(java.flags) -cp $(classpath) \
org.sqlite.jni.Tester1
test.deps := $(SQLite3Jni.class) $(sqlite3-jni.dll)
test.deps := $(CLASS_FILES) $(package.dll)
test-one: $(test.deps)
$(bin.java) $(test.main.flags) $(test.flags)
test-sqllog: $(test.deps)
@@ -252,7 +301,7 @@ test-sqllog: $(test.deps)
$(bin.java) $(test.main.flags) -sqllog
test-mt: $(test.deps)
@echo "Testing in multi-threaded mode:";
$(bin.java) $(test.main.flags) -t 5 -r 20 -shuffle $(test.flags)
$(bin.java) $(test.main.flags) -t 7 -r 50 -shuffle $(test.flags)
test: test-one test-mt
tests: test test-sqllog
@@ -261,24 +310,24 @@ tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test))
tester.flags ?= # --verbose
.PHONY: tester tester-local tester-ext
ifeq (1,$(enable.tester))
tester-local: $(CLASS_FILES.tester) $(sqlite3-jni.dll)
tester-local: $(CLASS_FILES.tester) $(package.dll)
$(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
$(java.flags) -cp $(classpath) \
org.sqlite.jni.tester.SQLTester $(tester.flags) $(tester.scripts)
org.sqlite.jni.SQLTester $(tester.flags) $(tester.scripts)
tester: tester-local
else
tester:
@echo "SQLTester support is disabled. Build with enable.tester=1 to enable it."
@echo "SQLTester support is disabled."
endif
tester.extdir.default := src/tests/ext
tester.extdir.default := $(dir.tests)/ext
tester.extdir ?= $(tester.extdir.default)
tester.extern-scripts := $(wildcard $(tester.extdir)/*.test)
ifneq (,$(tester.extern-scripts))
tester-ext:
$(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
$(java.flags) -cp $(classpath) \
org.sqlite.jni.tester.SQLTester $(tester.flags) $(tester.extern-scripts)
org.sqlite.jni.SQLTester $(tester.flags) $(tester.extern-scripts)
else
tester-ext:
@echo "******************************************************"; \
@@ -292,37 +341,59 @@ endif
tester-ext: tester-local
tester: tester-ext
tests: tester
########################################################################
# Build each SQLITE_THREADMODE variant and run all tests against them.
multitest: clean
$(MAKE) opt.threadsafe=0 tests clean
$(MAKE) opt.threadsafe=1 tests clean
$(MAKE) opt.threadsafe=2 tests clean
define MULTIOPT
multitest: multitest-$(1)
multitest-$(1):
$$(MAKE) opt.debug=$$(opt.debug) $(patsubst %,opt.%,$(2)) \
tests clean enable.fts5=1
endef
$(eval $(call MULTIOPT,01,threadsafe=0 oom=1))
$(eval $(call MULTIOPT,00,threadsafe=0 oom=0))
$(eval $(call MULTIOPT,11,threadsafe=1 oom=1))
$(eval $(call MULTIOPT,10,threadsafe=1 oom=0))
$(eval $(call MULTIOPT,21,threadsafe=2 oom=1))
$(eval $(call MULTIOPT,20,threadsafe=2 oom=0))
########################################################################
# jar bundle...
package.jar.in := $(abspath $(dir.src)/jar.in)
CLEAN_FILES += $(package.jar.in)
$(package.jar.in): $(MAKEFILE) $(CLASS_FILES.main)
cd $(dir.src); ls -1 org/sqlite/jni/*.java org/sqlite/jni/*.class > $@
@echo "To use this jar you will need the -Djava.library.path=DIR/CONTAINING/libsqlite3-jni.so flag."
@echo "e.g. java -jar $@ -Djava.library.path=bld"
JAVA_FILES.jar := $(JAVA_FILES.main) $(JAVA_FILES.unittest) $(JAVA_FILES.package.info)
CLASS_FILES.jar := $(filter-out %/package-info.class,$(JAVA_FILES.jar:.java=.class))
$(package.jar.in): $(package.dll) $(MAKEFILE)
ls -1 \
$(dir.src.jni)/*.java $(dir.src.jni)/*.class \
$(dir.src.jni)/annotation/*.java $(dir.src.jni)/annotation/*.class \
| sed -e 's,^$(dir.src)/,,' | sort > $@
$(package.jar): $(CLASS_FILES) $(MAKEFILE) $(package.jar.in)
rm -f $(dir.src)/c/*~ $(dir.src.jni)/*~
$(package.jar): $(CLASS_FILES.jar) $(MAKEFILE) $(package.jar.in)
@rm -f $(dir.src)/c/*~ $(dir.src.jni)/*~
cd $(dir.src); $(bin.jar) -cfe ../$@ org.sqlite.jni.Tester1 @$(package.jar.in)
@ls -la $@
@echo "To use this jar you will need the -Djava.library.path=DIR/CONTAINING/libsqlite3-jni.so flag."
@echo "e.g. java -Djava.library.path=bld -jar $@"
jar: $(package.jar)
run-jar: $(package.jar) $(package.dll)
$(bin.java) -Djava.library.path=$(dir.bld) -jar $(package.jar) $(run-jar.flags)
########################################################################
# javadoc...
dir.doc := $(dir.jni)/javadoc
doc.index := $(dir.doc)/index.html
javadoc.exclude := -exclude org.sqlite.jni.fts5
# ^^^^ 2023-09-13: elide the fts5 parts from the public docs for
# the time being, as it's not clear where the Java bindings for
# those bits are going.
$(doc.index): $(JAVA_FILES.main) $(MAKEFILE)
@if [ -d $(dir.doc) ]; then rm -fr $(dir.doc)/*; fi
$(bin.javadoc) -cp $(classpath) -d $(dir.doc) -quiet org.sqlite.jni
$(bin.javadoc) -cp $(classpath) -d $(dir.doc) -quiet \
-subpackages org.sqlite.jni $(javadoc.exclude)
@echo "javadoc output is in $@"
.PHONY: doc javadoc docserve
@@ -340,7 +411,7 @@ docserve: $(doc.index)
CLEAN_FILES += $(dir.bld.c)/* \
$(dir.src.jni)/*.class \
$(dir.src.jni.tester)/*.class \
$(sqlite3-jni.dll) \
$(package.dll) \
hs_err_pid*.log
.PHONY: clean distclean
+1 -1
View File
@@ -55,7 +55,7 @@ Hello World
```java
import org.sqlite.jni.*;
import static SQLite3Jni.*;
import static org.sqlite.jni.SQLite3Jni.*;
...
+2101 -1452
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,34 @@
/*
** 2023-08-25
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
import org.sqlite.jni.annotation.NotNull;
/**
An implementation of {@link CollationCallback} which provides a
no-op xDestroy() method.
*/
public abstract class AbstractCollationCallback
implements CollationCallback, XDestroyCallback {
/**
Must compare the given byte arrays and return the result using
{@code memcmp()} semantics.
*/
public abstract int call(@NotNull byte[] lhs, @NotNull byte[] rhs);
/**
Optionally override to be notified when the UDF is finalized by
SQLite. This implementation does nothing.
*/
public void xDestroy(){}
}
@@ -15,12 +15,12 @@ package org.sqlite.jni;
/**
SQLFunction subclass for creating aggregate functions. Its T is
the data type of its "accumulator" state, an instance of which is
A SQLFunction implementation for aggregate functions. Its T is the
data type of its "accumulator" state, an instance of which is
intended to be be managed using the getAggregateState() and
takeAggregateState() methods.
*/
public abstract class AggregateFunction<T> extends SQLFunction {
public abstract class AggregateFunction<T> implements SQLFunction {
/**
As for the xStep() argument of the C API's
@@ -48,7 +48,7 @@ public abstract class AggregateFunction<T> extends SQLFunction {
/**
To be called from the implementation's xStep() method, as well
as the xValue() and xInverse() methods of the Window<T>
as the xValue() and xInverse() methods of the {@link WindowFunction}
subclass, to fetch the current per-call UDF state. On the
first call to this method for any given sqlite3_context
argument, the context is set to the given initial value. On all other
@@ -12,16 +12,15 @@
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
import org.sqlite.jni.annotation.*;
/**
Callback for use with sqlite3_set_authorizer().
Callback for use with {@link SQLite3Jni#sqlite3_set_authorizer}.
*/
public interface AuthorizerCallback extends SQLite3CallbackProxy {
public interface AuthorizerCallback extends CallbackProxy {
/**
Must function as described for the C-level
sqlite3_set_authorizer() callback.
Must not throw.
*/
int call(int opId, @Nullable String s1, @Nullable String s2,
@Nullable String s3, @Nullable String s4);
@@ -14,28 +14,27 @@
package org.sqlite.jni;
/**
A callback for use with the sqlite3_auto_extension() family of
APIs.
Callback for use with the {@link SQLite3Jni#sqlite3_auto_extension}
family of APIs.
*/
public interface AutoExtensionCallback extends SQLite3CallbackProxy {
public interface AutoExtensionCallback extends CallbackProxy {
/**
Must function as described for a C-level
sqlite3_auto_extension() callback, with the caveat that the
signature is shorter.
sqlite3_auto_extension() callback.
This callback may throw and the exception's error message will
<p>This callback may throw and the exception's error message will
be set as the db's error string.
Tips for implementations:
<p>Tips for implementations:
- Opening a database from an auto-extension handler will lead to
<p>- Opening a database from an auto-extension handler will lead to
an endless recursion of the auto-handler triggering itself
indirectly for each newly-opened database.
- If this routine is stateful, it may be useful to make the
<p>- If this routine is stateful, it may be useful to make the
overridden method synchronized.
- Results are undefined if db is closed by an auto-extension.
<p>- Results are undefined if the given db is closed by an auto-extension.
*/
int call(sqlite3 db);
}
@@ -13,18 +13,14 @@
*/
package org.sqlite.jni;
/**
Callback for use with sqlite3_busy_handler()
Callback for use with {@link SQLite3Jni#sqlite3_busy_handler}.
*/
public abstract class BusyHandlerCallback implements SQLite3CallbackProxy {
public interface BusyHandlerCallback extends CallbackProxy {
/**
Must function as documented for the C-level
sqlite3_busy_handler() callback argument, minus the (void*)
argument the C-level function requires.
Any exceptions thrown by this callback are suppressed in order to
retain the C-style API semantics of the JNI bindings.
*/
public abstract int call(int n);
int call(int n);
}
@@ -0,0 +1,44 @@
/*
** 2023-08-25
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
/**
This marker interface exists soley for use as a documentation and
class-grouping tool. It should be applied to interfaces or
classes which have a call() method implementing some specific
callback interface on behalf of the C library.
<p>Unless very explicitely documented otherwise, callbacks must
never throw. Any which do throw but should not might trigger debug
output regarding the error, but the exception will not be
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.
<p>Callbacks of this style follow a common naming convention:
<p>1) They use the UpperCamelCase form of the C function they're
proxying for, minus the {@code sqlite3_} prefix, plus a {@code
Callback} suffix. e.g. {@code sqlite3_busy_handler()}'s callback is
named {@code BusyHandlerCallback}. Exceptions are made where that
would potentially be ambiguous, e.g. {@link ConfigSqllogCallback}
instead of {@code ConfigCallback} because the {@code
sqlite3_config()} interface may need to support more callback types
in the future.
<p>2) They all have a {@code call()} method but its signature is
callback-specific.
*/
public interface CallbackProxy {}
@@ -12,20 +12,24 @@
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
import org.sqlite.jni.annotation.NotNull;
/**
Callback for use with sqlite3_create_collation()
Callback for use with {@link SQLite3Jni#sqlite3_create_collation}.
@see AbstractCollationCallback
*/
public abstract class CollationCallback
implements SQLite3CallbackProxy, XDestroyCallback {
public interface CollationCallback
extends CallbackProxy, XDestroyCallback {
/**
Must compare the given byte arrays using memcmp() semantics.
Must compare the given byte arrays and return the result using
{@code memcmp()} semantics.
*/
public abstract int call(byte[] lhs, byte[] rhs);
int call(@NotNull byte[] lhs, @NotNull byte[] rhs);
/**
Called by SQLite when the collation is destroyed. If a collation
requires custom cleanup, override this method.
*/
public void xDestroy(){}
void xDestroy();
}
@@ -14,9 +14,9 @@
package org.sqlite.jni;
/**
Callback for use with sqlite3_collation_needed().
Callback for use with {@link SQLite3Jni#sqlite3_collation_needed}.
*/
public interface CollationNeededCallback extends SQLite3CallbackProxy {
public interface CollationNeededCallback extends CallbackProxy {
/**
Has the same semantics as the C-level sqlite3_create_collation()
callback.
@@ -14,9 +14,9 @@
package org.sqlite.jni;
/**
Callback for use with sqlite3_commit_hook()
Callback for use with {@link SQLite3Jni#sqlite3_commit_hook}.
*/
public interface CommitHookCallback extends SQLite3CallbackProxy {
public interface CommitHookCallback extends CallbackProxy {
/**
Works as documented for the C-level sqlite3_commit_hook()
callback. Must not throw.
@@ -1,5 +1,5 @@
/*
** 2023-07-21
** 2023-08-23
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
@@ -14,12 +14,12 @@
package org.sqlite.jni;
/**
A helper class which simply holds a single value. Its current use
is for communicating values out of anonymous classes, as doing so
requires a "final" reference.
A callback for use with sqlite3_config().
*/
public class ValueHolder<T> {
public T value;
public ValueHolder(){}
public ValueHolder(T v){value = v;}
public interface ConfigLogCallback {
/**
Must function as described for a C-level callback for
{@link SQLite3Jni#sqlite3_config(ConfigLogCallback)}, with the slight signature change.
*/
void call(int errCode, String msg);
}
@@ -1,27 +0,0 @@
/*
** 2023-08-04
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
/**
Fts5Function is used in conjunction with the
sqlite3_create_fts_function() JNI-bound API to give that native code
access to the callback functions needed in order to implement
FTS5 auxiliary functions in Java.
*/
public abstract class Fts5Function {
public abstract void xFunction(Fts5ExtensionApi pApi, Fts5Context pFts,
sqlite3_context pCtx, sqlite3_value argv[]);
public void xDestroy() {}
}
@@ -29,5 +29,18 @@ package org.sqlite.jni;
public class NativePointerHolder<ContextType> {
//! Only set from JNI, where access permissions don't matter.
private volatile long nativePointer = 0;
/**
For use ONLY by package-level APIs which act as proxies for
close/finalize operations. Such ops must call this to zero out
the pointer so that this object is not carrying a stale
pointer. This function returns the prior value of the pointer and
sets it to 0.
*/
final long clearNativePointer() {
final long rv = nativePointer;
nativePointer= 0;
return rv;
}
public final long getNativePointer(){ return nativePointer; }
}
-23
View File
@@ -1,23 +0,0 @@
package org.sqlite.jni;
/**
This annotation is for flagging parameters which may not legally be
null. Note that the C-style API does not throw any exceptions on
its own because it has a no-throw policy in order to retain its
C-style semantics, but it may trigger NullPointerExceptions (or
similar) if passed a null for a parameter flagged with this
annotation.
<p>This annotation is informational only. No policy is in place to
programmatically ensure that NotNull is conformed to in client
code.
<p>This annotation is solely for the use by the classes in this
package but is made public so that javadoc will link to it from the
annotated functions. It is not part of the public API and
client-level code must not rely on it.
*/
@java.lang.annotation.Documented
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER)
public @interface NotNull{}
-16
View File
@@ -1,16 +0,0 @@
package org.sqlite.jni;
/**
This annotation is for flagging parameters which may legally be
null, noting that they may behave differently if passed null but
are prepared to expect null as a value.
<p>This annotation is solely for the use by the classes in this
package but is made public so that javadoc will link to it from the
annotated functions. It is not part of the public API and
client-level code must not rely on it.
*/
@java.lang.annotation.Documented
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER)
public @interface Nullable{}
@@ -64,6 +64,27 @@ public final class OutputPointer {
}
}
/**
Output pointer for sqlite3_blob_open(). These
pointers can only be set by the JNI layer, not by client-level
code.
*/
public static final class sqlite3_blob {
private org.sqlite.jni.sqlite3_blob value;
/** Initializes with a null value. */
public sqlite3_blob(){value = null;}
/** Sets the current value to null. */
public void clear(){value = null;}
/** Returns the current value. */
public final org.sqlite.jni.sqlite3_blob get(){return value;}
/** Equivalent to calling get() then clear(). */
public final org.sqlite.jni.sqlite3_blob take(){
final org.sqlite.jni.sqlite3_blob v = value;
value = null;
return v;
}
}
/**
Output pointer for use with routines, such as sqlite3_prepare(),
which return a statement handle via an output pointer. These
@@ -108,6 +129,26 @@ public final class OutputPointer {
}
}
/**
Output pointer for use with native routines which return booleans
via integer output pointers.
*/
public static final class Bool {
/**
This is public for ease of use. Accessors are provided for
consistency with the higher-level types.
*/
public boolean value;
/** Initializes with the value 0. */
public Bool(){this(false);}
/** Initializes with the value v. */
public Bool(boolean v){value = v;}
/** Returns the current value. */
public final boolean get(){return value;}
/** Sets the current value to v. */
public final void set(boolean v){value = v;}
}
/**
Output pointer for use with native routines which return integers via
output pointers.
@@ -0,0 +1,78 @@
/*
** 2023-09-13
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
/**
Callback for use with {@link SQLite3Jni#sqlite3_prepare_multi}.
*/
public interface PrepareMultiCallback extends CallbackProxy {
/**
Gets passed a sqlite3_stmt which it may handle in arbitrary ways,
transfering ownership of it to this function.
sqlite3_prepare_multi() will _not_ finalize st - it is up
to the call() implementation how st is handled.
Must return 0 on success or an SQLITE_... code on error.
See the {@link Finalize} class for a wrapper which finalizes the
statement after calling a proxy PrepareMultiCallback.
*/
int call(sqlite3_stmt st);
/**
A PrepareMultiCallback impl which wraps a separate impl and finalizes
any sqlite3_stmt passed to its callback.
*/
public static final class Finalize implements PrepareMultiCallback {
private PrepareMultiCallback p;
/**
p is the proxy to call() when this.call() is called.
*/
public Finalize( PrepareMultiCallback p ){
this.p = p;
}
/**
Calls the call() method of the proxied callback and either returns its
result or propagates an exception. Either way, it passes its argument to
sqlite3_finalize() before returning.
*/
@Override public int call(sqlite3_stmt st){
try {
return this.p.call(st);
}finally{
SQLite3Jni.sqlite3_finalize(st);
}
}
}
/**
A PrepareMultiCallback impl which steps entirely through a result set,
ignoring all non-error results.
*/
public static final class StepAll implements PrepareMultiCallback {
public StepAll(){}
/**
Calls sqlite3_step() on st until it returns something other than
SQLITE_ROW. If the final result is SQLITE_DONE then 0 is returned,
else the result of the final step is returned.
*/
@Override public int call(sqlite3_stmt st){
int rc = SQLite3Jni.SQLITE_DONE;
while( SQLite3Jni.SQLITE_ROW == (rc = SQLite3Jni.sqlite3_step(st)) ){}
return SQLite3Jni.SQLITE_DONE==rc ? 0 : rc;
}
}
}
@@ -14,12 +14,12 @@
package org.sqlite.jni;
/**
Callback for use with sqlite3_preupdate_hook().
Callback for use with {@link SQLite3Jni#sqlite3_preupdate_hook}.
*/
public interface PreupdateHookCallback extends SQLite3CallbackProxy {
public interface PreupdateHookCallback extends CallbackProxy {
/**
Must function as described for the C-level sqlite3_preupdate_hook()
callback. Must not throw.
callback.
*/
void call(sqlite3 db, int op, String dbName, String dbTable,
long iKey1, long iKey2 );
@@ -14,13 +14,13 @@
package org.sqlite.jni;
/**
Callback for use with sqlite3_progress_handler()
Callback for use with {@link SQLite3Jni#sqlite3_progress_handler}.
*/
public interface ProgressHandlerCallback extends SQLite3CallbackProxy {
public interface ProgressHandlerCallback extends CallbackProxy {
/**
Works as documented for the C-level sqlite3_progress_handler() callback.
If it throws, the exception message is passed on to the db and
<p>If it throws, the exception message is passed on to the db and
the exception is suppressed.
*/
int call();
@@ -14,12 +14,12 @@
package org.sqlite.jni;
/**
Callback for use with sqlite3_rollback_hook()
Callback for use with {@link SQLite3Jni#sqlite3_rollback_hook}.
*/
public interface RollbackHookCallback extends SQLite3CallbackProxy {
public interface RollbackHookCallback extends CallbackProxy {
/**
Works as documented for the C-level sqlite3_rollback_hook()
callback. Must not throw.
callback.
*/
void call();
}
+5 -5
View File
@@ -31,7 +31,7 @@ package org.sqlite.jni;
SQLFunction base class and the method names and signatures used by
the UDF callback interfaces.
*/
public abstract class SQLFunction {
public interface SQLFunction {
/**
PerContextState assists aggregate and window functions in
@@ -53,11 +53,11 @@ public abstract class SQLFunction {
state, of a client-defined type (the T part of this class), which
persists across a "matching set" of the UDF's callbacks.
<p>This class is a helper providing commonly-needed functionality -
it is not required for use with aggregate or window functions.
<p>This class is a helper providing commonly-needed functionality
- it is not required for use with aggregate or window functions.
Client UDFs are free to perform such mappings using custom
approaches. The provided Aggregate<T> and Window<T> classes
use this.
approaches. The provided {@link AggregateFunction} and {@link
WindowFunction} classes use this.
*/
public static final class PerContextState<T> {
private final java.util.Map<Long,ValueHolder<T>> map
@@ -12,16 +12,13 @@
** This file contains the main application entry pointer for the
** SQLTester framework.
*/
package org.sqlite.jni.tester;
package org.sqlite.jni;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;
import java.util.regex.*;
import org.sqlite.jni.*;
import static org.sqlite.jni.SQLite3Jni.*;
import org.sqlite.jni.sqlite3;
/**
Modes for how to escape (or not) column values and names from
@@ -71,7 +68,7 @@ class SQLTesterException extends RuntimeException {
class DbException extends SQLTesterException {
DbException(sqlite3 db, int rc, boolean closeDb){
super("DB error #"+rc+": "+sqlite3_errmsg(db),true);
super("DB error #"+rc+": "+sqlite3_errmsg16(db),true);
if( closeDb ) sqlite3_close_v2(db);
}
DbException(sqlite3 db, int rc){
@@ -150,14 +147,17 @@ class Outer {
}
/**
This class provides an application which aims to implement the
<p>This class provides an application which aims to implement the
rudimentary SQL-driven test tool described in the accompanying
test-script-interpreter.md.
{@code test-script-interpreter.md}.
This is a work in progress.
<p>This class is an internal testing tool, not part of the public
interface but is (A) in the same package as the library because
access permissions require it to be so and (B) the JDK8 javadoc
offers no way to filter individual classes out of the doc
generation process (it can only exclude packages, but see (A)).
An instance of this application provides a core set of services
<p>An instance of this application provides a core set of services
which TestScript instances use for processing testing logic.
TestScripts, in turn, delegate the concrete test work to Command
objects, which the TestScript parses on their behalf.
@@ -181,7 +181,7 @@ public class SQLTester {
private int nTestFile = 0;
//! Number of scripts which were aborted.
private int nAbortedScript = 0;
//! Per-script test counter.
//! Incremented by test case handlers
private int nTest = 0;
//! True to enable column name output from execSql()
private boolean emitColNames;
@@ -270,7 +270,6 @@ public class SQLTester {
final long timeEnd = System.currentTimeMillis();
outln("🏁",(threw ? "" : "")," ",nTest," test(s) in ",
(timeEnd-timeStart),"ms.");
//ts.getFilename());
}
}
final long tEnd = System.currentTimeMillis();
@@ -336,7 +335,9 @@ public class SQLTester {
}
sqlite3 setCurrentDb(int n) throws Exception{
return affirmDbId(n).aDb[n];
affirmDbId(n);
iCurrentDb = n;
return this.aDb[n];
}
sqlite3 getCurrentDb(){ return aDb[iCurrentDb]; }
@@ -399,7 +400,7 @@ public class SQLTester {
nullView = "nil";
emitColNames = false;
iCurrentDb = 0;
dbInitSql.append("SELECT 1;");
//dbInitSql.append("SELECT 1;");
}
void setNullValue(String v){nullView = v;}
@@ -457,7 +458,7 @@ public class SQLTester {
private void appendDbErr(sqlite3 db, StringBuilder sb, int rc){
sb.append(org.sqlite.jni.ResultCode.getEntryForInt(rc)).append(' ');
final String msg = escapeSqlValue(sqlite3_errmsg(db));
final String msg = escapeSqlValue(sqlite3_errmsg16(db));
if( '{' == msg.charAt(0) ){
sb.append(msg);
}else{
@@ -474,12 +475,12 @@ public class SQLTester {
the db's result code.
appendMode specifies how/whether to append results to the result
buffer. lineMode specifies whether to output all results in a
buffer. rowMode specifies whether to output all results in a
single line or one line per row. If appendMode is
ResultBufferMode.NONE then lineMode is ignored and may be null.
ResultBufferMode.NONE then rowMode is ignored and may be null.
*/
public int execSql(sqlite3 db, boolean throwOnError,
ResultBufferMode appendMode, ResultRowMode lineMode,
ResultBufferMode appendMode, ResultRowMode rowMode,
String sql) throws SQLTesterException {
if( null==db && null==aDb[0] ){
// Delay opening of the initial db to enable tests to change its
@@ -561,7 +562,7 @@ public class SQLTester {
throw new SQLTesterException("Unhandled ResultBufferMode: "+appendMode);
}
}
if( ResultRowMode.NEWLINE == lineMode ){
if( ResultRowMode.NEWLINE == rowMode ){
spacing = 0;
sb.append('\n');
}
@@ -580,6 +581,10 @@ public class SQLTester {
}
}
}finally{
sqlite3_reset(stmt
/* In order to trigger an exception in the
INSERT...RETURNING locking scenario:
https://sqlite.org/forum/forumpost/36f7a2e7494897df */);
sqlite3_finalize(stmt);
}
if( 0!=rc && throwOnError ){
@@ -924,10 +929,10 @@ class RunCommand extends Command {
final sqlite3 db = (1==argv.length)
? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) );
final String sql = t.takeInputBuffer();
int rc = t.execSql(db, false, ResultBufferMode.NONE,
ResultRowMode.ONELINE, sql);
final int rc = t.execSql(db, false, ResultBufferMode.NONE,
ResultRowMode.ONELINE, sql);
if( 0!=rc && t.isVerbose() ){
String msg = sqlite3_errmsg(db);
String msg = sqlite3_errmsg16(db);
ts.verbose1(argv[0]," non-fatal command error #",rc,": ",
msg,"\nfor SQL:\n",sql);
}
@@ -948,8 +953,7 @@ class TableResultCommand extends Command {
if( !body.endsWith("\n--end") ){
ts.toss(argv[0], " must be terminated with --end.");
}else{
int n = body.length();
body = body.substring(0, n-6);
body = body.substring(0, body.length()-6);
}
final String[] globs = body.split("\\s*\\n\\s*");
if( globs.length < 1 ){
@@ -1124,8 +1128,9 @@ class TestScript {
}
public String getOutputPrefix(){
String rc = "["+(moduleName==null ? filename : moduleName)+"]";
String rc = "["+(moduleName==null ? "<unnamed>" : moduleName)+"]";
if( null!=testCaseName ) rc += "["+testCaseName+"]";
if( null!=filename ) rc += "["+filename+"]";
return rc + " line "+ cur.lineNo;
}
@@ -1238,14 +1243,15 @@ class TestScript {
final int oldPB = cur.putbackPos;
final int oldPBL = cur.putbackLineNo;
final int oldLine = cur.lineNo;
final String rc = getLine();
cur.peekedPos = cur.pos;
cur.peekedLineNo = cur.lineNo;
cur.pos = oldPos;
cur.lineNo = oldLine;
cur.putbackPos = oldPB;
cur.putbackLineNo = oldPBL;
return rc;
try{ return getLine(); }
finally{
cur.peekedPos = cur.pos;
cur.peekedLineNo = cur.lineNo;
cur.pos = oldPos;
cur.lineNo = oldLine;
cur.putbackPos = oldPB;
cur.putbackLineNo = oldPBL;
}
}
/**
@@ -1268,6 +1274,7 @@ class TestScript {
}
private boolean checkRequiredProperties(SQLTester t, String[] props) throws SQLTesterException{
if( true ) return false;
int nOk = 0;
for(String rp : props){
verbose1("REQUIRED_PROPERTIES: ",rp);
@@ -1288,6 +1295,12 @@ class TestScript {
t.appendDbInitSql("pragma temp_store=0;");
++nOk;
break;
case "AUTOVACUUM":
t.appendDbInitSql("pragma auto_vacuum=full;");
++nOk;
case "INCRVACUUM":
t.appendDbInitSql("pragma auto_vacuum=incremental;");
++nOk;
default:
break;
}
@@ -1326,9 +1339,9 @@ class TestScript {
m = patternRequiredProperties.matcher(line);
if( m.find() ){
final String rp = m.group(1);
//if( ! checkRequiredProperties( tester, rp.split("\\s+") ) ){
throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp);
//}
if( ! checkRequiredProperties( tester, rp.split("\\s+") ) ){
throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp);
}
}
m = patternMixedModuleName.matcher(line);
if( m.find() ){
@@ -1372,11 +1385,10 @@ class TestScript {
String line;
while( (null != (line = peekLine())) ){
checkForDirective(tester, line);
if( !isCommandLine(line, true) ){
if( isCommandLine(line, true) ) break;
else {
sb.append(line).append("\n");
consumePeeked();
}else{
break;
}
}
line = sb.toString();
@@ -1,34 +0,0 @@
/*
** 2023-08-25
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
/**
This marker interface exists soley for use as a documentation and
class-grouping tool. It should be applied to interfaces or
classes which have a call() method implementing some specific
callback interface on behalf of the C library.
<p>Callbacks of this style follow a common naming convention:
<p>1) They use the UpperCamelCase form of the C function they're
proxying for, minus the sqlite3_ prefix, plus a Callback
suffix. e.g. sqlite3_busy_handler()'s callback is named
BusyHandlerCallback. Exceptions are made where that would
potentially be ambiguous, e.g. ConfigSqllogCallback instead of
config_callback because the sqlite3_config() interface may need to
support more callback types in the future.
<p>2) They all have a call() method but its signature is
callback-specific.
*/
public interface SQLite3CallbackProxy {}
File diff suppressed because it is too large Load Diff
@@ -15,9 +15,9 @@ package org.sqlite.jni;
/**
SQLFunction subclass for creating scalar functions.
A SQLFunction implementation for scalar functions.
*/
public abstract class ScalarFunction extends SQLFunction {
public abstract class ScalarFunction implements SQLFunction {
/**
As for the xFunc() argument of the C API's
sqlite3_create_function(). If this function throws, it is
@@ -27,7 +27,7 @@ public abstract class ScalarFunction extends SQLFunction {
/**
Optionally override to be notified when the UDF is finalized by
SQLite.
SQLite. This implementation does nothing.
*/
public void xDestroy() {}
}
@@ -0,0 +1,35 @@
/*
** 2023-07-21
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
/**
A wrapper object for use with sqlite3_table_column_metadata().
They are populated only via that interface.
*/
public final class TableColumnMetadata {
OutputPointer.Bool pNotNull = new OutputPointer.Bool();
OutputPointer.Bool pPrimaryKey = new OutputPointer.Bool();
OutputPointer.Bool pAutoinc = new OutputPointer.Bool();
OutputPointer.String pzCollSeq = new OutputPointer.String();
OutputPointer.String pzDataType = new OutputPointer.String();
public TableColumnMetadata(){
}
public String getDataType(){ return pzDataType.value; }
public String getCollation(){ return pzCollSeq.value; }
public boolean isNotNull(){ return pNotNull.value; }
public boolean isPrimaryKey(){ return pPrimaryKey.value; }
public boolean isAutoincrement(){ return pAutoinc.value; }
}
+438 -118
View File
@@ -30,6 +30,24 @@ import java.util.concurrent.Future;
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
@interface ManualTest{}
/**
Annotation for Tester1 tests which mark those which must be skipped
in multi-threaded mode.
*/
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
@interface SingleThreadOnly{}
/**
A helper class which simply holds a single value. Its current use
is for communicating values out of anonymous classes, as doing so
requires a "final" reference.
*/
class ValueHolder<T> {
public T value;
public ValueHolder(){}
public ValueHolder(T v){value = v;}
}
public class Tester1 implements Runnable {
//! True when running in multi-threaded mode.
@@ -61,69 +79,74 @@ public class Tester1 implements Runnable {
static final Metrics metrics = new Metrics();
public synchronized static void outln(){
public static synchronized void outln(){
if( !quietMode ){
System.out.println("");
}
}
public synchronized static void outln(Object val){
public static synchronized void outPrefix(){
if( !quietMode ){
System.out.print(Thread.currentThread().getName()+": ");
}
}
public static synchronized void outln(Object val){
if( !quietMode ){
outPrefix();
System.out.println(val);
}
}
public synchronized static void out(Object val){
public static synchronized void out(Object val){
if( !quietMode ){
System.out.print(val);
}
}
@SuppressWarnings("unchecked")
public synchronized static void out(Object... vals){
public static synchronized void out(Object... vals){
if( !quietMode ){
System.out.print(Thread.currentThread().getName()+": ");
outPrefix();
for(Object v : vals) out(v);
}
}
@SuppressWarnings("unchecked")
public synchronized static void outln(Object... vals){
public static synchronized void outln(Object... vals){
if( !quietMode ){
out(vals); out("\n");
}
}
static volatile int affirmCount = 0;
public synchronized static void affirm(Boolean v, String comment){
public static synchronized int affirm(Boolean v, String comment){
++affirmCount;
if( false ) assert( v /* prefer assert over exception if it's enabled because
the JNI layer sometimes has to suppress exceptions,
so they might be squelched on their way back to the
top. */);
if( !v ) throw new RuntimeException(comment);
return affirmCount;
}
public static void affirm(Boolean v){
affirm(v, "Affirmation failed.");
}
@ManualTest /* because testing this for threading is pointless */
@SingleThreadOnly /* because it's thread-agnostic */
private void test1(){
affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER);
affirm(SQLITE_MAX_LENGTH > 0);
affirm(SQLITE_MAX_TRIGGER_DEPTH>0);
}
static sqlite3 createNewDb(){
public static sqlite3 createNewDb(){
final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
int rc = sqlite3_open(":memory:", out);
++metrics.dbOpen;
sqlite3 db = out.take();
if( 0!=rc ){
final String msg =
null==db ? sqlite3_errstr(rc) : sqlite3_errmsg(db);
null==db ? sqlite3_errstr(rc) : sqlite3_errmsg16(db);
sqlite3_close(db);
throw new RuntimeException("Opening db failed: "+msg);
}
@@ -134,11 +157,11 @@ public class Tester1 implements Runnable {
return db;
}
static void execSql(sqlite3 db, String[] sql){
public static void execSql(sqlite3 db, String[] sql){
execSql(db, String.join("", sql));
}
static int execSql(sqlite3 db, boolean throwOnError, String sql){
public static int execSql(sqlite3 db, boolean throwOnError, String sql){
OutputPointer.Int32 oTail = new OutputPointer.Int32();
final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8);
int pos = 0, n = 1;
@@ -174,18 +197,18 @@ public class Tester1 implements Runnable {
if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0;
if( 0!=rc && throwOnError){
throw new RuntimeException("db op failed with rc="
+rc+": "+sqlite3_errmsg(db));
+rc+": "+sqlite3_errmsg16(db));
}
return rc;
}
static void execSql(sqlite3 db, String sql){
public static void execSql(sqlite3 db, String sql){
execSql(db, true, sql);
}
static sqlite3_stmt prepare(sqlite3 db, boolean throwOnError, String sql){
public static sqlite3_stmt prepare(sqlite3 db, boolean throwOnError, String sql){
final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
int rc = sqlite3_prepare(db, sql, outStmt);
int rc = sqlite3_prepare_v2(db, sql, outStmt);
if( throwOnError ){
affirm( 0 == rc );
}
@@ -196,9 +219,11 @@ public class Tester1 implements Runnable {
}
return rv;
}
static sqlite3_stmt prepare(sqlite3 db, String sql){
public static sqlite3_stmt prepare(sqlite3 db, String sql){
return prepare(db, true, sql);
}
private void showCompileOption(){
int i = 0;
String optName;
@@ -207,7 +232,15 @@ public class Tester1 implements Runnable {
outln("\t"+optName+"\t (used="+
sqlite3_compileoption_used(optName)+")");
}
}
private void testCompileOption(){
int i = 0;
String optName;
for( ; null != (optName = sqlite3_compileoption_get(i)); ++i){
}
affirm( i > 10 );
affirm( null==sqlite3_compileoption_get(-1) );
}
private void testOpenDb1(){
@@ -253,10 +286,11 @@ public class Tester1 implements Runnable {
affirm(0 == rc);
sqlite3_stmt stmt = outStmt.take();
affirm(0 != stmt.getNativePointer());
affirm( !sqlite3_stmt_readonly(stmt) );
affirm( db == sqlite3_db_handle(stmt) );
rc = sqlite3_step(stmt);
if( SQLITE_DONE != rc ){
outln("step failed ??? ",rc, " ",sqlite3_errmsg(db));
outln("step failed ??? ",rc, " ",sqlite3_errmsg16(db));
}
affirm(SQLITE_DONE == rc);
sqlite3_finalize(stmt);
@@ -312,7 +346,7 @@ public class Tester1 implements Runnable {
stmt = sqlite3_prepare(db, "intentional error");
affirm( null==stmt );
affirm( 0!=sqlite3_errcode(db) );
affirm( 0==sqlite3_errmsg(db).indexOf("near \"intentional\"") );
affirm( 0==sqlite3_errmsg16(db).indexOf("near \"intentional\"") );
sqlite3_finalize(stmt);
stmt = sqlite3_prepare(db, "/* empty input*/\n-- comments only");
affirm( null==stmt );
@@ -328,6 +362,7 @@ public class Tester1 implements Runnable {
affirm(1 == sqlite3_bind_parameter_count(stmt));
final int paramNdx = sqlite3_bind_parameter_index(stmt, ":a");
affirm(1 == paramNdx);
affirm( ":a".equals(sqlite3_bind_parameter_name(stmt, paramNdx)));
int total1 = 0;
long rowid = -1;
int changes = sqlite3_changes(db);
@@ -353,6 +388,7 @@ public class Tester1 implements Runnable {
affirm(sqlite3_changes64(db) > changes64);
affirm(sqlite3_total_changes64(db) > changesT64);
stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
affirm( sqlite3_stmt_readonly(stmt) );
int total2 = 0;
while( SQLITE_ROW == sqlite3_step(stmt) ){
total2 += sqlite3_column_int(stmt, 0);
@@ -368,57 +404,59 @@ public class Tester1 implements Runnable {
}
private void testBindFetchInt64(){
sqlite3 db = createNewDb();
execSql(db, "CREATE TABLE t(a)");
sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
long total1 = 0;
for(long i = 0xffffffff; i < 0xffffffff + 3; ++i ){
total1 += i;
sqlite3_bind_int64(stmt, 1, i);
sqlite3_step(stmt);
sqlite3_reset(stmt);
try (sqlite3 db = createNewDb()){
execSql(db, "CREATE TABLE t(a)");
sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
long total1 = 0;
for(long i = 0xffffffff; i < 0xffffffff + 3; ++i ){
total1 += i;
sqlite3_bind_int64(stmt, 1, i);
sqlite3_step(stmt);
sqlite3_reset(stmt);
}
sqlite3_finalize(stmt);
stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
long total2 = 0;
while( SQLITE_ROW == sqlite3_step(stmt) ){
total2 += sqlite3_column_int64(stmt, 0);
}
sqlite3_finalize(stmt);
affirm(total1 == total2);
//sqlite3_close_v2(db);
}
sqlite3_finalize(stmt);
stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
long total2 = 0;
while( SQLITE_ROW == sqlite3_step(stmt) ){
total2 += sqlite3_column_int64(stmt, 0);
}
sqlite3_finalize(stmt);
affirm(total1 == total2);
sqlite3_close_v2(db);
}
private void testBindFetchDouble(){
sqlite3 db = createNewDb();
execSql(db, "CREATE TABLE t(a)");
sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
double total1 = 0;
for(double i = 1.5; i < 5.0; i = i + 1.0 ){
total1 += i;
sqlite3_bind_double(stmt, 1, i);
sqlite3_step(stmt);
sqlite3_reset(stmt);
try (sqlite3 db = createNewDb()){
execSql(db, "CREATE TABLE t(a)");
sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
double total1 = 0;
for(double i = 1.5; i < 5.0; i = i + 1.0 ){
total1 += i;
sqlite3_bind_double(stmt, 1, i);
sqlite3_step(stmt);
sqlite3_reset(stmt);
}
sqlite3_finalize(stmt);
stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
double total2 = 0;
int counter = 0;
while( SQLITE_ROW == sqlite3_step(stmt) ){
++counter;
total2 += sqlite3_column_double(stmt, 0);
}
affirm(4 == counter);
sqlite3_finalize(stmt);
affirm(total2<=total1+0.01 && total2>=total1-0.01);
//sqlite3_close_v2(db);
}
sqlite3_finalize(stmt);
stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
double total2 = 0;
int counter = 0;
while( SQLITE_ROW == sqlite3_step(stmt) ){
++counter;
total2 += sqlite3_column_double(stmt, 0);
}
affirm(4 == counter);
sqlite3_finalize(stmt);
affirm(total2<=total1+0.01 && total2>=total1-0.01);
sqlite3_close_v2(db);
}
private void testBindFetchText(){
sqlite3 db = createNewDb();
execSql(db, "CREATE TABLE t(a)");
sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
String[] list1 = { "hell🤩", "w😃rld", "!" };
String[] list1 = { "hell🤩", "w😃rld", "!🤩" };
int rc;
int n = 0;
for( String e : list1 ){
@@ -435,15 +473,45 @@ public class Tester1 implements Runnable {
StringBuilder sbuf = new StringBuilder();
n = 0;
while( SQLITE_ROW == sqlite3_step(stmt) ){
String txt = sqlite3_column_text16(stmt, 0);
//outln("txt = "+txt);
final sqlite3_value sv = sqlite3_value_dup(sqlite3_column_value(stmt,0));
final String txt = sqlite3_column_text16(stmt, 0);
sbuf.append( txt );
affirm( txt.equals(new String(
sqlite3_column_text(stmt, 0),
StandardCharsets.UTF_8
)) );
affirm( txt.length() < sqlite3_value_bytes(sv) );
affirm( txt.equals(new String(
sqlite3_value_text(sv),
StandardCharsets.UTF_8)) );
affirm( txt.length() == sqlite3_value_bytes16(sv)/2 );
affirm( txt.equals(sqlite3_value_text16(sv)) );
sqlite3_value_free(sv);
++n;
}
sqlite3_finalize(stmt);
affirm(3 == n);
affirm("w😃rldhell🤩!".equals(sbuf.toString()));
sqlite3_close_v2(db);
affirm("w😃rldhell🤩!🤩".equals(sbuf.toString()));
try( sqlite3_stmt stmt2 = prepare(db, "SELECT ?, ?") ){
rc = sqlite3_bind_text(stmt2, 1, "");
affirm( 0==rc );
rc = sqlite3_bind_text(stmt2, 2, (String)null);
affirm( 0==rc );
rc = sqlite3_step(stmt2);
affirm( SQLITE_ROW==rc );
byte[] colBa = sqlite3_column_text(stmt2, 0);
affirm( 0==colBa.length );
colBa = sqlite3_column_text(stmt2, 1);
affirm( null==colBa );
//sqlite3_finalize(stmt);
}
if(true){
sqlite3_close_v2(db);
}else{
// Let the Object.finalize() override deal with it.
}
}
private void testBindFetchBlob(){
@@ -562,7 +630,7 @@ public class Tester1 implements Runnable {
affirm( 1 == xDestroyCalled.value );
}
@ManualTest /* because threading is meaningless here */
@SingleThreadOnly /* because it's thread-agnostic */
private void testToUtf8(){
/**
https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html
@@ -668,7 +736,7 @@ public class Tester1 implements Runnable {
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
affirm( 0 != rc );
affirm( sqlite3_errmsg(db).indexOf("an xFinal") > 0 );
affirm( sqlite3_errmsg16(db).indexOf("an xFinal") > 0 );
SQLFunction funcSc = new ScalarFunction(){
@Override public void xFunc(sqlite3_context cx, sqlite3_value[] args){
@@ -682,25 +750,31 @@ public class Tester1 implements Runnable {
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
affirm( 0 != rc );
affirm( sqlite3_errmsg(db).indexOf("an xFunc") > 0 );
affirm( sqlite3_errmsg16(db).indexOf("an xFunc") > 0 );
rc = sqlite3_create_function(db, "mysca", 1, -1, funcSc);
affirm( SQLITE_FORMAT==rc, "invalid encoding value." );
sqlite3_close_v2(db);
}
@SingleThreadOnly
private void testUdfJavaObject(){
affirm( !mtMode );
final sqlite3 db = createNewDb();
final ValueHolder<sqlite3> testResult = new ValueHolder<>(db);
final ValueHolder<Integer> boundObj = new ValueHolder<>(42);
final SQLFunction func = new ScalarFunction(){
public void xFunc(sqlite3_context cx, sqlite3_value args[]){
sqlite3_result_java_object(cx, testResult.value);
affirm( sqlite3_value_java_object(args[0]) == boundObj );
}
};
int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func);
affirm(0 == rc);
final sqlite3_stmt stmt = prepare(db, "select myfunc()");
sqlite3_stmt stmt = prepare(db, "select myfunc(?)");
affirm( 0 != stmt.getNativePointer() );
affirm( testResult.value == db );
rc = sqlite3_bind_java_object(stmt, 1, boundObj);
affirm( 0==rc );
int n = 0;
if( SQLITE_ROW == sqlite3_step(stmt) ){
final sqlite3_value v = sqlite3_column_value(stmt, 0);
@@ -714,6 +788,7 @@ public class Tester1 implements Runnable {
}
sqlite3_finalize(stmt);
affirm( 1 == n );
affirm( 0==sqlite3_db_release_memory(db) );
sqlite3_close_v2(db);
}
@@ -745,7 +820,7 @@ public class Tester1 implements Runnable {
int rc = sqlite3_create_function(db, "myfunc", 1, SQLITE_UTF8, func);
affirm(0 == rc);
sqlite3_stmt stmt = prepare(db, "select myfunc(a), myfunc(a+10) from t");
affirm( null != stmt );
affirm( 0==sqlite3_stmt_status(stmt, SQLITE_STMTSTATUS_RUN, false) );
int n = 0;
if( SQLITE_ROW == sqlite3_step(stmt) ){
int v = sqlite3_column_int(stmt, 0);
@@ -757,6 +832,7 @@ public class Tester1 implements Runnable {
affirm( 1==n );
affirm(!xFinalNull.value);
sqlite3_reset(stmt);
affirm( 1==sqlite3_stmt_status(stmt, SQLITE_STMTSTATUS_RUN, false) );
// Ensure that the accumulator is reset on subsequent calls...
n = 0;
if( SQLITE_ROW == sqlite3_step(stmt) ){
@@ -923,8 +999,8 @@ public class Tester1 implements Runnable {
affirm( 7 == counter.value );
}
@ManualTest /* because threads inherently break this test */
private void testBusy(){
@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();
@@ -1088,7 +1164,7 @@ public class Tester1 implements Runnable {
This test is functionally identical to testUpdateHook(), only with a
different callback type.
*/
private synchronized void testPreUpdateHook(){
private void testPreUpdateHook(){
if( !sqlite3_compileoption_used("ENABLE_PREUPDATE_HOOK") ){
//outln("Skipping testPreUpdateHook(): no pre-update hook support.");
return;
@@ -1198,8 +1274,8 @@ public class Tester1 implements Runnable {
it throws.
*/
@SuppressWarnings("unchecked")
@ManualTest /* because the Fts5 parts are not yet known to be
thread-safe */
@SingleThreadOnly /* because the Fts5 parts are not yet known to be
thread-safe */
private void testFts5() throws Exception {
if( !sqlite3_compileoption_used("ENABLE_FTS5") ){
//outln("SQLITE_ENABLE_FTS5 is not set. Skipping FTS5 tests.");
@@ -1210,7 +1286,10 @@ public class Tester1 implements Runnable {
Class t = Class.forName("org.sqlite.jni.TesterFts5");
java.lang.reflect.Constructor ctor = t.getConstructor();
ctor.setAccessible(true);
final long timeStart = System.currentTimeMillis();
ctor.newInstance() /* will run all tests */;
final long timeEnd = System.currentTimeMillis();
outln("FTS5 Tests done in ",(timeEnd - timeStart),"ms");
}catch(ClassNotFoundException e){
outln("FTS5 classes not loaded.");
err = e;
@@ -1250,13 +1329,13 @@ public class Tester1 implements Runnable {
sqlite3_close(db);
}
@ManualTest/* because multiple threads legitimately make these
results unpredictable */
@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 AutoExtensionCallback ax = new AutoExtensionCallback(){
@Override public synchronized int call(sqlite3 db){
@Override public int call(sqlite3 db){
++val.value;
if( null!=toss.value ){
throw new RuntimeException(toss.value);
@@ -1283,7 +1362,7 @@ public class Tester1 implements Runnable {
sqlite3 db = createNewDb();
affirm( 4==val.value );
execSql(db, "ATTACH ':memory' as foo");
execSql(db, "ATTACH ':memory:' as foo");
affirm( 4==val.value, "ATTACH uses the same connection, not sub-connections." );
sqlite3_close(db);
db = null;
@@ -1342,7 +1421,219 @@ public class Tester1 implements Runnable {
affirm( 8 == val.value );
}
@ManualTest /* because we only want to run this test manually */
private void testColumnMetadata(){
final sqlite3 db = createNewDb();
execSql(db, new String[] {
"CREATE TABLE t(a duck primary key not null collate noCase); ",
"INSERT INTO t(a) VALUES(1),(2),(3);"
});
OutputPointer.Bool bNotNull = new OutputPointer.Bool();
OutputPointer.Bool bPrimaryKey = new OutputPointer.Bool();
OutputPointer.Bool bAutoinc = new OutputPointer.Bool();
OutputPointer.String zCollSeq = new OutputPointer.String();
OutputPointer.String zDataType = new OutputPointer.String();
int rc = sqlite3_table_column_metadata(
db, "main", "t", "a", zDataType, zCollSeq,
bNotNull, bPrimaryKey, bAutoinc);
affirm( 0==rc );
affirm( bPrimaryKey.value );
affirm( !bAutoinc.value );
affirm( bNotNull.value );
affirm( "noCase".equals(zCollSeq.value) );
affirm( "duck".equals(zDataType.value) );
final TableColumnMetadata m =
sqlite3_table_column_metadata(db, "main", "t", "a");
affirm( null != m );
affirm( bPrimaryKey.value == m.isPrimaryKey() );
affirm( bAutoinc.value == m.isAutoincrement() );
affirm( bNotNull.value == m.isNotNull() );
affirm( zCollSeq.value.equals(m.getCollation()) );
affirm( zDataType.value.equals(m.getDataType()) );
affirm( null == sqlite3_table_column_metadata(db, "nope", "t", "a") );
sqlite3_close_v2(db);
}
private void testTxnState(){
final sqlite3 db = createNewDb();
affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
affirm( sqlite3_get_autocommit(db) );
execSql(db, "BEGIN;");
affirm( !sqlite3_get_autocommit(db) );
affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
execSql(db, "SELECT * FROM sqlite_schema;");
affirm( SQLITE_TXN_READ == sqlite3_txn_state(db, "main") );
execSql(db, "CREATE TABLE t(a);");
affirm( SQLITE_TXN_WRITE == sqlite3_txn_state(db, null) );
execSql(db, "ROLLBACK;");
affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
sqlite3_close_v2(db);
}
private void testExplain(){
final sqlite3 db = createNewDb();
sqlite3_stmt stmt = prepare(db,"SELECT 1");
affirm( 0 == sqlite3_stmt_isexplain(stmt) );
int rc = sqlite3_stmt_explain(stmt, 1);
affirm( 1 == sqlite3_stmt_isexplain(stmt) );
rc = sqlite3_stmt_explain(stmt, 2);
affirm( 2 == sqlite3_stmt_isexplain(stmt) );
sqlite3_finalize(stmt);
sqlite3_close_v2(db);
}
private void testLimit(){
final sqlite3 db = createNewDb();
int v;
v = sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1);
affirm( v > 0 );
affirm( v == sqlite3_limit(db, SQLITE_LIMIT_LENGTH, v-1) );
affirm( v-1 == sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1) );
sqlite3_close_v2(db);
}
private void testComplete(){
affirm( 0==sqlite3_complete("select 1") );
affirm( 0!=sqlite3_complete("select 1;") );
affirm( 0!=sqlite3_complete("nope 'nope' 'nope' 1;"), "Yup" );
}
private void testKeyword(){
final int n = sqlite3_keyword_count();
affirm( n>0 );
affirm( !sqlite3_keyword_check("_nope_") );
affirm( sqlite3_keyword_check("seLect") );
affirm( null!=sqlite3_keyword_name(0) );
affirm( null!=sqlite3_keyword_name(n-1) );
affirm( null==sqlite3_keyword_name(n) );
}
private void testBackup(){
final sqlite3 dbDest = createNewDb();
try (sqlite3 dbSrc = createNewDb()) {
execSql(dbSrc, new String[]{
"pragma page_size=512; VACUUM;",
"create table t(a);",
"insert into t(a) values(1),(2),(3);"
});
affirm( null==sqlite3_backup_init(dbSrc,"main",dbSrc,"main") );
try (sqlite3_backup b = sqlite3_backup_init(dbDest,"main",dbSrc,"main")) {
affirm( null!=b );
affirm( b.getNativePointer()!=0 );
int rc;
while( SQLITE_DONE!=(rc = sqlite3_backup_step(b, 1)) ){
affirm( 0==rc );
}
affirm( sqlite3_backup_pagecount(b) > 0 );
rc = sqlite3_backup_finish(b);
affirm( 0==rc );
affirm( b.getNativePointer()==0 );
}
}
try (sqlite3_stmt stmt = prepare(dbDest,"SELECT sum(a) from t")) {
sqlite3_step(stmt);
affirm( sqlite3_column_int(stmt,0) == 6 );
}
sqlite3_close_v2(dbDest);
}
private void testRandomness(){
byte[] foo = new byte[20];
int i = 0;
for( byte b : foo ){
i += b;
}
affirm( i==0 );
sqlite3_randomness(foo);
for( byte b : foo ){
if(b!=0) ++i;
}
affirm( i!=0, "There's a very slight chance that 0 is actually correct." );
}
private void testBlobOpen(){
final sqlite3 db = createNewDb();
execSql(db, "CREATE TABLE T(a BLOB);"
+"INSERT INTO t(rowid,a) VALUES(1, 'def'),(2, 'XYZ');"
);
final OutputPointer.sqlite3_blob pOut = new OutputPointer.sqlite3_blob();
int rc = sqlite3_blob_open(db, "main", "t", "a",
sqlite3_last_insert_rowid(db), 1, pOut);
affirm( 0==rc );
sqlite3_blob b = pOut.take();
affirm( null!=b );
affirm( 0!=b.getNativePointer() );
affirm( 3==sqlite3_blob_bytes(b) );
rc = sqlite3_blob_write( b, new byte[] {100, 101, 102 /*"DEF"*/}, 0);
affirm( 0==rc );
rc = sqlite3_blob_close(b);
affirm( 0==rc );
rc = sqlite3_blob_close(b);
affirm( 0!=rc );
affirm( 0==b.getNativePointer() );
sqlite3_stmt stmt = prepare(db,"SELECT length(a), a FROM t ORDER BY a");
affirm( SQLITE_ROW == sqlite3_step(stmt) );
affirm( 3 == sqlite3_column_int(stmt,0) );
affirm( "def".equals(sqlite3_column_text16(stmt,1)) );
sqlite3_finalize(stmt);
b = sqlite3_blob_open(db, "main", "t", "a",
sqlite3_last_insert_rowid(db), 1);
affirm( null!=b );
rc = sqlite3_blob_reopen(b, 2);
affirm( 0==rc );
final byte[] tgt = new byte[3];
rc = sqlite3_blob_read(b, tgt, 0);
affirm( 0==rc );
affirm( 100==tgt[0] && 101==tgt[1] && 102==tgt[2], "DEF" );
rc = sqlite3_blob_close(b);
affirm( 0==rc );
sqlite3_close_v2(db);
}
private void testPrepareMulti(){
final sqlite3 db = createNewDb();
final String[] sql = {
"create table t(","a)",
"; insert into t(a) values(1),(2),(3);",
"select a from t;"
};
final List<sqlite3_stmt> liStmt = new ArrayList<sqlite3_stmt>();
final PrepareMultiCallback proxy = new PrepareMultiCallback.StepAll();
PrepareMultiCallback m = new PrepareMultiCallback() {
@Override public int call(sqlite3_stmt st){
liStmt.add(st);
return proxy.call(st);
}
};
int rc = sqlite3_prepare_multi(db, sql, m);
affirm( 0==rc );
affirm( liStmt.size() == 3 );
for( sqlite3_stmt st : liStmt ){
sqlite3_finalize(st);
}
sqlite3_close_v2(db);
}
/* Copy/paste/rename this to add new tests. */
private void _testTemplate(){
final sqlite3 db = createNewDb();
sqlite3_stmt stmt = prepare(db,"SELECT 1");
sqlite3_finalize(stmt);
sqlite3_close_v2(db);
}
@ManualTest /* we really only want to run this test manually */
private void testSleep(){
out("Sleeping briefly... ");
sqlite3_sleep(600);
@@ -1385,8 +1676,6 @@ public class Tester1 implements Runnable {
outln();
}
}
testToUtf8();
test1();
for(java.lang.reflect.Method m : mlist){
nap();
try{
@@ -1396,13 +1685,6 @@ public class Tester1 implements Runnable {
throw e;
}
}
if( !fromThread ){
testBusy();
if( !mtMode ){
testAutoExtension() /* threads rightfully muck up these results */;
testFts5();
}
}
synchronized( this.getClass() ){
++nTestRuns;
}
@@ -1453,6 +1735,7 @@ public class Tester1 implements Runnable {
Integer nRepeat = 1;
boolean forceFail = false;
boolean sqlLog = false;
boolean configLog = false;
boolean squelchTestOutput = false;
for( int i = 0; i < args.length; ){
String arg = args[i++];
@@ -1473,6 +1756,8 @@ public class Tester1 implements Runnable {
forceFail = true;
}else if(arg.equals("sqllog")){
sqlLog = true;
}else if(arg.equals("configlog")){
configLog = true;
}else if(arg.equals("naps")){
takeNaps = true;
}else if(arg.equals("q") || arg.equals("quiet")){
@@ -1483,26 +1768,9 @@ public class Tester1 implements Runnable {
}
}
{
// Build list of tests to run from the methods named test*().
testMethods = new ArrayList<>();
for(final java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){
final String name = m.getName();
if( name.equals("testFail") ){
if( forceFail ){
testMethods.add(m);
}
}else if( !m.isAnnotationPresent( ManualTest.class ) ){
if( name.startsWith("test") ){
testMethods.add(m);
}
}
}
}
if( sqlLog ){
if( sqlite3_compileoption_used("ENABLE_SQLLOG") ){
int rc = sqlite3_config( new ConfigSqllogCallback() {
final ConfigSqllogCallback log = new ConfigSqllogCallback() {
@Override public void call(sqlite3 db, String msg, int op){
switch(op){
case 0: outln("Opening db: ",db); break;
@@ -1510,22 +1778,64 @@ public class Tester1 implements Runnable {
case 2: outln("Closing db: ",db); break;
}
}
});
};
int rc = sqlite3_config( log );
affirm( 0==rc );
rc = sqlite3_config( (ConfigSqllogCallback)null );
affirm( 0==rc );
rc = sqlite3_config( log );
affirm( 0==rc );
}else{
outln("WARNING: -sqllog is not active because library was built ",
"without SQLITE_ENABLE_SQLLOG.");
}
}
if( configLog ){
final ConfigLogCallback log = new ConfigLogCallback() {
@Override public void call(int code, String msg){
outln("ConfigLogCallback: ",ResultCode.getEntryForInt(code),": ", msg);
};
};
int rc = sqlite3_config( log );
affirm( 0==rc );
rc = sqlite3_config( (ConfigLogCallback)null );
affirm( 0==rc );
rc = sqlite3_config( log );
affirm( 0==rc );
}
quietMode = squelchTestOutput;
outln("If you just saw warning messages regarding CallStaticObjectMethod, ",
"you are very likely seeing the side effects of a known openjdk8 ",
"bug. It is unsightly but does not affect the library.");
{
// Build list of tests to run from the methods named test*().
testMethods = new ArrayList<>();
int nSkipped = 0;
for(final java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){
final String name = m.getName();
if( name.equals("testFail") ){
if( forceFail ){
testMethods.add(m);
}
}else if( !m.isAnnotationPresent( ManualTest.class ) ){
if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){
if( 0==nSkipped++ ){
out("Skipping tests in multi-thread mode:");
}
out(" "+name+"()");
}else if( name.startsWith("test") ){
testMethods.add(m);
}
}
}
if( nSkipped>0 ) out("\n");
}
final long timeStart = System.currentTimeMillis();
int nLoop = 0;
switch( SQLITE_THREADSAFE ){ /* Sanity checking */
switch( sqlite3_threadsafe() ){ /* Sanity checking */
case 0:
affirm( SQLITE_ERROR==sqlite3_config( SQLITE_CONFIG_SINGLETHREAD ),
"Could not switch to single-thread mode." );
@@ -1551,19 +1861,21 @@ public class Tester1 implements Runnable {
outln("libversion_number: ",
sqlite3_libversion_number(),"\n",
sqlite3_libversion(),"\n",SQLITE_SOURCE_ID,"\n",
"SQLITE_THREADSAFE=",SQLITE_THREADSAFE);
outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each.");
"SQLITE_THREADSAFE=",sqlite3_threadsafe());
final boolean showLoopCount = (nRepeat>1 && nThread>1);
if( showLoopCount ){
outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each.");
}
if( takeNaps ) outln("Napping between tests is enabled.");
for( int n = 0; n < nRepeat; ++n ){
++nLoop;
if( showLoopCount ) out((1==nLoop ? "" : " ")+nLoop);
if( nThread<=1 ){
new Tester1(0).runTests(false);
continue;
}
Tester1.mtMode = true;
final ExecutorService ex = Executors.newFixedThreadPool( nThread );
//final List<Future<?>> futures = new ArrayList<>();
++nLoop;
out((1==nLoop ? "" : " ")+nLoop);
for( int i = 0; i < nThread; ++i ){
ex.submit( new Tester1(i), i );
}
@@ -1586,7 +1898,7 @@ public class Tester1 implements Runnable {
if( null!=err ) throw err;
}
}
outln();
if( showLoopCount ) outln();
quietMode = false;
final long timeEnd = System.currentTimeMillis();
@@ -1596,17 +1908,22 @@ public class Tester1 implements Runnable {
if( doSomethingForDev ){
sqlite3_jni_internal_details();
}
affirm( 0==sqlite3_release_memory(1) );
sqlite3_shutdown();
int nMethods = 0;
int nNatives = 0;
int nCanonical = 0;
final java.lang.reflect.Method[] declaredMethods =
SQLite3Jni.class.getDeclaredMethods();
for(java.lang.reflect.Method m : declaredMethods){
int mod = m.getModifiers();
final int mod = m.getModifiers();
if( 0!=(mod & java.lang.reflect.Modifier.STATIC) ){
final String name = m.getName();
if(name.startsWith("sqlite3_")){
++nMethods;
if( m.isAnnotationPresent( org.sqlite.jni.annotation.Canonical.class ) ){
++nCanonical;
}
if( 0!=(mod & java.lang.reflect.Modifier.NATIVE) ){
++nNatives;
}
@@ -1614,8 +1931,11 @@ public class Tester1 implements Runnable {
}
}
outln("\tSQLite3Jni.sqlite3_*() methods: "+
nNatives+" native methods and "+
(nMethods - nNatives)+" Java impls");
nMethods+" total, with "+
nNatives+" native, "+
(nMethods - nNatives)+" Java, ",
nCanonical," @Canonical"
);
outln("\tTotal test time = "
+(timeEnd - timeStart)+"ms");
}
+776 -33
View File
@@ -14,11 +14,15 @@
package org.sqlite.jni;
import static org.sqlite.jni.SQLite3Jni.*;
import static org.sqlite.jni.Tester1.*;
import org.sqlite.jni.*;
import org.sqlite.jni.fts5.*;
import java.util.*;
public class TesterFts5 {
private static void test1(){
Fts5ExtensionApi fea = Fts5ExtensionApi.getInstance();
final Fts5ExtensionApi fea = Fts5ExtensionApi.getInstance();
affirm( null != fea );
affirm( fea.getNativePointer() != 0 );
affirm( fea == Fts5ExtensionApi.getInstance() )/*singleton*/;
@@ -35,55 +39,794 @@ public class TesterFts5 {
});
final String pUserData = "This is pUserData";
ValueHolder<Boolean> xDestroyCalled = new ValueHolder<>(false);
ValueHolder<Integer> xFuncCount = new ValueHolder<>(0);
final int outputs[] = {0, 0};
final fts5_extension_function func = new fts5_extension_function(){
public void xFunction(Fts5ExtensionApi ext, Fts5Context fCx,
sqlite3_context pCx, sqlite3_value argv[]){
int nCols = ext.xColumnCount(fCx);
@Override public void call(Fts5ExtensionApi ext, Fts5Context fCx,
sqlite3_context pCx, sqlite3_value argv[]){
final int nCols = ext.xColumnCount(fCx);
affirm( 2 == nCols );
affirm( nCols == argv.length );
affirm( ext.xUserData(fCx) == pUserData );
if(true){
OutputPointer.String op = new OutputPointer.String();
for(int i = 0; i < nCols; ++i ){
int rc = ext.xColumnText(fCx, i, op);
affirm( 0 == rc );
final String val = op.value;
affirm( val.equals(sqlite3_value_text(argv[i])) );
//outln("xFunction col "+i+": "+val);
}
final OutputPointer.String op = new OutputPointer.String();
final OutputPointer.Int32 colsz = new OutputPointer.Int32();
final OutputPointer.Int64 colTotalSz = new OutputPointer.Int64();
for(int i = 0; i < nCols; ++i ){
int rc = ext.xColumnText(fCx, i, op);
affirm( 0 == rc );
final String val = op.value;
affirm( val.equals(sqlite3_value_text16(argv[i])) );
rc = ext.xColumnSize(fCx, i, colsz);
affirm( 0==rc );
affirm( 3==sqlite3_value_bytes(argv[i]) );
rc = ext.xColumnTotalSize(fCx, i, colTotalSz);
affirm( 0==rc );
}
++xFuncCount.value;
++outputs[0];
}
public void xDestroy(){
xDestroyCalled.value = true;
outputs[1] = 1;
}
};
int rc = fApi.xCreateFunction("myaux", pUserData, func);
affirm( 0==rc );
affirm( 0==xFuncCount.value );
affirm( 0==outputs[0] );
execSql(db, "select myaux(ft,a,b) from ft;");
affirm( 2==xFuncCount.value );
affirm( !xDestroyCalled.value );
affirm( 2==outputs[0] );
affirm( 0==outputs[1] );
sqlite3_close_v2(db);
affirm( xDestroyCalled.value );
affirm( 1==outputs[1] );
}
public TesterFts5(boolean verbose){
if(verbose){
final long timeStart = System.currentTimeMillis();
final int oldAffirmCount = Tester1.affirmCount;
test1();
final int affirmCount = Tester1.affirmCount - oldAffirmCount;
final long timeEnd = System.currentTimeMillis();
outln("FTS5 Tests done. Assertions checked = ",affirmCount,
", Total time = ",(timeEnd - timeStart),"ms");
}else{
test1();
/*
** Argument sql is a string containing one or more SQL statements
** separated by ";" characters. This function executes each of these
** statements against the database passed as the first argument. If
** no error occurs, the results of the SQL script are returned as
** an array of strings. If an error does occur, a RuntimeException is
** thrown.
*/
private static String[] sqlite3_exec(sqlite3 db, String sql) {
List<String> aOut = new ArrayList<String>();
/* Iterate through the list of SQL statements. For each, step through
** it and add any results to the aOut[] array. */
int rc = sqlite3_prepare_multi(db, sql, new PrepareMultiCallback() {
@Override public int call(sqlite3_stmt pStmt){
while( SQLITE_ROW==sqlite3_step(pStmt) ){
int ii;
for(ii=0; ii<sqlite3_column_count(pStmt); ii++){
aOut.add( sqlite3_column_text16(pStmt, ii) );
}
}
return sqlite3_finalize(pStmt);
}
});
if( rc!=SQLITE_OK ){
throw new RuntimeException(sqlite3_errmsg16(db));
}
/* Convert to array and return */
String[] arr = new String[aOut.size()];
return aOut.toArray(arr);
}
/*
** Execute the SQL script passed as the second parameter via
** sqlite3_exec(). Then affirm() that the results, when converted to
** a string, match the value of the 3rd parameter. Example:
**
** do_execsql_test(db, "SELECT 'abc'", "[abc]");
**
*/
private static void do_execsql_test(sqlite3 db, String sql, String expect) {
String res = Arrays.toString( sqlite3_exec(db, sql) );
affirm( res.equals(expect),
"got {" + res + "} expected {" + expect + "}"
);
}
private static void do_execsql_test(sqlite3 db, String sql){
do_execsql_test(db, sql, "[]");
}
/*
** Create the following custom SQL functions:
**
** fts5_rowid()
** fts5_columncount()
*/
private static void create_test_functions(sqlite3 db){
/*
** A user-defined-function fts5_rowid() that uses xRowid()
*/
fts5_extension_function fts5_rowid = new fts5_extension_function(){
@Override public void call(
Fts5ExtensionApi ext,
Fts5Context fCx,
sqlite3_context pCx,
sqlite3_value argv[]
){
long rowid = ext.xRowid(fCx);
sqlite3_result_int64(pCx, rowid);
}
public void xDestroy(){ }
};
/*
** fts5_columncount() - xColumnCount()
*/
fts5_extension_function fts5_columncount = new fts5_extension_function(){
@Override public void call(
Fts5ExtensionApi ext,
Fts5Context fCx,
sqlite3_context pCx,
sqlite3_value argv[]
){
int nCol = ext.xColumnCount(fCx);
sqlite3_result_int(pCx, nCol);
}
public void xDestroy(){ }
};
/*
** fts5_columnsize() - xColumnSize()
*/
fts5_extension_function fts5_columnsize = new fts5_extension_function(){
@Override public void call(
Fts5ExtensionApi ext,
Fts5Context fCx,
sqlite3_context pCx,
sqlite3_value argv[]
){
if( argv.length!=1 ){
throw new RuntimeException("fts5_columncount: wrong number of args");
}
int iCol = sqlite3_value_int(argv[0]);
OutputPointer.Int32 piSz = new OutputPointer.Int32();
int rc = ext.xColumnSize(fCx, iCol, piSz);
if( rc!=SQLITE_OK ){
throw new RuntimeException( sqlite3_errstr(rc) );
}
sqlite3_result_int(pCx, piSz.get());
}
public void xDestroy(){ }
};
/*
** fts5_columntext() - xColumnText()
*/
fts5_extension_function fts5_columntext = new fts5_extension_function(){
@Override public void call(
Fts5ExtensionApi ext,
Fts5Context fCx,
sqlite3_context pCx,
sqlite3_value argv[]
){
if( argv.length!=1 ){
throw new RuntimeException("fts5_columntext: wrong number of args");
}
int iCol = sqlite3_value_int(argv[0]);
OutputPointer.String pzText = new OutputPointer.String();
int rc = ext.xColumnText(fCx, iCol, pzText);
if( rc!=SQLITE_OK ){
throw new RuntimeException( sqlite3_errstr(rc) );
}
sqlite3_result_text16(pCx, pzText.get());
}
public void xDestroy(){ }
};
/*
** fts5_columntotalsize() - xColumnTotalSize()
*/
fts5_extension_function fts5_columntsize = new fts5_extension_function(){
@Override public void call(
Fts5ExtensionApi ext,
Fts5Context fCx,
sqlite3_context pCx,
sqlite3_value argv[]
){
if( argv.length!=1 ){
throw new RuntimeException(
"fts5_columntotalsize: wrong number of args"
);
}
int iCol = sqlite3_value_int(argv[0]);
OutputPointer.Int64 piSz = new OutputPointer.Int64();
int rc = ext.xColumnTotalSize(fCx, iCol, piSz);
if( rc!=SQLITE_OK ){
throw new RuntimeException( sqlite3_errstr(rc) );
}
sqlite3_result_int64(pCx, piSz.get());
}
public void xDestroy(){ }
};
/*
** fts5_aux(<fts>, <value>);
*/
class fts5_aux implements fts5_extension_function {
@Override public void call(
Fts5ExtensionApi ext,
Fts5Context fCx,
sqlite3_context pCx,
sqlite3_value argv[]
){
if( argv.length>1 ){
throw new RuntimeException("fts5_aux: wrong number of args");
}
boolean bClear = (argv.length==1);
Object obj = ext.xGetAuxdata(fCx, bClear);
if( obj instanceof String ){
sqlite3_result_text16(pCx, (String)obj);
}
if( argv.length==1 ){
String val = sqlite3_value_text16(argv[0]);
if( !val.equals("") ){
ext.xSetAuxdata(fCx, val);
}
}
}
public void xDestroy(){ }
};
/*
** fts5_inst(<fts>);
**
** This is used to test the xInstCount() and xInst() APIs. It returns a
** text value containing a Tcl list with xInstCount() elements. Each
** element is itself a list of 3 integers - the phrase number, column
** number and token offset returned by each call to xInst().
*/
fts5_extension_function fts5_inst = new fts5_extension_function(){
@Override public void call(
Fts5ExtensionApi ext,
Fts5Context fCx,
sqlite3_context pCx,
sqlite3_value argv[]
){
if( argv.length!=0 ){
throw new RuntimeException("fts5_inst: wrong number of args");
}
OutputPointer.Int32 pnInst = new OutputPointer.Int32();
OutputPointer.Int32 piPhrase = new OutputPointer.Int32();
OutputPointer.Int32 piCol = new OutputPointer.Int32();
OutputPointer.Int32 piOff = new OutputPointer.Int32();
String ret = new String();
int rc = ext.xInstCount(fCx, pnInst);
int nInst = pnInst.get();
int ii;
for(ii=0; rc==SQLITE_OK && ii<nInst; ii++){
ext.xInst(fCx, ii, piPhrase, piCol, piOff);
if( ii>0 ) ret += " ";
ret += "{"+piPhrase.get()+" "+piCol.get()+" "+piOff.get()+"}";
}
sqlite3_result_text(pCx, ret);
}
public void xDestroy(){ }
};
/*
** fts5_pinst(<fts>);
**
** Like SQL function fts5_inst(), except using the following
**
** xPhraseCount
** xPhraseFirst
** xPhraseNext
*/
fts5_extension_function fts5_pinst = new fts5_extension_function(){
@Override public void call(
Fts5ExtensionApi ext,
Fts5Context fCx,
sqlite3_context pCx,
sqlite3_value argv[]
){
if( argv.length!=0 ){
throw new RuntimeException("fts5_pinst: wrong number of args");
}
OutputPointer.Int32 piCol = new OutputPointer.Int32();
OutputPointer.Int32 piOff = new OutputPointer.Int32();
String ret = new String();
int rc = SQLITE_OK;
int nPhrase = ext.xPhraseCount(fCx);
int ii;
for(ii=0; rc==SQLITE_OK && ii<nPhrase; ii++){
Fts5PhraseIter pIter = new Fts5PhraseIter();
for(rc = ext.xPhraseFirst(fCx, ii, pIter, piCol, piOff);
rc==SQLITE_OK && piCol.get()>=0;
ext.xPhraseNext(fCx, pIter, piCol, piOff)
){
if( !ret.equals("") ) ret += " ";
ret += "{"+ii+" "+piCol.get()+" "+piOff.get()+"}";
}
}
if( rc!=SQLITE_OK ){
throw new RuntimeException("fts5_pinst: rc=" + rc);
}else{
sqlite3_result_text(pCx, ret);
}
}
public void xDestroy(){ }
};
/*
** fts5_pcolinst(<fts>);
**
** Like SQL function fts5_pinst(), except using the following
**
** xPhraseFirstColumn
** xPhraseNextColumn
*/
fts5_extension_function fts5_pcolinst = new fts5_extension_function(){
@Override public void call(
Fts5ExtensionApi ext,
Fts5Context fCx,
sqlite3_context pCx,
sqlite3_value argv[]
){
if( argv.length!=0 ){
throw new RuntimeException("fts5_pcolinst: wrong number of args");
}
OutputPointer.Int32 piCol = new OutputPointer.Int32();
String ret = new String();
int rc = SQLITE_OK;
int nPhrase = ext.xPhraseCount(fCx);
int ii;
for(ii=0; rc==SQLITE_OK && ii<nPhrase; ii++){
Fts5PhraseIter pIter = new Fts5PhraseIter();
for(rc = ext.xPhraseFirstColumn(fCx, ii, pIter, piCol);
rc==SQLITE_OK && piCol.get()>=0;
ext.xPhraseNextColumn(fCx, pIter, piCol)
){
if( !ret.equals("") ) ret += " ";
ret += "{"+ii+" "+piCol.get()+"}";
}
}
if( rc!=SQLITE_OK ){
throw new RuntimeException("fts5_pcolinst: rc=" + rc);
}else{
sqlite3_result_text(pCx, ret);
}
}
public void xDestroy(){ }
};
/*
** fts5_rowcount(<fts>);
*/
fts5_extension_function fts5_rowcount = new fts5_extension_function(){
@Override public void call(
Fts5ExtensionApi ext,
Fts5Context fCx,
sqlite3_context pCx,
sqlite3_value argv[]
){
if( argv.length!=0 ){
throw new RuntimeException("fts5_rowcount: wrong number of args");
}
OutputPointer.Int64 pnRow = new OutputPointer.Int64();
int rc = ext.xRowCount(fCx, pnRow);
if( rc==SQLITE_OK ){
sqlite3_result_int64(pCx, pnRow.get());
}else{
throw new RuntimeException("fts5_rowcount: rc=" + rc);
}
}
public void xDestroy(){ }
};
/*
** fts5_phrasesize(<fts>);
*/
fts5_extension_function fts5_phrasesize = new fts5_extension_function(){
@Override public void call(
Fts5ExtensionApi ext,
Fts5Context fCx,
sqlite3_context pCx,
sqlite3_value argv[]
){
if( argv.length!=1 ){
throw new RuntimeException("fts5_phrasesize: wrong number of args");
}
int iPhrase = sqlite3_value_int(argv[0]);
int sz = ext.xPhraseSize(fCx, iPhrase);
sqlite3_result_int(pCx, sz);
}
public void xDestroy(){ }
};
/*
** fts5_phrasehits(<fts>, <phrase-number>);
**
** Use the xQueryPhrase() API to determine how many hits, in total,
** there are for phrase <phrase-number> in the database.
*/
fts5_extension_function fts5_phrasehits = new fts5_extension_function(){
@Override public void call(
Fts5ExtensionApi ext,
Fts5Context fCx,
sqlite3_context pCx,
sqlite3_value argv[]
){
if( argv.length!=1 ){
throw new RuntimeException("fts5_phrasesize: wrong number of args");
}
int iPhrase = sqlite3_value_int(argv[0]);
int rc = SQLITE_OK;
class MyCallback implements Fts5ExtensionApi.XQueryPhraseCallback {
public int nRet = 0;
public int getRet() { return nRet; }
@Override
public int call(Fts5ExtensionApi fapi, Fts5Context cx){
OutputPointer.Int32 pnInst = new OutputPointer.Int32();
int rc = fapi.xInstCount(cx, pnInst);
nRet += pnInst.get();
return rc;
}
};
MyCallback xCall = new MyCallback();
rc = ext.xQueryPhrase(fCx, iPhrase, xCall);
if( rc!=SQLITE_OK ){
throw new RuntimeException("fts5_phrasehits: rc=" + rc);
}
sqlite3_result_int(pCx, xCall.getRet());
}
public void xDestroy(){ }
};
/*
** fts5_tokenize(<fts>, <text>)
*/
fts5_extension_function fts5_tokenize = new fts5_extension_function(){
@Override public void call(
Fts5ExtensionApi ext,
Fts5Context fCx,
sqlite3_context pCx,
sqlite3_value argv[]
){
if( argv.length!=1 ){
throw new RuntimeException("fts5_tokenize: wrong number of args");
}
byte[] utf8 = sqlite3_value_text(argv[0]);
int rc = SQLITE_OK;
class MyCallback implements XTokenizeCallback {
private List<String> myList = new ArrayList<String>();
public String getval() {
return String.join("+", myList);
}
@Override
public int call(int tFlags, byte[] txt, int iStart, int iEnd){
try {
String str = new String(txt, "UTF-8");
myList.add(str);
} catch (Exception e) {
}
return SQLITE_OK;
}
};
MyCallback xCall = new MyCallback();
ext.xTokenize(fCx, utf8, xCall);
sqlite3_result_text16(pCx, xCall.getval());
if( rc!=SQLITE_OK ){
throw new RuntimeException("fts5_tokenize: rc=" + rc);
}
}
public void xDestroy(){ }
};
fts5_api api = fts5_api.getInstanceForDb(db);
api.xCreateFunction("fts5_rowid", fts5_rowid);
api.xCreateFunction("fts5_columncount", fts5_columncount);
api.xCreateFunction("fts5_columnsize", fts5_columnsize);
api.xCreateFunction("fts5_columntext", fts5_columntext);
api.xCreateFunction("fts5_columntotalsize", fts5_columntsize);
api.xCreateFunction("fts5_aux1", new fts5_aux());
api.xCreateFunction("fts5_aux2", new fts5_aux());
api.xCreateFunction("fts5_inst", fts5_inst);
api.xCreateFunction("fts5_pinst", fts5_pinst);
api.xCreateFunction("fts5_pcolinst", fts5_pcolinst);
api.xCreateFunction("fts5_rowcount", fts5_rowcount);
api.xCreateFunction("fts5_phrasesize", fts5_phrasesize);
api.xCreateFunction("fts5_phrasehits", fts5_phrasehits);
api.xCreateFunction("fts5_tokenize", fts5_tokenize);
}
/*
** Test of various Fts5ExtensionApi methods
*/
private static void test2(){
/* Open db and populate an fts5 table */
sqlite3 db = createNewDb();
do_execsql_test(db,
"CREATE VIRTUAL TABLE ft USING fts5(a, b);" +
"INSERT INTO ft(rowid, a, b) VALUES(-9223372036854775808, 'x', 'x');" +
"INSERT INTO ft(rowid, a, b) VALUES(0, 'x', 'x');" +
"INSERT INTO ft(rowid, a, b) VALUES(1, 'x y z', 'x y z');" +
"INSERT INTO ft(rowid, a, b) VALUES(2, 'x y z', 'x z');" +
"INSERT INTO ft(rowid, a, b) VALUES(3, 'x y z', 'x y z');" +
"INSERT INTO ft(rowid, a, b) VALUES(9223372036854775807, 'x', 'x');"
);
create_test_functions(db);
/* Test that fts5_rowid() seems to work */
do_execsql_test(db,
"SELECT rowid==fts5_rowid(ft) FROM ft('x')",
"[1, 1, 1, 1, 1, 1]"
);
/* Test fts5_columncount() */
do_execsql_test(db,
"SELECT fts5_columncount(ft) FROM ft('x')",
"[2, 2, 2, 2, 2, 2]"
);
/* Test fts5_columnsize() */
do_execsql_test(db,
"SELECT fts5_columnsize(ft, 0) FROM ft('x') ORDER BY rowid",
"[1, 1, 3, 3, 3, 1]"
);
do_execsql_test(db,
"SELECT fts5_columnsize(ft, 1) FROM ft('x') ORDER BY rowid",
"[1, 1, 3, 2, 3, 1]"
);
do_execsql_test(db,
"SELECT fts5_columnsize(ft, -1) FROM ft('x') ORDER BY rowid",
"[2, 2, 6, 5, 6, 2]"
);
/* Test that xColumnSize() returns SQLITE_RANGE if the column number
** is out-of range */
try {
do_execsql_test(db,
"SELECT fts5_columnsize(ft, 2) FROM ft('x') ORDER BY rowid"
);
} catch( RuntimeException e ){
affirm( e.getMessage().matches(".*column index out of range") );
}
/* Test fts5_columntext() */
do_execsql_test(db,
"SELECT fts5_columntext(ft, 0) FROM ft('x') ORDER BY rowid",
"[x, x, x y z, x y z, x y z, x]"
);
do_execsql_test(db,
"SELECT fts5_columntext(ft, 1) FROM ft('x') ORDER BY rowid",
"[x, x, x y z, x z, x y z, x]"
);
do_execsql_test(db,
"SELECT fts5_columntext(ft, 2) FROM ft('x') ORDER BY rowid",
"[null, null, null, null, null, null]"
);
/* Test fts5_columntotalsize() */
do_execsql_test(db,
"SELECT fts5_columntotalsize(ft, 0) FROM ft('x') ORDER BY rowid",
"[12, 12, 12, 12, 12, 12]"
);
do_execsql_test(db,
"SELECT fts5_columntotalsize(ft, 1) FROM ft('x') ORDER BY rowid",
"[11, 11, 11, 11, 11, 11]"
);
do_execsql_test(db,
"SELECT fts5_columntotalsize(ft, -1) FROM ft('x') ORDER BY rowid",
"[23, 23, 23, 23, 23, 23]"
);
/* Test that xColumnTotalSize() returns SQLITE_RANGE if the column
** number is out-of range */
try {
do_execsql_test(db,
"SELECT fts5_columntotalsize(ft, 2) FROM ft('x') ORDER BY rowid"
);
} catch( RuntimeException e ){
affirm( e.getMessage().matches(".*column index out of range") );
}
do_execsql_test(db,
"SELECT rowid, fts5_rowcount(ft) FROM ft('z')",
"[1, 6, 2, 6, 3, 6]"
);
sqlite3_close_v2(db);
}
/*
** Test of various Fts5ExtensionApi methods
*/
private static void test3(){
/* Open db and populate an fts5 table */
sqlite3 db = createNewDb();
do_execsql_test(db,
"CREATE VIRTUAL TABLE ft USING fts5(a, b);" +
"INSERT INTO ft(a, b) VALUES('the one', 1);" +
"INSERT INTO ft(a, b) VALUES('the two', 2);" +
"INSERT INTO ft(a, b) VALUES('the three', 3);" +
"INSERT INTO ft(a, b) VALUES('the four', '');"
);
create_test_functions(db);
/* Test fts5_aux1() + fts5_aux2() - users of xGetAuxdata and xSetAuxdata */
do_execsql_test(db,
"SELECT fts5_aux1(ft, a) FROM ft('the')",
"[null, the one, the two, the three]"
);
do_execsql_test(db,
"SELECT fts5_aux2(ft, b) FROM ft('the')",
"[null, 1, 2, 3]"
);
do_execsql_test(db,
"SELECT fts5_aux1(ft, a), fts5_aux2(ft, b) FROM ft('the')",
"[null, null, the one, 1, the two, 2, the three, 3]"
);
do_execsql_test(db,
"SELECT fts5_aux1(ft, b), fts5_aux1(ft) FROM ft('the')",
"[null, 1, 1, 2, 2, 3, 3, null]"
);
}
/*
** Test of various Fts5ExtensionApi methods
*/
private static void test4(){
/* Open db and populate an fts5 table */
sqlite3 db = createNewDb();
create_test_functions(db);
do_execsql_test(db,
"CREATE VIRTUAL TABLE ft USING fts5(a, b);" +
"INSERT INTO ft(a, b) VALUES('one two three', 'two three four');" +
"INSERT INTO ft(a, b) VALUES('two three four', 'three four five');" +
"INSERT INTO ft(a, b) VALUES('three four five', 'four five six');"
);
do_execsql_test(db,
"SELECT fts5_inst(ft) FROM ft('two')",
"[{0 0 1} {0 1 0}, {0 0 0}]"
);
do_execsql_test(db,
"SELECT fts5_inst(ft) FROM ft('four')",
"[{0 1 2}, {0 0 2} {0 1 1}, {0 0 1} {0 1 0}]"
);
do_execsql_test(db,
"SELECT fts5_inst(ft) FROM ft('a OR b OR four')",
"[{2 1 2}, {2 0 2} {2 1 1}, {2 0 1} {2 1 0}]"
);
do_execsql_test(db,
"SELECT fts5_inst(ft) FROM ft('two four')",
"[{0 0 1} {0 1 0} {1 1 2}, {0 0 0} {1 0 2} {1 1 1}]"
);
do_execsql_test(db,
"SELECT fts5_pinst(ft) FROM ft('two')",
"[{0 0 1} {0 1 0}, {0 0 0}]"
);
do_execsql_test(db,
"SELECT fts5_pinst(ft) FROM ft('four')",
"[{0 1 2}, {0 0 2} {0 1 1}, {0 0 1} {0 1 0}]"
);
do_execsql_test(db,
"SELECT fts5_pinst(ft) FROM ft('a OR b OR four')",
"[{2 1 2}, {2 0 2} {2 1 1}, {2 0 1} {2 1 0}]"
);
do_execsql_test(db,
"SELECT fts5_pinst(ft) FROM ft('two four')",
"[{0 0 1} {0 1 0} {1 1 2}, {0 0 0} {1 0 2} {1 1 1}]"
);
do_execsql_test(db,
"SELECT fts5_pcolinst(ft) FROM ft('two')",
"[{0 0} {0 1}, {0 0}]"
);
do_execsql_test(db,
"SELECT fts5_pcolinst(ft) FROM ft('four')",
"[{0 1}, {0 0} {0 1}, {0 0} {0 1}]"
);
do_execsql_test(db,
"SELECT fts5_pcolinst(ft) FROM ft('a OR b OR four')",
"[{2 1}, {2 0} {2 1}, {2 0} {2 1}]"
);
do_execsql_test(db,
"SELECT fts5_pcolinst(ft) FROM ft('two four')",
"[{0 0} {0 1} {1 1}, {0 0} {1 0} {1 1}]"
);
do_execsql_test(db,
"SELECT fts5_phrasesize(ft, 0) FROM ft('four five six') LIMIT 1;",
"[1]"
);
do_execsql_test(db,
"SELECT fts5_phrasesize(ft, 0) FROM ft('four + five + six') LIMIT 1;",
"[3]"
);
sqlite3_close_v2(db);
}
private static void test5(){
/* Open db and populate an fts5 table */
sqlite3 db = createNewDb();
create_test_functions(db);
do_execsql_test(db,
"CREATE VIRTUAL TABLE ft USING fts5(x, b);" +
"INSERT INTO ft(x) VALUES('one two three four five six seven eight');" +
"INSERT INTO ft(x) VALUES('one two one four one six one eight');" +
"INSERT INTO ft(x) VALUES('one two three four five six seven eight');"
);
do_execsql_test(db,
"SELECT fts5_phrasehits(ft, 0) FROM ft('one') LIMIT 1",
"[6]"
);
sqlite3_close_v2(db);
}
private static void test6(){
sqlite3 db = createNewDb();
create_test_functions(db);
do_execsql_test(db,
"CREATE VIRTUAL TABLE ft USING fts5(x, b);" +
"INSERT INTO ft(x) VALUES('one two three four five six seven eight');"
);
do_execsql_test(db,
"SELECT fts5_tokenize(ft, 'abc def ghi') FROM ft('one')",
"[abc+def+ghi]"
);
do_execsql_test(db,
"SELECT fts5_tokenize(ft, 'it''s BEEN a...') FROM ft('one')",
"[it+s+been+a]"
);
sqlite3_close_v2(db);
}
private static synchronized void runTests(){
test1();
test2();
test3();
test4();
test5();
test6();
}
public TesterFts5(){
runTests();
}
public TesterFts5(){ this(false); }
}
@@ -12,18 +12,19 @@
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
import org.sqlite.jni.annotation.Nullable;
/**
Callback proxy for use with sqlite3_trace_v2().
Callback for use with {@link SQLite3Jni#sqlite3_trace_v2}.
*/
public interface TraceV2Callback extends SQLite3CallbackProxy {
public interface TraceV2Callback extends CallbackProxy {
/**
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.
These callbacks may throw, in which case their exceptions are
<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
@@ -45,5 +46,5 @@ public interface TraceV2Callback extends SQLite3CallbackProxy {
<p>- SQLITE_TRACE_CLOSE: pNative is a sqlite3. pX is null.
*/
int call(int traceFlag, Object pNative, Object pX);
int call(int traceFlag, Object pNative, @Nullable Object pX);
}
@@ -14,12 +14,12 @@
package org.sqlite.jni;
/**
Callback for use with sqlite3_update_hook().
Callback for use with {@link SQLite3Jni#sqlite3_update_hook}.
*/
public interface UpdateHookCallback extends SQLite3CallbackProxy {
public interface UpdateHookCallback extends CallbackProxy {
/**
Must function as described for the C-level sqlite3_update_hook()
callback. Must not throw.
callback.
*/
void call(int opId, String dbName, String tableName, long rowId);
}
@@ -15,11 +15,11 @@ package org.sqlite.jni;
/**
An SQLFunction subclass for creating window functions. Note that
Window<T> inherits from Aggregate<T> and each instance is
required to implement the inherited abstract methods from that
class. See Aggregate<T> for information on managing the UDF's
invocation-specific state.
A SQLFunction implementation for window functions. Note that
WindowFunction inherits from {@link AggregateFunction} and each
instance is required to implement the inherited abstract methods
from that class. See {@link AggregateFunction} for information on
managing the UDF's invocation-specific state.
*/
public abstract class WindowFunction<T> extends AggregateFunction<T> {
@@ -24,6 +24,14 @@ public interface XDestroyCallback {
Must perform any cleanup required by this object. Must not
throw. Must not call back into the sqlite3 API, else it might
invoke a deadlock.
WARNING: as a rule, it is never safe to register individual
instances with this interface multiple times in the
library. e.g., do not register the same CollationCallback with
multiple arities or names using sqlite3_create_collation(). If
this rule is violated, the library will eventually try to free
each individual reference, leading to memory corruption or a
crash via duplicate free().
*/
public void xDestroy();
}
@@ -0,0 +1,48 @@
/*
** 2023-09-27
**
** 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 houses the Canonical annotaion for the sqlite3 C API.
*/
package org.sqlite.jni.annotation;
/**
This annotation is for marking functions as "canonical", meaning
that they map directly to a function in the core sqlite3 C API. The
intent is to distinguish them from functions added specifically to
the Java API.
<p>Canonical functions, unless specifically documented, have the
same semantics as their counterparts in <a
href="https://sqlite.org/c3ref/intro.html">the C API
documentation</a>, despite their signatures perhaps differing
slightly. Canonical forms may be native or implemented in Java.
Sometimes multiple overloads are labeled as Canonical because one
or more of them are just type- or encoding-related conversion
wrappers but provide identical semantics (e.g. from a String to a
byte[]). The Java API adds a number of convenience overloads to
simplify use, as well as a few Java-specific functions, and those
are never flagged as @Canonical.
<p>In some cases, the canonical version of a function is private
and exposed to Java via public overloads.
<p>The comment property can be used to add a comment.
*/
@java.lang.annotation.Documented
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD)
public @interface Canonical{
/**
Brief comments about the binding, e.g. noting any major
semantic differences.
*/
String comment() default "";
}
@@ -0,0 +1,59 @@
/*
** 2023-09-27
**
** 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 houses the NotNull annotaion for the sqlite3 C API.
*/
package org.sqlite.jni.annotation;
/**
This annotation is for flagging parameters which may not legally be
null or point to closed/finalized C-side resources.
<p>In the case of Java types which map directly to C struct types
(e.g. {@link org.sqlite.jni.sqlite3}, {@link
org.sqlite.jni.sqlite3_stmt}, and {@link
org.sqlite.jni.sqlite3_context}), a closed/finalized resource is
also considered to be null for purposes this annotation because the
C-side effect of passing such a handle is the same as if null is
passed.</p>
<p>When used in the context of Java interfaces which are called
from the C APIs, this annotation communicates that the C API will
never pass a null value to the callback for that parameter.</p>
<p>Passing a null, for this annotation's definition of null, for
any parameter marked with this annoation specifically invokes
undefined behavior.</p>
<p>Passing 0 (i.e. C NULL) or a negative value for any long-type
parameter marked with this annoation specifically invokes undefined
behavior. Such values are treated as C pointers in the JNI
layer.</p>
<p>Note that the C-style API does not throw any exceptions on its
own because it has a no-throw policy in order to retain its C-style
semantics, but it may trigger NullPointerExceptions (or similar) if
passed a null for a parameter flagged with this annotation.</p>
<p>This annotation is informational only. No policy is in place to
programmatically ensure that NotNull is conformed to in client
code.</p>
<p>This annotation is solely for the use by the classes in the
org.sqlite package and subpackages, but is made public so that
javadoc will link to it from the annotated functions. It is not
part of the public API and client-level code must not rely on
it.</p>
*/
@java.lang.annotation.Documented
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER)
public @interface NotNull{}
@@ -0,0 +1,32 @@
/*
** 2023-09-27
**
** 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 houses the Nullable annotaion for the sqlite3 C API.
*/
package org.sqlite.jni.annotation;
/**
This annotation is for flagging parameters which may legally be
null, noting that they may behave differently if passed null but
are prepared to expect null as a value. When used in the context of
callback methods which are called into from the C APIs, this
annotation communicates that the C API may pass a null value to the
callback.
<p>This annotation is solely for the use by the classes in this
package but is made public so that javadoc will link to it from the
annotated functions. It is not part of the public API and
client-level code must not rely on it.
*/
@java.lang.annotation.Documented
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER)
public @interface Nullable{}
@@ -0,0 +1,17 @@
/*
** 2023-09-27
**
** 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 package houses annotations specific to the JNI bindings of the
SQLite3 C API.
*/
package org.sqlite.jni.annotation;
@@ -11,26 +11,18 @@
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
package org.sqlite.jni.fts5;
/**
INCOMPLETE AND COMPLETELY UNTESTED.
A wrapper for communicating C-level (fts5_api*) instances with
Java. These wrappers do not own their associated pointer, they
simply provide a type-safe way to communicate it between Java and C
via JNI.
A utility object for holding FTS5-specific types and constants
which are used by multiple FTS5 classes.
*/
public final class Fts5 {
/* Not used */
private Fts5(){}
/**
Callback type for use with xTokenize() variants
*/
public static interface xTokenize_callback {
int call(int tFlags, byte[] txt, int iStart, int iEnd);
}
public static final int FTS5_TOKENIZE_QUERY = 0x0001;
public static final int FTS5_TOKENIZE_PREFIX = 0x0002;
@@ -11,7 +11,8 @@
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
package org.sqlite.jni.fts5;
import org.sqlite.jni.*;
/**
A wrapper for communicating C-level (Fts5Context*) instances with
@@ -11,14 +11,12 @@
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
package org.sqlite.jni.fts5;
import java.nio.charset.StandardCharsets;
import org.sqlite.jni.*;
import org.sqlite.jni.annotation.*;
/**
ALMOST COMPLETELY UNTESTED.
FAR FROM COMPLETE and the feasibility of binding this to Java
is still undetermined. This might be removed.
*/
public final class Fts5ExtensionApi extends NativePointerHolder<Fts5ExtensionApi> {
//! Only called from JNI
@@ -26,8 +24,8 @@ public final class Fts5ExtensionApi extends NativePointerHolder<Fts5ExtensionApi
private final int iVersion = 2;
/* Callback type for used by xQueryPhrase(). */
public static interface xQueryPhraseCallback {
int xCallback(Fts5ExtensionApi fapi, Fts5Context cx);
public static interface XQueryPhraseCallback {
int call(Fts5ExtensionApi fapi, Fts5Context cx);
}
/**
@@ -35,40 +33,67 @@ public final class Fts5ExtensionApi extends NativePointerHolder<Fts5ExtensionApi
*/
public static native Fts5ExtensionApi getInstance();
@Canonical
public native int xColumnCount(@NotNull Fts5Context fcx);
@Canonical
public native int xColumnSize(@NotNull Fts5Context cx, int iCol,
@NotNull OutputPointer.Int32 pnToken);
@Canonical
public native int xColumnText(@NotNull Fts5Context cx, int iCol,
@NotNull OutputPointer.String txt);
@Canonical
public native int xColumnTotalSize(@NotNull Fts5Context fcx, int iCol,
@NotNull OutputPointer.Int64 pnToken);
@Canonical
public native Object xGetAuxdata(@NotNull Fts5Context cx, boolean clearIt);
@Canonical
public native int xInst(@NotNull Fts5Context cx, int iIdx,
@NotNull OutputPointer.Int32 piPhrase,
@NotNull OutputPointer.Int32 piCol,
@NotNull OutputPointer.Int32 piOff);
@Canonical
public native int xInstCount(@NotNull Fts5Context fcx,
@NotNull OutputPointer.Int32 pnInst);
@Canonical
public native int xPhraseCount(@NotNull Fts5Context fcx);
@Canonical
public native int xPhraseFirst(@NotNull Fts5Context cx, int iPhrase,
@NotNull Fts5PhraseIter iter,
@NotNull OutputPointer.Int32 iCol,
@NotNull OutputPointer.Int32 iOff);
@Canonical
public native int xPhraseFirstColumn(@NotNull Fts5Context cx, int iPhrase,
@NotNull Fts5PhraseIter iter,
@NotNull OutputPointer.Int32 iCol);
@Canonical
public native void xPhraseNext(@NotNull Fts5Context cx,
@NotNull Fts5PhraseIter iter,
@NotNull OutputPointer.Int32 iCol,
@NotNull OutputPointer.Int32 iOff);
@Canonical
public native void xPhraseNextColumn(@NotNull Fts5Context cx,
@NotNull Fts5PhraseIter iter,
@NotNull OutputPointer.Int32 iCol);
@Canonical
public native int xPhraseSize(@NotNull Fts5Context fcx, int iPhrase);
@Canonical
public native int xQueryPhrase(@NotNull Fts5Context cx, int iPhrase,
@NotNull xQueryPhraseCallback callback);
@NotNull XQueryPhraseCallback callback);
@Canonical
public native int xRowCount(@NotNull Fts5Context fcx,
@NotNull OutputPointer.Int64 nRow);
@Canonical
public native long xRowid(@NotNull Fts5Context cx);
/* Note that the JNI binding lacks the C version's xDelete()
callback argument. Instead, if pAux has an xDestroy() method, it
@@ -76,10 +101,15 @@ public final class Fts5ExtensionApi extends NativePointerHolder<Fts5ExtensionApi
allocation of storage for the auxdata fails). Any reference to
pAux held by the JNI layer will be relinquished regardless of
whether pAux has an xDestroy() method. */
public native int xSetAuxdata(@NotNull Fts5Context cx, @Nullable Object pAux);
public native int xTokenize(@NotNull Fts5Context cx, @NotNull byte[] pText,
@NotNull Fts5.xTokenize_callback callback);
@Canonical
public native int xSetAuxdata(@NotNull Fts5Context cx, @Nullable Object pAux);
@Canonical
public native int xTokenize(@NotNull Fts5Context cx, @NotNull byte[] pText,
@NotNull XTokenizeCallback callback);
@Canonical
public native Object xUserData(Fts5Context cx);
//^^^ returns the pointer passed as the 3rd arg to the C-level
// fts5_api::xCreateFunction().
@@ -11,7 +11,8 @@
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
package org.sqlite.jni.fts5;
import org.sqlite.jni.NativePointerHolder;
/**
A wrapper for C-level Fts5PhraseIter. They are only modified and
@@ -11,7 +11,8 @@
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
package org.sqlite.jni.fts5;
import org.sqlite.jni.NativePointerHolder;
/**
INCOMPLETE AND COMPLETELY UNTESTED.
@@ -0,0 +1,22 @@
/*
** 2023-08-04
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni.fts5;
/**
Callback type for use with xTokenize() variants.
*/
public interface XTokenizeCallback {
int call(int tFlags, byte[] txt, int iStart, int iEnd);
}
@@ -11,11 +11,11 @@
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
package org.sqlite.jni.fts5;
import org.sqlite.jni.annotation.*;
import org.sqlite.jni.*;
/**
INCOMPLETE AND COMPLETELY UNTESTED.
A wrapper for communicating C-level (fts5_api*) instances with
Java. These wrappers do not own their associated pointer, they
simply provide a type-safe way to communicate it between Java and C
@@ -24,7 +24,8 @@ package org.sqlite.jni;
public final class fts5_api extends NativePointerHolder<fts5_api> {
/* Only invoked from JNI */
private fts5_api(){}
public final int iVersion = 2;
public static final int iVersion = 2;
/**
Returns the fts5_api instance associated with the given db, or
@@ -32,6 +33,31 @@ public final class fts5_api extends NativePointerHolder<fts5_api> {
*/
public static synchronized native fts5_api getInstanceForDb(@NotNull sqlite3 db);
@Canonical
public synchronized native int xCreateFunction(@NotNull String name,
@Nullable Object userData,
@NotNull fts5_extension_function xFunction);
/**
Convenience overload which passes null as the 2nd argument to the
3-parameter form.
*/
public int xCreateFunction(@NotNull String name,
@NotNull fts5_extension_function xFunction){
return xCreateFunction(name, null, xFunction);
}
// /* Create a new auxiliary function */
// int (*xCreateFunction)(
// fts5_api *pApi,
// const char *zName,
// void *pContext,
// fts5_extension_function xFunction,
// void (*xDestroy)(void*)
// );
// Still potentially todo:
// int (*xCreateTokenizer)(
// fts5_api *pApi,
// const char *zName,
@@ -48,22 +74,4 @@ public final class fts5_api extends NativePointerHolder<fts5_api> {
// fts5_tokenizer *pTokenizer
// );
// /* Create a new auxiliary function */
// int (*xCreateFunction)(
// fts5_api *pApi,
// const char *zName,
// void *pContext,
// fts5_extension_function xFunction,
// void (*xDestroy)(void*)
// );
public synchronized native int xCreateFunction(@NotNull String name,
@Nullable Object userData,
@NotNull fts5_extension_function xFunction);
public int xCreateFunction(@NotNull String name,
@NotNull fts5_extension_function xFunction){
return xCreateFunction(name, null, xFunction);
}
}
@@ -11,13 +11,14 @@
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
package org.sqlite.jni.fts5;
import org.sqlite.jni.sqlite3_context;
import org.sqlite.jni.sqlite3_value;
/**
JNI-level wrapper for C's fts5_extension_function type.
*/
public abstract class fts5_extension_function {
public interface fts5_extension_function {
// typedef void (*fts5_extension_function)(
// const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
// Fts5Context *pFts, /* First arg to pass to pApi functions */
@@ -30,8 +31,17 @@ public abstract class fts5_extension_function {
The callback implementation, corresponding to the xFunction
argument of C's fts5_api::xCreateFunction().
*/
public abstract void xFunction(Fts5ExtensionApi ext, Fts5Context fCx,
sqlite3_context pCx, sqlite3_value argv[]);
//! Optionally override
public void xDestroy(){}
void call(Fts5ExtensionApi ext, Fts5Context fCx,
sqlite3_context pCx, sqlite3_value argv[]);
/**
Is called when this function is destroyed by sqlite3. Typically
this function will be empty.
*/
void xDestroy();
public static abstract class Abstract implements fts5_extension_function {
@Override public abstract void call(Fts5ExtensionApi ext, Fts5Context fCx,
sqlite3_context pCx, sqlite3_value argv[]);
@Override public void xDestroy(){}
}
}
@@ -11,11 +11,11 @@
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
package org.sqlite.jni.fts5;
import org.sqlite.jni.NativePointerHolder;
import org.sqlite.jni.annotation.NotNull;
/**
INCOMPLETE AND COMPLETELY UNTESTED.
A wrapper for communicating C-level (fts5_tokenizer*) instances with
Java. These wrappers do not own their associated pointer, they
simply provide a type-safe way to communicate it between Java and C
@@ -30,7 +30,7 @@ public final class fts5_tokenizer extends NativePointerHolder<fts5_tokenizer> {
public native int xTokenize(@NotNull Fts5Tokenizer t, int tokFlags,
@NotNull byte pText[],
@NotNull Fts5.xTokenize_callback callback);
@NotNull XTokenizeCallback callback);
// int (*xTokenize)(Fts5Tokenizer*,
+51 -5
View File
@@ -1,14 +1,59 @@
/**
This package houses a JNI binding to the SQLite3 C API.
<p>The docs are in progress.
<p>The primary interfaces are in {@link
org.sqlite.jni.SQLite3Jni}.</p>
<p>The primary interfaces are in {@link org.sqlite.jni.SQLite3Jni}.
<h1>API Goals and Requirements</h1>
<ul>
<li>A 1-to-1(-ish) mapping of the C API to Java via JNI, insofar
as cross-language semantics allow for. A closely-related goal is
that <a href='https://sqlite.org/c3ref/intro.html'>the C
documentation</a> should be usable as-is, insofar as possible,
for most of the JNI binding. As a rule, undocumented symbols in
the Java interface behave as documented for their C API
counterpart. Only semantic differences and Java-specific features
are documented here.</li>
<li>Support Java as far back as version 8 (2014).</li>
<li>Environment-independent. Should work everywhere both Java and
SQLite3 do.</li>
<li>No 3rd-party dependencies beyond the JDK. That includes no
build-level dependencies for specific IDEs and toolchains. We
welcome the addition of build files for arbitrary environments
insofar as they neither interfere with each other nor become a
maintenance burden for the sqlite developers.</li>
</ul>
<h2>Non-Goals</h2>
<ul>
<li>Creation of high-level OO wrapper APIs. Clients are free to
create them off of the C-style API.</li>
<li>Support for mixed-mode operation, where client code accesses
SQLite both via the Java-side API and the C API via their own
native code. In such cases, proxy functionalities (primarily
callback handler wrappers of all sorts) may fail because the
C-side use of the SQLite APIs will bypass those proxies.</li>
</ul>
<h1>State of this API</h1>
<p>As of version 3.43, this software is in "tech preview" form. We
tentatively plan to stamp it as stable with the 3.44 release.</p>
<h1>Threading Considerations</h1>
<p>This API is, if built with SQLITE_THREADSAFE set to 1 or 2,
thread-safe, insofar as the C API guarantees, with some addenda:
thread-safe, insofar as the C API guarantees, with some addenda:</p>
<ul>
@@ -34,10 +79,11 @@
<p>Any number of threads may, of course, create and use any number
of database handles they wish. Care only needs to be taken when
those handles or their associated resources cross threads, or...
those handles or their associated resources cross threads, or...</p>
<p>When built with SQLITE_THREADSAFE=0 then no threading guarantees
are provided and multi-threaded use of the library will provoke
undefined behavior.
undefined behavior.</p>
*/
package org.sqlite.jni;
+14 -3
View File
@@ -19,19 +19,30 @@ package org.sqlite.jni;
simply provide a type-safe way to communicate it between Java
and C via JNI.
*/
public final class sqlite3 extends NativePointerHolder<sqlite3> {
public final class sqlite3 extends NativePointerHolder<sqlite3>
implements AutoCloseable {
// Only invoked from JNI
private sqlite3(){}
public String toString(){
long ptr = getNativePointer();
final long ptr = getNativePointer();
if( 0==ptr ){
return sqlite3.class.getSimpleName()+"@null";
}
String fn = SQLite3Jni.sqlite3_db_filename(this, "main");
final String fn = SQLite3Jni.sqlite3_db_filename(this, "main");
return sqlite3.class.getSimpleName()
+"@"+String.format("0x%08x",ptr)
+"["+((null == fn) ? "<unnamed>" : fn)+"]"
;
}
@Override protected void finalize(){
//System.out.println(this+".finalize()");
SQLite3Jni.sqlite3_close_v2(this.clearNativePointer());
}
@Override public void close(){
SQLite3Jni.sqlite3_close_v2(this.clearNativePointer());
}
}
@@ -0,0 +1,31 @@
/*
** 2023-09-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.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
/**
A wrapper for passing C-level (sqlite3_backup*) instances around in
Java. These wrappers do not own their associated pointer, they
simply provide a type-safe way to communicate it between Java and C
via JNI.
*/
public final class sqlite3_backup extends NativePointerHolder<sqlite3_backup>
implements AutoCloseable {
// Only invoked from JNI.
private sqlite3_backup(){}
@Override public void close(){
SQLite3Jni.sqlite3_backup_finish(this);
}
}
@@ -0,0 +1,31 @@
/*
** 2023-09-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.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
/**
A wrapper for passing C-level (sqlite3_blob*) instances around in
Java. These wrappers do not own their associated pointer, they
simply provide a type-safe way to communicate it between Java and C
via JNI.
*/
public final class sqlite3_blob extends NativePointerHolder<sqlite3_blob>
implements AutoCloseable {
// Only invoked from JNI.
private sqlite3_blob(){}
@Override public void close(){
SQLite3Jni.sqlite3_blob_close(this.clearNativePointer());
}
}
+11 -1
View File
@@ -19,7 +19,17 @@ package org.sqlite.jni;
simply provide a type-safe way to communicate it between Java and C
via JNI.
*/
public final class sqlite3_stmt extends NativePointerHolder<sqlite3_stmt> {
public final class sqlite3_stmt extends NativePointerHolder<sqlite3_stmt>
implements AutoCloseable {
// Only invoked from JNI.
private sqlite3_stmt(){}
//For as-yet-unknown reasons, this triggers a JVM crash.
//@Override protected void finalize(){
// SQLite3Jni.sqlite3_finalize(this.clearNativePointer());
//}
@Override public void close(){
SQLite3Jni.sqlite3_finalize(this.clearNativePointer());
}
}
+2 -1
View File
@@ -6,7 +6,8 @@
** xMODULE_NAME: module-name
** xREQUIRED_PROPERTIES: small fast reliable
** xREQUIRED_PROPERTIES: RECURSIVE_TRIGGERS
** xREQUIRED_PROPERTIES: TEMPSTORE_MEM TEMPSTORE_FILE
** xREQUIRED_PROPERTIES: TEMPSTORE_FILE TEMPSTORE_MEM
** xREQUIRED_PROPERTIES: AUTOVACUUM INCRVACUUM
**
*/
--print starting up 😃
+12
View File
@@ -0,0 +1,12 @@
/*
** SCRIPT_MODULE_NAME: fts5-sanity-checks
** xREQUIRED_PROPERTIES: FTS5
**
*/
--testcase 1.0
CREATE VIRTUAL TABLE email USING fts5(sender, title, body);
insert into email values('fred','Help!','Dear Sir...');
insert into email values('barney','Assistance','Dear Madam...');
select * from email where email match 'assistance';
--result barney Assistance {Dear Madam...}
+5 -3
View File
@@ -1256,20 +1256,23 @@ static int geopolyInit(
/* Allocate the sqlite3_vtab structure */
nDb = strlen(argv[1]);
nName = strlen(argv[2]);
pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName+2);
pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName*2+8);
if( !pRtree ){
return SQLITE_NOMEM;
}
memset(pRtree, 0, sizeof(Rtree)+nDb+nName+2);
memset(pRtree, 0, sizeof(Rtree)+nDb+nName*2+8);
pRtree->nBusy = 1;
pRtree->base.pModule = &rtreeModule;
pRtree->zDb = (char *)&pRtree[1];
pRtree->zName = &pRtree->zDb[nDb+1];
pRtree->zNodeName = &pRtree->zName[nName+1];
pRtree->eCoordType = RTREE_COORD_REAL32;
pRtree->nDim = 2;
pRtree->nDim2 = 4;
memcpy(pRtree->zDb, argv[1], nDb);
memcpy(pRtree->zName, argv[2], nName);
memcpy(pRtree->zNodeName, argv[2], nName);
memcpy(&pRtree->zNodeName[nName], "_node", 6);
/* Create/Connect to the underlying relational database schema. If
@@ -1683,7 +1686,6 @@ static int geopolyUpdate(
}
if( rc==SQLITE_OK ){
int rc2;
pRtree->iReinsertHeight = -1;
rc = rtreeInsertCell(pRtree, pLeaf, &cell, 0);
rc2 = nodeRelease(pRtree, pLeaf);
if( rc==SQLITE_OK ){
+93 -238
View File
@@ -166,6 +166,7 @@ struct Rtree {
int iDepth; /* Current depth of the r-tree structure */
char *zDb; /* Name of database containing r-tree table */
char *zName; /* Name of r-tree table */
char *zNodeName; /* Name of the %_node table */
u32 nBusy; /* Current number of users of this structure */
i64 nRowEst; /* Estimated number of rows in this table */
u32 nCursor; /* Number of open cursors */
@@ -178,7 +179,6 @@ struct Rtree {
** headed by the node (leaf nodes have RtreeNode.iNode==0).
*/
RtreeNode *pDeleted;
int iReinsertHeight; /* Height of sub-trees Reinsert() has run on */
/* Blob I/O on xxx_node */
sqlite3_blob *pNodeBlob;
@@ -200,7 +200,7 @@ struct Rtree {
/* Statement for writing to the "aux:" fields, if there are any */
sqlite3_stmt *pWriteAux;
RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */
RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */
};
/* Possible values for Rtree.eCoordType: */
@@ -475,15 +475,20 @@ struct RtreeMatchArg {
** -DSQLITE_RUNTIME_BYTEORDER=1 is set, then byte-order is determined
** at run-time.
*/
#ifndef SQLITE_BYTEORDER
# if defined(i386) || defined(__i386__) || defined(_M_IX86) || \
#ifndef SQLITE_BYTEORDER /* Replicate changes at tag-20230904a */
# if defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_BIG_ENDIAN__
# define SQLITE_BYTEORDER 4321
# elif defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__
# define SQLITE_BYTEORDER 1234
# elif defined(__BIG_ENDIAN__) && __BIG_ENDIAN__==1
# define SQLITE_BYTEORDER 4321
# elif defined(i386) || defined(__i386__) || defined(_M_IX86) || \
defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \
defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \
defined(__ARMEL__) || defined(__AARCH64EL__) || defined(_M_ARM64)
# define SQLITE_BYTEORDER 1234
# elif defined(sparc) || defined(__ppc__) || \
defined(__ARMEB__) || defined(__AARCH64EB__)
# define SQLITE_BYTEORDER 4321
# define SQLITE_BYTEORDER 1234
# elif defined(sparc) || defined(__ARMEB__) || defined(__AARCH64EB__)
# define SQLITE_BYTEORDER 4321
# else
# define SQLITE_BYTEORDER 0
# endif
@@ -732,11 +737,9 @@ static int nodeAcquire(
}
}
if( pRtree->pNodeBlob==0 ){
char *zTab = sqlite3_mprintf("%s_node", pRtree->zName);
if( zTab==0 ) return SQLITE_NOMEM;
rc = sqlite3_blob_open(pRtree->db, pRtree->zDb, zTab, "data", iNode, 0,
rc = sqlite3_blob_open(pRtree->db, pRtree->zDb, pRtree->zNodeName,
"data", iNode, 0,
&pRtree->pNodeBlob);
sqlite3_free(zTab);
}
if( rc ){
nodeBlobReset(pRtree);
@@ -2077,8 +2080,12 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
pIdxInfo->idxNum = 2;
pIdxInfo->needToFreeIdxStr = 1;
if( iIdx>0 && 0==(pIdxInfo->idxStr = sqlite3_mprintf("%s", zIdxStr)) ){
return SQLITE_NOMEM;
if( iIdx>0 ){
pIdxInfo->idxStr = sqlite3_malloc( iIdx+1 );
if( pIdxInfo->idxStr==0 ){
return SQLITE_NOMEM;
}
memcpy(pIdxInfo->idxStr, zIdxStr, iIdx+1);
}
nRow = pRtree->nRowEst >> (iIdx/2);
@@ -2157,31 +2164,22 @@ static void cellUnion(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){
*/
static int cellContains(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){
int ii;
int isInt = (pRtree->eCoordType==RTREE_COORD_INT32);
for(ii=0; ii<pRtree->nDim2; ii+=2){
RtreeCoord *a1 = &p1->aCoord[ii];
RtreeCoord *a2 = &p2->aCoord[ii];
if( (!isInt && (a2[0].f<a1[0].f || a2[1].f>a1[1].f))
|| ( isInt && (a2[0].i<a1[0].i || a2[1].i>a1[1].i))
){
return 0;
if( pRtree->eCoordType==RTREE_COORD_INT32 ){
for(ii=0; ii<pRtree->nDim2; ii+=2){
RtreeCoord *a1 = &p1->aCoord[ii];
RtreeCoord *a2 = &p2->aCoord[ii];
if( a2[0].i<a1[0].i || a2[1].i>a1[1].i ) return 0;
}
}else{
for(ii=0; ii<pRtree->nDim2; ii+=2){
RtreeCoord *a1 = &p1->aCoord[ii];
RtreeCoord *a2 = &p2->aCoord[ii];
if( a2[0].f<a1[0].f || a2[1].f>a1[1].f ) return 0;
}
}
return 1;
}
/*
** Return the amount cell p would grow by if it were unioned with pCell.
*/
static RtreeDValue cellGrowth(Rtree *pRtree, RtreeCell *p, RtreeCell *pCell){
RtreeDValue area;
RtreeCell cell;
memcpy(&cell, p, sizeof(RtreeCell));
area = cellArea(pRtree, &cell);
cellUnion(pRtree, &cell, pCell);
return (cellArea(pRtree, &cell)-area);
}
static RtreeDValue cellOverlap(
Rtree *pRtree,
RtreeCell *p,
@@ -2228,38 +2226,52 @@ static int ChooseLeaf(
for(ii=0; rc==SQLITE_OK && ii<(pRtree->iDepth-iHeight); ii++){
int iCell;
sqlite3_int64 iBest = 0;
int bFound = 0;
RtreeDValue fMinGrowth = RTREE_ZERO;
RtreeDValue fMinArea = RTREE_ZERO;
int nCell = NCELL(pNode);
RtreeCell cell;
RtreeNode *pChild = 0;
RtreeCell *aCell = 0;
/* Select the child node which will be enlarged the least if pCell
** is inserted into it. Resolve ties by choosing the entry with
** the smallest area.
/* First check to see if there is are any cells in pNode that completely
** contains pCell. If two or more cells in pNode completely contain pCell
** then pick the smallest.
*/
for(iCell=0; iCell<nCell; iCell++){
int bBest = 0;
RtreeDValue growth;
RtreeDValue area;
RtreeCell cell;
nodeGetCell(pRtree, pNode, iCell, &cell);
growth = cellGrowth(pRtree, &cell, pCell);
area = cellArea(pRtree, &cell);
if( iCell==0||growth<fMinGrowth||(growth==fMinGrowth && area<fMinArea) ){
bBest = 1;
if( cellContains(pRtree, &cell, pCell) ){
RtreeDValue area = cellArea(pRtree, &cell);
if( bFound==0 || area<fMinArea ){
iBest = cell.iRowid;
fMinArea = area;
bFound = 1;
}
}
if( bBest ){
fMinGrowth = growth;
fMinArea = area;
iBest = cell.iRowid;
}
if( !bFound ){
/* No cells of pNode will completely contain pCell. So pick the
** cell of pNode that grows by the least amount when pCell is added.
** Break ties by selecting the smaller cell.
*/
for(iCell=0; iCell<nCell; iCell++){
RtreeCell cell;
RtreeDValue growth;
RtreeDValue area;
nodeGetCell(pRtree, pNode, iCell, &cell);
area = cellArea(pRtree, &cell);
cellUnion(pRtree, &cell, pCell);
growth = cellArea(pRtree, &cell)-area;
if( iCell==0
|| growth<fMinGrowth
|| (growth==fMinGrowth && area<fMinArea)
){
fMinGrowth = growth;
fMinArea = area;
iBest = cell.iRowid;
}
}
}
sqlite3_free(aCell);
rc = nodeAcquire(pRtree, iBest, pNode, &pChild);
nodeRelease(pRtree, pNode);
pNode = pChild;
@@ -2332,77 +2344,6 @@ static int parentWrite(Rtree *pRtree, sqlite3_int64 iNode, sqlite3_int64 iPar){
static int rtreeInsertCell(Rtree *, RtreeNode *, RtreeCell *, int);
/*
** Arguments aIdx, aDistance and aSpare all point to arrays of size
** nIdx. The aIdx array contains the set of integers from 0 to
** (nIdx-1) in no particular order. This function sorts the values
** in aIdx according to the indexed values in aDistance. For
** example, assuming the inputs:
**
** aIdx = { 0, 1, 2, 3 }
** aDistance = { 5.0, 2.0, 7.0, 6.0 }
**
** this function sets the aIdx array to contain:
**
** aIdx = { 0, 1, 2, 3 }
**
** The aSpare array is used as temporary working space by the
** sorting algorithm.
*/
static void SortByDistance(
int *aIdx,
int nIdx,
RtreeDValue *aDistance,
int *aSpare
){
if( nIdx>1 ){
int iLeft = 0;
int iRight = 0;
int nLeft = nIdx/2;
int nRight = nIdx-nLeft;
int *aLeft = aIdx;
int *aRight = &aIdx[nLeft];
SortByDistance(aLeft, nLeft, aDistance, aSpare);
SortByDistance(aRight, nRight, aDistance, aSpare);
memcpy(aSpare, aLeft, sizeof(int)*nLeft);
aLeft = aSpare;
while( iLeft<nLeft || iRight<nRight ){
if( iLeft==nLeft ){
aIdx[iLeft+iRight] = aRight[iRight];
iRight++;
}else if( iRight==nRight ){
aIdx[iLeft+iRight] = aLeft[iLeft];
iLeft++;
}else{
RtreeDValue fLeft = aDistance[aLeft[iLeft]];
RtreeDValue fRight = aDistance[aRight[iRight]];
if( fLeft<fRight ){
aIdx[iLeft+iRight] = aLeft[iLeft];
iLeft++;
}else{
aIdx[iLeft+iRight] = aRight[iRight];
iRight++;
}
}
}
#if 0
/* Check that the sort worked */
{
int jj;
for(jj=1; jj<nIdx; jj++){
RtreeDValue left = aDistance[aIdx[jj-1]];
RtreeDValue right = aDistance[aIdx[jj]];
assert( left<=right );
}
}
#endif
}
}
/*
** Arguments aIdx, aCell and aSpare all point to arrays of size
@@ -2886,108 +2827,7 @@ static int deleteCell(Rtree *pRtree, RtreeNode *pNode, int iCell, int iHeight){
return rc;
}
static int Reinsert(
Rtree *pRtree,
RtreeNode *pNode,
RtreeCell *pCell,
int iHeight
){
int *aOrder;
int *aSpare;
RtreeCell *aCell;
RtreeDValue *aDistance;
int nCell;
RtreeDValue aCenterCoord[RTREE_MAX_DIMENSIONS];
int iDim;
int ii;
int rc = SQLITE_OK;
int n;
memset(aCenterCoord, 0, sizeof(RtreeDValue)*RTREE_MAX_DIMENSIONS);
nCell = NCELL(pNode)+1;
n = (nCell+1)&(~1);
/* Allocate the buffers used by this operation. The allocation is
** relinquished before this function returns.
*/
aCell = (RtreeCell *)sqlite3_malloc64(n * (
sizeof(RtreeCell) + /* aCell array */
sizeof(int) + /* aOrder array */
sizeof(int) + /* aSpare array */
sizeof(RtreeDValue) /* aDistance array */
));
if( !aCell ){
return SQLITE_NOMEM;
}
aOrder = (int *)&aCell[n];
aSpare = (int *)&aOrder[n];
aDistance = (RtreeDValue *)&aSpare[n];
for(ii=0; ii<nCell; ii++){
if( ii==(nCell-1) ){
memcpy(&aCell[ii], pCell, sizeof(RtreeCell));
}else{
nodeGetCell(pRtree, pNode, ii, &aCell[ii]);
}
aOrder[ii] = ii;
for(iDim=0; iDim<pRtree->nDim; iDim++){
aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2]);
aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2+1]);
}
}
for(iDim=0; iDim<pRtree->nDim; iDim++){
aCenterCoord[iDim] = (aCenterCoord[iDim]/(nCell*(RtreeDValue)2));
}
for(ii=0; ii<nCell; ii++){
aDistance[ii] = RTREE_ZERO;
for(iDim=0; iDim<pRtree->nDim; iDim++){
RtreeDValue coord = (DCOORD(aCell[ii].aCoord[iDim*2+1]) -
DCOORD(aCell[ii].aCoord[iDim*2]));
aDistance[ii] += (coord-aCenterCoord[iDim])*(coord-aCenterCoord[iDim]);
}
}
SortByDistance(aOrder, nCell, aDistance, aSpare);
nodeZero(pRtree, pNode);
for(ii=0; rc==SQLITE_OK && ii<(nCell-(RTREE_MINCELLS(pRtree)+1)); ii++){
RtreeCell *p = &aCell[aOrder[ii]];
nodeInsertCell(pRtree, pNode, p);
if( p->iRowid==pCell->iRowid ){
if( iHeight==0 ){
rc = rowidWrite(pRtree, p->iRowid, pNode->iNode);
}else{
rc = parentWrite(pRtree, p->iRowid, pNode->iNode);
}
}
}
if( rc==SQLITE_OK ){
rc = fixBoundingBox(pRtree, pNode);
}
for(; rc==SQLITE_OK && ii<nCell; ii++){
/* Find a node to store this cell in. pNode->iNode currently contains
** the height of the sub-tree headed by the cell.
*/
RtreeNode *pInsert;
RtreeCell *p = &aCell[aOrder[ii]];
rc = ChooseLeaf(pRtree, p, iHeight, &pInsert);
if( rc==SQLITE_OK ){
int rc2;
rc = rtreeInsertCell(pRtree, pInsert, p, iHeight);
rc2 = nodeRelease(pRtree, pInsert);
if( rc==SQLITE_OK ){
rc = rc2;
}
}
}
sqlite3_free(aCell);
return rc;
}
/*
** Insert cell pCell into node pNode. Node pNode is the head of a
** subtree iHeight high (leaf nodes have iHeight==0).
@@ -3008,12 +2848,7 @@ static int rtreeInsertCell(
}
}
if( nodeInsertCell(pRtree, pNode, pCell) ){
if( iHeight<=pRtree->iReinsertHeight || pNode->iNode==1){
rc = SplitNode(pRtree, pNode, pCell, iHeight);
}else{
pRtree->iReinsertHeight = iHeight;
rc = Reinsert(pRtree, pNode, pCell, iHeight);
}
rc = SplitNode(pRtree, pNode, pCell, iHeight);
}else{
rc = AdjustTree(pRtree, pNode, pCell);
if( ALWAYS(rc==SQLITE_OK) ){
@@ -3356,7 +3191,6 @@ static int rtreeUpdate(
}
if( rc==SQLITE_OK ){
int rc2;
pRtree->iReinsertHeight = -1;
rc = rtreeInsertCell(pRtree, pLeaf, &cell, 0);
rc2 = nodeRelease(pRtree, pLeaf);
if( rc==SQLITE_OK ){
@@ -3497,8 +3331,11 @@ static int rtreeShadowName(const char *zName){
return 0;
}
/* Forward declaration */
static int rtreeIntegrity(sqlite3_vtab*, char**);
static sqlite3_module rtreeModule = {
3, /* iVersion */
4, /* iVersion */
rtreeCreate, /* xCreate - create a table */
rtreeConnect, /* xConnect - connect to an existing table */
rtreeBestIndex, /* xBestIndex - Determine search strategy */
@@ -3521,7 +3358,8 @@ static sqlite3_module rtreeModule = {
rtreeSavepoint, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
rtreeShadowName /* xShadowName */
rtreeShadowName, /* xShadowName */
rtreeIntegrity /* xIntegrity */
};
static int rtreeSqlInit(
@@ -3781,18 +3619,21 @@ static int rtreeInit(
/* Allocate the sqlite3_vtab structure */
nDb = (int)strlen(argv[1]);
nName = (int)strlen(argv[2]);
pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName+2);
pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName*2+8);
if( !pRtree ){
return SQLITE_NOMEM;
}
memset(pRtree, 0, sizeof(Rtree)+nDb+nName+2);
memset(pRtree, 0, sizeof(Rtree)+nDb+nName*2+8);
pRtree->nBusy = 1;
pRtree->base.pModule = &rtreeModule;
pRtree->zDb = (char *)&pRtree[1];
pRtree->zName = &pRtree->zDb[nDb+1];
pRtree->zNodeName = &pRtree->zName[nName+1];
pRtree->eCoordType = (u8)eCoordType;
memcpy(pRtree->zDb, argv[1], nDb);
memcpy(pRtree->zName, argv[2], nName);
memcpy(pRtree->zNodeName, argv[2], nName);
memcpy(&pRtree->zNodeName[nName], "_node", 6);
/* Create/Connect to the underlying relational database schema. If
@@ -4355,6 +4196,20 @@ static int rtreeCheckTable(
return check.rc;
}
/*
** Implementation of the xIntegrity method for Rtree.
*/
static int rtreeIntegrity(sqlite3_vtab *pVtab, char **pzErr){
Rtree *pRtree = (Rtree*)pVtab;
int rc;
rc = rtreeCheckTable(pRtree->db, pRtree->zDb, pRtree->zName, pzErr);
if( rc==SQLITE_OK && *pzErr ){
*pzErr = sqlite3_mprintf("In RTree %s.%s:\n%z",
pRtree->zDb, pRtree->zName, *pzErr);
}
return rc;
}
/*
** Usage:
**
+1 -1
View File
@@ -75,7 +75,7 @@ do_test rtree8-1.2.2 { nested_select 1 } {51}
#
populate_t1 1500
do_rtree_integrity_test rtree8-1.3.0 t1
do_execsql_test rtree8-1.3.1 { SELECT max(nodeno) FROM t1_node } {164}
do_execsql_test rtree8-1.3.1 { SELECT max(nodeno) FROM t1_node } {183}
do_test rtree8-1.3.2 {
set rowids [execsql {SELECT min(rowid) FROM t1_rowid GROUP BY nodeno}]
set stmt_list [list]
+2 -2
View File
@@ -113,7 +113,7 @@ do_execsql_test rtreeA-1.1.1 {
SELECT rtreecheck('main', 't1')
} {{Node 1 missing from database
Wrong number of entries in %_rowid table - expected 0, actual 500
Wrong number of entries in %_parent table - expected 0, actual 23}}
Wrong number of entries in %_parent table - expected 0, actual 25}}
do_execsql_test rtreeA-1.2.0 { DROP TABLE t1_node } {}
do_corruption_tests rtreeA-1.2 -error "database disk image is malformed" {
@@ -191,7 +191,7 @@ do_execsql_test rtreeA-3.3.3.4 {
SELECT rtreecheck('main', 't1')
} {{Rtree depth out of range (65535)
Wrong number of entries in %_rowid table - expected 0, actual 499
Wrong number of entries in %_parent table - expected 0, actual 23}}
Wrong number of entries in %_parent table - expected 0, actual 25}}
#-------------------------------------------------------------------------
# Set the "number of entries" field on some nodes incorrectly.
+2 -2
View File
@@ -192,6 +192,6 @@ proc rtree_treedump {db zTab} {
}
proc do_rtree_integrity_test {tn tbl} {
uplevel [list do_execsql_test $tn "SELECT rtreecheck('$tbl')" ok]
uplevel [list do_execsql_test $tn.1 "SELECT rtreecheck('$tbl')" ok]
uplevel [list do_execsql_test $tn.2 "PRAGMA integrity_check" ok]
}
+25 -5
View File
@@ -78,6 +78,11 @@ do_execsql_test 2.3 {
SELECT rtreecheck('r1')
} {{Dimension 0 of cell 0 on node 1 is corrupt
Dimension 1 of cell 3 on node 1 is corrupt}}
do_execsql_test 2.3b {
PRAGMA integrity_check;
} {{In RTree main.r1:
Dimension 0 of cell 0 on node 1 is corrupt
Dimension 1 of cell 3 on node 1 is corrupt}}
setup_simple_db
do_execsql_test 2.4 {
@@ -85,12 +90,21 @@ do_execsql_test 2.4 {
SELECT rtreecheck('r1')
} {{Mapping (3 -> 1) missing from %_rowid table
Wrong number of entries in %_rowid table - expected 5, actual 4}}
do_execsql_test 2.4b {
PRAGMA integrity_check
} {{In RTree main.r1:
Mapping (3 -> 1) missing from %_rowid table
Wrong number of entries in %_rowid table - expected 5, actual 4}}
setup_simple_db
do_execsql_test 2.5 {
UPDATE r1_rowid SET nodeno=2 WHERE rowid=3;
SELECT rtreecheck('r1')
} {{Found (3 -> 2) in %_rowid table, expected (3 -> 1)}}
do_execsql_test 2.5b {
PRAGMA integrity_check
} {{In RTree main.r1:
Found (3 -> 2) in %_rowid table, expected (3 -> 1)}}
reset_db
do_execsql_test 3.0 {
@@ -104,14 +118,16 @@ do_execsql_test 3.0 {
INSERT INTO r1 VALUES(7, 5, 0x00000080);
INSERT INTO r1 VALUES(8, 5, 0x40490fdb);
INSERT INTO r1 VALUES(9, 0x7f800000, 0x7f900000);
SELECT rtreecheck('r1')
} {ok}
SELECT rtreecheck('r1');
PRAGMA integrity_check;
} {ok ok}
do_execsql_test 3.1 {
CREATE VIRTUAL TABLE r2 USING rtree_i32(id, x1, x2);
INSERT INTO r2 VALUES(2, -1*(1<<31), -1*(1<<31)+5);
SELECT rtreecheck('r2')
} {ok}
SELECT rtreecheck('r2');
PRAGMA integrity_check;
} {ok ok}
sqlite3_db_config db DEFENSIVE 0
do_execsql_test 3.2 {
@@ -125,6 +141,11 @@ do_execsql_test 3.3 {
UPDATE r2_node SET data = X'00001234';
SELECT rtreecheck('r2')!='ok';
} {1}
do_execsql_test 3.4 {
PRAGMA integrity_check;
} {{In RTree main.r2:
Node 1 is too small for cell count of 4660 (4 bytes)
Wrong number of entries in %_rowid table - expected 0, actual 1}}
do_execsql_test 4.0 {
CREATE TABLE notanrtree(i);
@@ -181,4 +202,3 @@ if {[permutation]=="inmemory_journal"} {
}
finish_test
+15 -5
View File
@@ -601,11 +601,21 @@ do_execsql_test 2.5.2 {
SELECT A.id FROM demo_index AS A, demo_index AS B
WHERE A.maxX>=B.minX AND A.minX<=B.maxX
AND A.maxY>=B.minY AND A.minY<=B.maxY
AND B.id=28269;
AND B.id=28269 ORDER BY +A.id;
} {
28293 28216 28322 28286 28269
28215 28336 28262 28291 28320
28313 28298 28287
28215
28216
28262
28269
28286
28287
28291
28293
28298
28313
28320
28322
28336
}
# EVIDENCE-OF: R-02723-34107 Note that it is not necessary for all
@@ -1575,7 +1585,7 @@ execsql BEGIN
do_test 3.6 {
execsql { INSERT INTO rt2_parent VALUES(1000, 1000) }
execsql { SELECT rtreecheck('rt2') }
} {{Wrong number of entries in %_parent table - expected 9, actual 10}}
} {{Wrong number of entries in %_parent table - expected 10, actual 11}}
execsql ROLLBACK
+1 -1
View File
@@ -1043,7 +1043,7 @@ do_test rtreefuzz001-500 {
| end crash-2e81f5dce5cbd4.db}]
execsql { PRAGMA writable_schema = 1;}
catchsql {UPDATE t1 SET ex= ex ISNULL}
} {1 {database disk image is malformed}}
} {0 {}}
do_test rtreefuzz001-600 {
sqlite3 db {}
+11 -7
View File
@@ -3236,15 +3236,19 @@ static int sessionReadRecord(
}
}
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
sqlite3_int64 v = sessionGetI64(aVal);
if( eType==SQLITE_INTEGER ){
sqlite3VdbeMemSetInt64(apOut[i], v);
if( (pIn->nData-pIn->iNext)<8 ){
rc = SQLITE_CORRUPT_BKPT;
}else{
double d;
memcpy(&d, &v, 8);
sqlite3VdbeMemSetDouble(apOut[i], d);
sqlite3_int64 v = sessionGetI64(aVal);
if( eType==SQLITE_INTEGER ){
sqlite3VdbeMemSetInt64(apOut[i], v);
}else{
double d;
memcpy(&d, &v, 8);
sqlite3VdbeMemSetDouble(apOut[i], d);
}
pIn->iNext += 8;
}
pIn->iNext += 8;
}
}
}
+2 -2
View File
@@ -838,11 +838,11 @@ sqlite3-worker1-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-bundler-frien
sqlite3-worker1-promiser-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-promiser-bundler-friendly.js
$(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1.js)))
$(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1-bundler-friendly.js),\
$(c-pp.D.bundler-friendly)))
$(c-pp.D.sqlite3-bundler-friendly)))
$(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),$(sqlite3-worker1-promiser.js)))
$(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),\
$(sqlite3-worker1-promiser-bundler-friendly.js),\
$(c-pp.D.bundler-friendly)))
$(c-pp.D.sqlite3-bundler-friendly)))
$(sqlite3-bundler-friendly.mjs): $(sqlite3-worker1-bundler-friendly.js) \
$(sqlite3-worker1-promiser-bundler-friendly.js)
$(sqlite3.js) $(sqlite3.mjs): $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js)
+55
View File
@@ -0,0 +1,55 @@
#!/this/is/make
#
# This makefile compiles SQLTester test files into something
# we can readily import into JavaScript.
all:
SHELL := $(shell which bash 2>/dev/null)
MAKEFILE := $(lastword $(MAKEFILE_LIST))
CLEAN_FILES :=
DISTCLEAN_FILES := ./--dummy-- *~
test-list.mjs := test-list.mjs
test-list.mjs.gz := $(test-list.mjs).gz
CLEAN_FILES += $(test-list.mjs)
tests.dir := $(firstword $(wildcard tests ../../jni/src/tests))
$(info test script dir=$(tests.dir))
tests.all := $(wildcard $(tests.dir)/*.test)
bin.touint8array := ./touint8array
$(bin.touint8array): $(bin.touint8array).c $(MAKEFILE)
$(CC) -o $@ $<
CLEAN_FILES += $(bin.touint8array)
ifneq (,$(tests.all))
$(test-list.mjs): $(bin.touint8array) $(tests.all) $(MAKEFILE)
@{\
echo 'export default ['; \
sep=''; \
for f in $(sort $(tests.all)); do \
echo -en $$sep'{"name": "'$${f##*/}'", "content":'; \
$(bin.touint8array) < $$f; \
echo -n '}'; \
sep=',\n'; \
done; \
echo '];'; \
} > $@
@echo "Created $@"
$(test-list.mjs.gz): $(test-list.mjs)
gzip -c $< > $@
CLEAN_FILES += $(test-list.mjs.gz)
all: $(test-list.mjs.gz)
else
@echo "Cannot build $(test-list.mjs) for lack of input test files."; \
echo "Symlink ./tests to a directory containing SQLTester-format "; \
echo "test scripts named *.test, then try again"; \
exit 1
endif
.PHONY: clean distclean
clean:
-rm -f $(CLEAN_FILES)
distclean: clean
-rm -f $(DISTCLEAN_FILES)
File diff suppressed because it is too large Load Diff
+148
View File
@@ -0,0 +1,148 @@
/*
** 2023-08-29
**
** 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 contains a test application for SQLTester.js.
*/
import {default as ns} from './SQLTester.mjs';
import {default as allTests} from './test-list.mjs';
globalThis.sqlite3 = ns.sqlite3;
const log = function f(...args){
console.log('SQLTester.run:',...args);
return f;
};
const out = function f(...args){ return f.outer.out(...args) };
out.outer = new ns.Outer();
out.outer.getOutputPrefix = ()=>'SQLTester.run: ';
const outln = (...args)=>{ return out.outer.outln(...args) };
const affirm = function(expr, msg){
if( !expr ){
throw new Error(arguments[1]
? ("Assertion failed: "+arguments[1])
: "Assertion failed");
}
}
let ts = new ns.TestScript('/foo.test',`
/*
** This is a comment. There are many like it but this one is mine.
**
** SCRIPT_MODULE_NAME: sanity-check-0
** xMIXED_MODULE_NAME: mixed-module
** xMODULE_NAME: module-name
** xREQUIRED_PROPERTIES: small fast reliable
** xREQUIRED_PROPERTIES: RECURSIVE_TRIGGERS
** xREQUIRED_PROPERTIES: TEMPSTORE_FILE TEMPSTORE_MEM
** xREQUIRED_PROPERTIES: AUTOVACUUM INCRVACUUM
**
*/
/* --verbosity 3 */
/* ---must-fail */
/* # must fail */
/* --verbosity 0 */
--print Hello, world.
--close all
--oom
--db 0
--new my.db
--null zilch
--testcase 1.0
SELECT 1, null;
--result 1 zilch
--glob *zil*
--notglob *ZIL*
SELECT 1, 2;
intentional error;
--run
/* ---intentional-failure */
--testcase json-1
SELECT json_array(1,2,3)
--json [1,2,3]
--testcase tableresult-1
select 1, 'a';
select 2, 'b';
--tableresult
# [a-z]
2 b
--end
--testcase json-block-1
select json_array(1,2,3);
select json_object('a',1,'b',2);
--json-block
[1,2,3]
{"a":1,"b":2}
--end
--testcase col-names-on
--column-names 1
select 1 as 'a', 2 as 'b';
--result a 1 b 2
--testcase col-names-off
--column-names 0
select 1 as 'a', 2 as 'b';
--result 1 2
--close
--print Until next time
`);
const sqt = new ns.SQLTester()
.setLogger(console.log.bind(console))
.verbosity(1)
.addTestScript(ts);
sqt.outer().outputPrefix('');
const runTests = function(){
try{
if( 0 ){
affirm( !sqt.getCurrentDb(), 'sqt.getCurrentDb()' );
sqt.openDb('/foo.db', true);
affirm( !!sqt.getCurrentDb(),'sqt.getCurrentDb()' );
affirm( 'zilch' !== sqt.nullValue() );
ts.run(sqt);
affirm( 'zilch' === sqt.nullValue() );
sqt.addTestScript(ts);
sqt.runTests();
}else{
for(const t of allTests){
sqt.addTestScript( new ns.TestScript(t) );
}
allTests.length = 0;
sqt.runTests();
}
}finally{
//log( "Metrics:", sqt.metrics );
sqt.reset();
}
};
if( globalThis.WorkerGlobalScope ){
const wPost = (type,payload)=>globalThis.postMessage({type, payload});
globalThis.onmessage = function({data}){
switch(data.type){
case 'run-tests':{
try{ runTests(); }
finally{ wPost('tests-end', sqt.metrics); }
break;
}
default:
log("unhandled onmessage: ",data);
break;
}
};
sqt.setLogger((msg)=>{
wPost('stdout', {message: msg});
});
wPost('is-ready');
//globalThis.onmessage({data:{type:'run-tests'}});
}else{
runTests();
}
+127
View File
@@ -0,0 +1,127 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
<!--link rel="stylesheet" href="../common/emscripten.css"/-->
<link rel="stylesheet" href="../common/testing.css"/>
<title>SQLTester</title>
</head>
<style>
fieldset {
display: flex;
flex-direction: row;
padding-right: 1em;
}
fieldset > :not(.legend) {
display: flex;
flex-direction: row;
padding-right: 1em;
}
</style>
<body>
<h1>SQLTester for JS/WASM</h1>
<p>This app reads in a build-time-defined set of SQLTester test
scripts and runs them through the test suite.
</p>
<fieldset>
<legend>Options</legend>
<span class='input-wrapper'>
<input type='checkbox' id='cb-log-reverse' checked>
<label for='cb-log-reverse'>Reverse log order?</label>
</span>
<input type='button' id='btn-run-tests' value='Run tests'/>
</fieldset>
<div id='test-output'>Test output will go here.</div>
<!--script src='SQLTester.run.mjs' type='module'></script-->
<script>
(async function(){
const W = new Worker('SQLTester.run.mjs',{
type: 'module'
});
const wPost = (type,payload)=>W.postMessage({type,payload});
const mapToString = (v)=>{
switch(typeof v){
case 'string': return v;
case 'number': case 'boolean':
case 'undefined': case 'bigint':
return ''+v;
default: break;
}
if(null===v) return 'null';
if(v instanceof Error){
v = {
message: v.message,
stack: v.stack,
errorClass: v.name
};
}
return JSON.stringify(v,undefined,2);
};
const normalizeArgs = (args)=>args.map(mapToString);
const logTarget = document.querySelector('#test-output');
const logClass = function(cssClass,...args){
const ln = document.createElement('div');
if(cssClass){
for(const c of (Array.isArray(cssClass) ? cssClass : [cssClass])){
ln.classList.add(c);
}
}
ln.append(document.createTextNode(normalizeArgs(args).join(' ')));
logTarget.append(ln);
};
{
const cbReverse = document.querySelector('#cb-log-reverse');
const cbReverseKey = 'SQLTester:cb-log-reverse';
const cbReverseIt = ()=>{
logTarget.classList[cbReverse.checked ? 'add' : 'remove']('reverse');
};
cbReverse.addEventListener('change', cbReverseIt, true);
cbReverseIt();
}
const btnRun = document.querySelector('#btn-run-tests');
const runTests = ()=>{
btnRun.setAttribute('disabled','disabled');
wPost('run-tests');
logTarget.innerText = 'Running tests...';
}
btnRun.addEventListener('click', runTests);
const log2 = function f(...args){
logClass('', ...args);
return f;
};
const log = function f(...args){
logClass('','index.html:',...args);
return f;
};
const timerId = setTimeout( ()=>{
logClass('error',"The SQLTester module is taking an unusually ",
"long time to load. More information may be available",
"in the dev console.");
}, 3000 /* assuming localhost */ );
W.onmessage = function({data}){
switch(data.type){
case 'stdout': log2(data.payload.message); break;
case 'tests-end':
btnRun.removeAttribute('disabled');
delete data.payload.nTest;
log("test results:",data.payload);
break;
case 'is-ready':
clearTimeout(timerId);
runTests(); break;
default:
log("unhandled onmessage",data);
break;
}
};
//runTests()
/* Inexplicably, */
})();
</script>
</body>
</html>
+29
View File
@@ -0,0 +1,29 @@
/*
** 2023-08-29
**
** 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 contains a tool for writing out the contents of stdin as
** a comma-separated list of numbers, one per byte.
*/
#include <stdio.h>
int main(int argc, char const **argv){
int i;
int rc = 0, colWidth = 30;
int ch;
printf("[");
for( i=0; EOF!=(ch = fgetc(stdin)); ++i ){
if( 0!=i ) printf(",");
if( i && 0==(i%colWidth) ) puts("");
printf("%d",ch);
}
printf("]");
return rc;
}
+36
View File
@@ -1135,7 +1135,23 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
return 1===n
? wasm.pstack.alloc(safePtrSize ? 8 : wasm.ptrSizeof)
: wasm.pstack.allocChunks(n, safePtrSize ? 8 : wasm.ptrSizeof);
},
/**
Records the current pstack position, calls the given function,
passing it the sqlite3 object, then restores the pstack
regardless of whether the function throws. Returns the result
of the call or propagates an exception on error.
Added in 3.44.
*/
call: function(f){
const stackPos = wasm.pstack.pointer;
try{ return f(sqlite3) } finally{
wasm.pstack.restore(stackPos);
}
}
})/*wasm.pstack*/;
Object.defineProperties(wasm.pstack, {
/**
@@ -1543,6 +1559,26 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
}
};
/**
Converts SQL input from a variety of convenient formats
to plain strings.
If v is a string, it is returned as-is. If it is-a Array, its
join("") result is returned. If is is a Uint8Array, Int8Array,
or ArrayBuffer, it is assumed to hold UTF-8-encoded text and is
decoded to a string. If it looks like a WASM pointer,
wasm.cstrToJs(sql) is returned. Else undefined is returned.
Added in 3.44
*/
capi.sqlite3_js_sql_to_string = (sql)=>{
if('string' === typeof sql){
return sql;
}
const x = flexibleString(v);
return x===v ? undefined : x;
}
if( util.isUIThread() ){
/* Features specific to the main window thread... */
@@ -895,7 +895,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
sah.read( header, {at: 0} );
util.affirmDbHeader( header );
}
sah.write(new Uint8Array(2), {
sah.write(new Uint8Array([1,1]), {
at: HEADER_OFFSET_DATA + 18
}/*force db out of WAL mode*/);
}catch(e){
@@ -927,7 +927,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
this.setAssociatedPath(sah, '', 0);
toss("Expected to write "+n+" bytes but wrote "+nWrote+".");
}else{
sah.write(new Uint8Array([0,0]), {at: HEADER_OFFSET_DATA+18}
sah.write(new Uint8Array([1,1]), {at: HEADER_OFFSET_DATA+18}
/* force db out of WAL mode */);
this.setAssociatedPath(sah, name, capi.SQLITE_OPEN_MAIN_DB);
}
+2 -2
View File
@@ -1197,7 +1197,7 @@ const installOpfsVfs = function callee(options){
sah.read( header, {at: 0} );
util.affirmDbHeader( header );
}
sah.write(new Uint8Array(2), {at: 18}/*force db out of WAL mode*/);
sah.write(new Uint8Array([1,1]), {at: 18}/*force db out of WAL mode*/);
return nWrote;
}catch(e){
await sah.close();
@@ -1255,7 +1255,7 @@ const installOpfsVfs = function callee(options){
if(nWrote != n){
toss("Expected to write "+n+" bytes but wrote "+nWrote+".");
}
sah.write(new Uint8Array(2), {at: 18}) /* force db out of WAL mode */;
sah.write(new Uint8Array([1,1]), {at: 18}) /* force db out of WAL mode */;
return nWrote;
}catch(e){
if( sah ){ await sah.close(); sah = undefined; }

Some files were not shown because too many files have changed in this diff Show More