Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From 4f512a39afe14cb4 To 0e69b2c379e61893
2023-01-13
| ||
19:32 | In expression nodes of type REGISTER with sub-type COLUMN, ensure that the iColumn field is set correctly, as otherwise the "IS NULL" operator might be incorrectly optimized. Fix for the problem described by forum post d010a26798915b53. (check-in: 76b440c588 user: drh tags: branch-3.40) | |
2023-01-05
| ||
13:55 | Remove the unused sqlite3IsMemdb() routine, to avoid a harmless compiler warning. (check-in: 4f512a39af user: drh tags: branch-3.40) | |
13:41 | Fix a problem with applying integer affinity to a MEM_IntReal value. Forum post d270125fae. (check-in: d28d9398a5 user: drh tags: branch-3.40) | |
2022-12-26
| ||
13:45 | Extend [0e69b2c379e618] to support uninstalling stale JS-to-WASM function pointers added on behalf of UDFs. (check-in: 7a46e629dc user: stephan tags: trunk) | |
13:00 | Add JS infrastructure which enables sqlite3.capi.sqlite3_close_v2() to clean up stale JS-to-WASM collation function conversions installed on behalf of a given db handle. The same for UDF mappings is TODO. (check-in: 0e69b2c379 user: stephan tags: trunk) | |
11:13 | Reimplement sqlite3.capi.sqlite3_close_v2() and sqlite3session_delete() as a hand-written bindings so that they can attempt to clean up certain (potentially) FuncPtrAdapter-installed functions before closing. Correct the create-function family of JS-to-function-pointer automated conversions to include the UDF's arity as part of the mapping's key so that (un)binding a UDF to different functions for different arities works (and add tests confirming it). Correct a broken doc link in module-symbols.html. (check-in: 60b262ef0f user: stephan tags: trunk) | |
Changes to Makefile.in.
︙ | ︙ | |||
433 434 435 436 437 438 439 | $(TOP)/src/test_wsd.c \ $(TOP)/ext/fts3/fts3_term.c \ $(TOP)/ext/fts3/fts3_test.c \ $(TOP)/ext/session/test_session.c \ $(TOP)/ext/recover/sqlite3recover.c \ $(TOP)/ext/recover/dbdata.c \ $(TOP)/ext/recover/test_recover.c \ | | > | 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 | $(TOP)/src/test_wsd.c \ $(TOP)/ext/fts3/fts3_term.c \ $(TOP)/ext/fts3/fts3_test.c \ $(TOP)/ext/session/test_session.c \ $(TOP)/ext/recover/sqlite3recover.c \ $(TOP)/ext/recover/dbdata.c \ $(TOP)/ext/recover/test_recover.c \ $(TOP)/ext/rbu/test_rbu.c # Statically linked extensions # TESTSRC += \ $(TOP)/ext/expert/sqlite3expert.c \ $(TOP)/ext/expert/test_expert.c \ $(TOP)/ext/misc/amatch.c \ $(TOP)/ext/misc/appendvfs.c \ $(TOP)/ext/misc/basexx.c \ $(TOP)/ext/misc/carray.c \ $(TOP)/ext/misc/cksumvfs.c \ $(TOP)/ext/misc/closure.c \ $(TOP)/ext/misc/csv.c \ $(TOP)/ext/misc/decimal.c \ $(TOP)/ext/misc/eval.c \ $(TOP)/ext/misc/explain.c \ |
︙ | ︙ | |||
774 775 776 777 778 779 780 781 782 783 784 785 786 787 | touch .target_source sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_LINE_MACROS) cp tsrc/sqlite3ext.h . cp $(TOP)/ext/session/sqlite3session.h . sqlite3ext.h: .target_source cp tsrc/sqlite3ext.h . tclsqlite3.c: sqlite3.c echo '#ifndef USE_SYSTEM_SQLITE' >tclsqlite3.c cat sqlite3.c >>tclsqlite3.c echo '#endif /* USE_SYSTEM_SQLITE */' >>tclsqlite3.c | > > > > > > > > > | 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 | touch .target_source sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_LINE_MACROS) cp tsrc/sqlite3ext.h . cp $(TOP)/ext/session/sqlite3session.h . sqlite3r.h: sqlite3.h $(TCLSH_CMD) $(TOP)/tool/mksqlite3h.tcl $(TOP) --enable-recover >sqlite3r.h sqlite3r.c: sqlite3.c sqlite3r.h cp $(TOP)/ext/recover/sqlite3recover.c tsrc/ cp $(TOP)/ext/recover/sqlite3recover.h tsrc/ cp $(TOP)/ext/recover/dbdata.c tsrc/ $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl --enable-recover $(AMALGAMATION_LINE_MACROS) sqlite3ext.h: .target_source cp tsrc/sqlite3ext.h . tclsqlite3.c: sqlite3.c echo '#ifndef USE_SYSTEM_SQLITE' >tclsqlite3.c cat sqlite3.c >>tclsqlite3.c echo '#endif /* USE_SYSTEM_SQLITE */' >>tclsqlite3.c |
︙ | ︙ | |||
1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 | # Source files that go into making shell.c SHELL_SRC = \ $(TOP)/src/shell.c.in \ $(TOP)/ext/misc/appendvfs.c \ $(TOP)/ext/misc/completion.c \ $(TOP)/ext/misc/decimal.c \ $(TOP)/ext/misc/fileio.c \ $(TOP)/ext/misc/ieee754.c \ $(TOP)/ext/misc/regexp.c \ $(TOP)/ext/misc/series.c \ $(TOP)/ext/misc/shathree.c \ $(TOP)/ext/misc/sqlar.c \ $(TOP)/ext/misc/uint.c \ | > > > | 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 | # Source files that go into making shell.c SHELL_SRC = \ $(TOP)/src/shell.c.in \ $(TOP)/ext/misc/appendvfs.c \ $(TOP)/ext/misc/completion.c \ $(TOP)/ext/misc/decimal.c \ $(TOP)/ext/misc/basexx.c \ $(TOP)/ext/misc/base64.c \ $(TOP)/ext/misc/base85.c \ $(TOP)/ext/misc/fileio.c \ $(TOP)/ext/misc/ieee754.c \ $(TOP)/ext/misc/regexp.c \ $(TOP)/ext/misc/series.c \ $(TOP)/ext/misc/shathree.c \ $(TOP)/ext/misc/sqlar.c \ $(TOP)/ext/misc/uint.c \ |
︙ | ︙ |
Changes to Makefile.msc.
︙ | ︙ | |||
1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 | # Statically linked extensions. # TESTEXT = \ $(TOP)\ext\expert\sqlite3expert.c \ $(TOP)\ext\expert\test_expert.c \ $(TOP)\ext\misc\amatch.c \ $(TOP)\ext\misc\appendvfs.c \ $(TOP)\ext\misc\carray.c \ $(TOP)\ext\misc\cksumvfs.c \ $(TOP)\ext\misc\closure.c \ $(TOP)\ext\misc\csv.c \ $(TOP)\ext\misc\decimal.c \ $(TOP)\ext\misc\eval.c \ $(TOP)\ext\misc\explain.c \ | > | 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 | # Statically linked extensions. # TESTEXT = \ $(TOP)\ext\expert\sqlite3expert.c \ $(TOP)\ext\expert\test_expert.c \ $(TOP)\ext\misc\amatch.c \ $(TOP)\ext\misc\appendvfs.c \ $(TOP)\ext\misc\basexx.c \ $(TOP)\ext\misc\carray.c \ $(TOP)\ext\misc\cksumvfs.c \ $(TOP)\ext\misc\closure.c \ $(TOP)\ext\misc\csv.c \ $(TOP)\ext\misc\decimal.c \ $(TOP)\ext\misc\eval.c \ $(TOP)\ext\misc\explain.c \ |
︙ | ︙ | |||
2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 | .\mkkeywordhash.exe > keywordhash.h # Source files that go into making shell.c SHELL_SRC = \ $(TOP)\src\shell.c.in \ $(TOP)\ext\misc\appendvfs.c \ $(TOP)\ext\misc\completion.c \ $(TOP)\ext\misc\decimal.c \ $(TOP)\ext\misc\fileio.c \ $(TOP)\ext\misc\ieee754.c \ $(TOP)\ext\misc\regexp.c \ $(TOP)\ext\misc\series.c \ $(TOP)\ext\misc\shathree.c \ $(TOP)\ext\misc\uint.c \ | > > | 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 | .\mkkeywordhash.exe > keywordhash.h # Source files that go into making shell.c SHELL_SRC = \ $(TOP)\src\shell.c.in \ $(TOP)\ext\misc\appendvfs.c \ $(TOP)\ext\misc\completion.c \ $(TOP)\ext\misc\base64.c \ $(TOP)\ext\misc\base85.c \ $(TOP)\ext\misc\decimal.c \ $(TOP)\ext\misc\fileio.c \ $(TOP)\ext\misc\ieee754.c \ $(TOP)\ext\misc\regexp.c \ $(TOP)\ext\misc\series.c \ $(TOP)\ext\misc\shathree.c \ $(TOP)\ext\misc\uint.c \ |
︙ | ︙ |
Changes to VERSION.
|
| | | 1 | 3.41.0 |
Changes to configure.
1 2 | #! /bin/sh # Guess values for system-dependent variables and create Makefiles. | | | 1 2 3 4 5 6 7 8 9 10 | #! /bin/sh # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.69 for sqlite 3.41.0. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. # # # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. |
︙ | ︙ | |||
722 723 724 725 726 727 728 | subdirs= MFLAGS= MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' | | | | 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 | subdirs= MFLAGS= MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' PACKAGE_VERSION='3.41.0' PACKAGE_STRING='sqlite 3.41.0' PACKAGE_BUGREPORT='' PACKAGE_URL='' # Factoring default headers for most tests. ac_includes_default="\ #include <stdio.h> #ifdef HAVE_SYS_TYPES_H |
︙ | ︙ | |||
1464 1465 1466 1467 1468 1469 1470 | # # Report the --help message. # 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 | | | 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 | # # Report the --help message. # 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.41.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. |
︙ | ︙ | |||
1529 1530 1531 1532 1533 1534 1535 | --build=BUILD configure for building on BUILD [guessed] --host=HOST cross-compile to build programs to run on HOST [BUILD] _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in | | | 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 | --build=BUILD configure for building on BUILD [guessed] --host=HOST cross-compile to build programs to run on HOST [BUILD] _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in short | recursive ) echo "Configuration of sqlite 3.41.0:";; esac cat <<\_ACEOF Optional Features: --disable-option-checking ignore unrecognized --enable/--with options --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] |
︙ | ︙ | |||
1657 1658 1659 1660 1661 1662 1663 | cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF | | | 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 | cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF sqlite configure 3.41.0 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF exit |
︙ | ︙ | |||
2076 2077 2078 2079 2080 2081 2082 | eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_header_mongrel cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. | | | 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 | eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_header_mongrel 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.41.0, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ _ACEOF exec 5>>config.log { |
︙ | ︙ | |||
12386 12387 12388 12389 12390 12391 12392 | test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" | | | 12386 12387 12388 12389 12390 12391 12392 12393 12394 12395 12396 12397 12398 12399 12400 | test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # 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.41.0, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ |
︙ | ︙ | |||
12452 12453 12454 12455 12456 12457 12458 | Report bugs to the package provider." _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ | | | 12452 12453 12454 12455 12456 12457 12458 12459 12460 12461 12462 12463 12464 12465 12466 | Report bugs to the package provider." _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.41.0 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" Copyright (C) 2012 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." |
︙ | ︙ |
Changes to ext/fts5/fts5_index.c.
︙ | ︙ | |||
5069 5070 5071 5072 5073 5074 5075 | Fts5Buffer out = {0, 0, 0}; Fts5Buffer tmp = {0, 0, 0}; i64 iLastRowid = 0; /* Initialize a doclist-iterator for each input buffer. Arrange them in ** a linked-list starting at pHead in ascending order of rowid. Avoid ** linking any iterators already at EOF into the linked list at all. */ | | | 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 | Fts5Buffer out = {0, 0, 0}; Fts5Buffer tmp = {0, 0, 0}; i64 iLastRowid = 0; /* Initialize a doclist-iterator for each input buffer. Arrange them in ** a linked-list starting at pHead in ascending order of rowid. Avoid ** linking any iterators already at EOF into the linked list at all. */ assert( nBuf+1<=(int)(sizeof(aMerger)/sizeof(aMerger[0])) ); memset(aMerger, 0, sizeof(PrefixMerger)*(nBuf+1)); pHead = &aMerger[nBuf]; fts5DoclistIterInit(p1, &pHead->iter); for(i=0; i<nBuf; i++){ fts5DoclistIterInit(&aBuf[i], &aMerger[i].iter); fts5PrefixMergerInsertByRowid(&pHead, &aMerger[i]); nOut += aBuf[i].n; |
︙ | ︙ |
Changes to ext/fts5/fts5_main.c.
︙ | ︙ | |||
1621 1622 1623 1624 1625 1626 1627 | ){ Fts5FullTable *pTab = (Fts5FullTable*)pVtab; Fts5Config *pConfig = pTab->p.pConfig; int eType0; /* value_type() of apVal[0] */ int rc = SQLITE_OK; /* Return code */ /* A transaction must be open when this is called. */ | | | 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 | ){ Fts5FullTable *pTab = (Fts5FullTable*)pVtab; Fts5Config *pConfig = pTab->p.pConfig; int eType0; /* value_type() of apVal[0] */ int rc = SQLITE_OK; /* Return code */ /* A transaction must be open when this is called. */ assert( pTab->ts.eState==1 ); assert( pVtab->zErrMsg==0 ); assert( nArg==1 || nArg==(2+pConfig->nCol+2) ); assert( sqlite3_value_type(apVal[0])==SQLITE_INTEGER || sqlite3_value_type(apVal[0])==SQLITE_NULL ); assert( pTab->p.pConfig->pzErrmsg==0 ); |
︙ | ︙ |
Changes to ext/fts5/test/fts5misc.test.
︙ | ︙ | |||
398 399 400 401 402 403 404 | SAVEPOINT one; } {} do_execsql_test 15.4 END do_test 15.4 { list [catch { db2 eval COMMIT } msg] $msg } {0 {}} | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 398 399 400 401 402 403 404 405 406 | SAVEPOINT one; } {} do_execsql_test 15.4 END do_test 15.4 { list [catch { db2 eval COMMIT } msg] $msg } {0 {}} finish_test |
Added ext/misc/base64.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | /* ** 2022-11-18 ** ** 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 is a SQLite extension for converting in either direction ** between a (binary) blob and base64 text. Base64 can transit a ** sane USASCII channel unmolested. It also plays nicely in CSV or ** written as TCL brace-enclosed literals or SQL string literals, ** and can be used unmodified in XML-like documents. ** ** This is an independent implementation of conversions specified in ** RFC 4648, done on the above date by the author (Larry Brasfield) ** who thereby has the right to put this into the public domain. ** ** The conversions meet RFC 4648 requirements, provided that this ** C source specifies that line-feeds are included in the encoded ** data to limit visible line lengths to 72 characters and to ** terminate any encoded blob having non-zero length. ** ** Length limitations are not imposed except that the runtime ** SQLite string or blob length limits are respected. Otherwise, ** any length binary sequence can be represented and recovered. ** Generated base64 sequences, with their line-feeds included, ** can be concatenated; the result converted back to binary will ** be the concatenation of the represented binary sequences. ** ** This SQLite3 extension creates a function, base64(x), which ** either: converts text x containing base64 to a returned blob; ** or converts a blob x to returned text containing base64. An ** error will be thrown for other input argument types. ** ** This code relies on UTF-8 encoding only with respect to the ** meaning of the first 128 (7-bit) codes matching that of USASCII. ** It will fail miserably if somehow made to try to convert EBCDIC. ** Because it is table-driven, it could be enhanced to handle that, ** but the world and SQLite have moved on from that anachronism. ** ** To build the extension: ** Set shell variable SQDIR=<your favorite SQLite checkout directory> ** *Nix: gcc -O2 -shared -I$SQDIR -fPIC -o base64.so base64.c ** OSX: gcc -O2 -dynamiclib -fPIC -I$SQDIR -o base64.dylib base64.c ** Win32: gcc -O2 -shared -I%SQDIR% -o base64.dll base64.c ** Win32: cl /Os -I%SQDIR% base64.c -link -dll -out:base64.dll */ #include <assert.h> #include "sqlite3ext.h" SQLITE_EXTENSION_INIT1; #define PC 0x80 /* pad character */ #define WS 0x81 /* whitespace */ #define ND 0x82 /* Not above or digit-value */ #define PAD_CHAR '=' #ifndef U8_TYPEDEF typedef unsigned char u8; #define U8_TYPEDEF #endif static const u8 b64DigitValues[128] = { /* HT LF VT FF CR */ ND,ND,ND,ND, ND,ND,ND,ND, ND,WS,WS,WS, WS,WS,ND,ND, /* US */ ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND, /*sp + / */ WS,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,62, ND,ND,ND,63, /* 0 1 5 9 = */ 52,53,54,55, 56,57,58,59, 60,61,ND,ND, ND,PC,ND,ND, /* A O */ ND, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, /* P Z */ 15,16,17,18, 19,20,21,22, 23,24,25,ND, ND,ND,ND,ND, /* a o */ ND,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, /* p z */ 41,42,43,44, 45,46,47,48, 49,50,51,ND, ND,ND,ND,ND }; static const char b64Numerals[64+1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; #define BX_DV_PROTO(c) \ ((((u8)(c))<0x80)? (u8)(b64DigitValues[(u8)(c)]) : 0x80) #define IS_BX_DIGIT(bdp) (((u8)(bdp))<0x80) #define IS_BX_WS(bdp) ((bdp)==WS) #define IS_BX_PAD(bdp) ((bdp)==PC) #define BX_NUMERAL(dv) (b64Numerals[(u8)(dv)]) /* Width of base64 lines. Should be an integer multiple of 4. */ #define B64_DARK_MAX 72 /* Encode a byte buffer into base64 text with linefeeds appended to limit ** encoded group lengths to B64_DARK_MAX or to terminate the last group. */ static char* toBase64( u8 *pIn, int nbIn, char *pOut ){ int nCol = 0; while( nbIn >= 3 ){ /* Do the bit-shuffle, exploiting unsigned input to avoid masking. */ pOut[0] = BX_NUMERAL(pIn[0]>>2); pOut[1] = BX_NUMERAL(((pIn[0]<<4)|(pIn[1]>>4))&0x3f); pOut[2] = BX_NUMERAL(((pIn[1]&0xf)<<2)|(pIn[2]>>6)); pOut[3] = BX_NUMERAL(pIn[2]&0x3f); pOut += 4; nbIn -= 3; pIn += 3; if( (nCol += 4)>=B64_DARK_MAX || nbIn<=0 ){ *pOut++ = '\n'; nCol = 0; } } if( nbIn > 0 ){ signed char nco = nbIn+1; int nbe; unsigned long qv = *pIn++; for( nbe=1; nbe<3; ++nbe ){ qv <<= 8; if( nbe<nbIn ) qv |= *pIn++; } for( nbe=3; nbe>=0; --nbe ){ char ce = (nbe<nco)? BX_NUMERAL((u8)(qv & 0x3f)) : PAD_CHAR; qv >>= 6; pOut[nbe] = ce; } pOut += 4; *pOut++ = '\n'; } *pOut = 0; return pOut; } /* Skip over text which is not base64 numeral(s). */ static char * skipNonB64( char *s ){ char c; while( (c = *s) && !IS_BX_DIGIT(BX_DV_PROTO(c)) ) ++s; return s; } /* Decode base64 text into a byte buffer. */ static u8* fromBase64( char *pIn, int ncIn, u8 *pOut ){ if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn; while( ncIn>0 && *pIn!=PAD_CHAR ){ static signed char nboi[] = { 0, 0, 1, 2, 3 }; char *pUse = skipNonB64(pIn); unsigned long qv = 0L; int nti, nbo, nac; ncIn -= (pUse - pIn); pIn = pUse; nti = (ncIn>4)? 4 : ncIn; ncIn -= nti; nbo = nboi[nti]; if( nbo==0 ) break; for( nac=0; nac<4; ++nac ){ char c = (nac<nti)? *pIn++ : b64Numerals[0]; u8 bdp = BX_DV_PROTO(c); switch( bdp ){ case ND: /* Treat dark non-digits as pad, but they terminate decode too. */ ncIn = 0; /* fall thru */ case WS: /* Treat whitespace as pad and terminate this group.*/ nti = nac; /* fall thru */ case PC: bdp = 0; --nbo; /* fall thru */ default: /* bdp is the digit value. */ qv = qv<<6 | bdp; break; } } switch( nbo ){ case 3: pOut[2] = (qv) & 0xff; case 2: pOut[1] = (qv>>8) & 0xff; case 1: pOut[0] = (qv>>16) & 0xff; } pOut += nbo; } return pOut; } /* This function does the work for the SQLite base64(x) UDF. */ static void base64(sqlite3_context *context, int na, sqlite3_value *av[]){ int nb, nc, nv = sqlite3_value_bytes(av[0]); int nvMax = sqlite3_limit(sqlite3_context_db_handle(context), SQLITE_LIMIT_LENGTH, -1); char *cBuf; u8 *bBuf; assert(na==1); switch( sqlite3_value_type(av[0]) ){ case SQLITE_BLOB: nb = nv; nc = 4*(nv+2/3); /* quads needed */ nc += (nc+(B64_DARK_MAX-1))/B64_DARK_MAX + 1; /* LFs and a 0-terminator */ if( nvMax < nc ){ sqlite3_result_error(context, "blob expanded to base64 too big", -1); return; } cBuf = sqlite3_malloc(nc); if( !cBuf ) goto memFail; bBuf = (u8*)sqlite3_value_blob(av[0]); nc = (int)(toBase64(bBuf, nb, cBuf) - cBuf); sqlite3_result_text(context, cBuf, nc, sqlite3_free); break; case SQLITE_TEXT: nc = nv; nb = 3*((nv+3)/4); /* may overestimate due to LF and padding */ if( nvMax < nb ){ sqlite3_result_error(context, "blob from base64 may be too big", -1); return; }else if( nb<1 ){ nb = 1; } bBuf = sqlite3_malloc(nb); if( !bBuf ) goto memFail; cBuf = (char *)sqlite3_value_text(av[0]); nb = (int)(fromBase64(cBuf, nc, bBuf) - bBuf); sqlite3_result_blob(context, bBuf, nb, sqlite3_free); break; default: sqlite3_result_error(context, "base64 accepts only blob or text", -1); return; } return; memFail: sqlite3_result_error(context, "base64 OOM", -1); } /* ** Establish linkage to running SQLite library. */ #ifndef SQLITE_SHELL_EXTFUNCS #ifdef _WIN32 __declspec(dllexport) #endif int sqlite3_base_init #else static int sqlite3_base64_init #endif (sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi){ SQLITE_EXTENSION_INIT2(pApi); (void)pzErr; return sqlite3_create_function (db, "base64", 1, SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_DIRECTONLY|SQLITE_UTF8, 0, base64, 0, 0); } /* ** Define some macros to allow this extension to be built into the shell ** conveniently, in conjunction with use of SQLITE_SHELL_EXTFUNCS. This ** allows shell.c, as distributed, to have this extension built in. */ #define BASE64_INIT(db) sqlite3_base64_init(db, 0, 0) #define BASE64_EXPOSE(db, pzErr) /* Not needed, ..._init() does this. */ |
Added ext/misc/base85.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 | /* ** 2022-11-16 ** ** 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 is a utility for converting binary to base85 or vice-versa. ** It can be built as a standalone program or an SQLite3 extension. ** ** Much like base64 representations, base85 can be sent through a ** sane USASCII channel unmolested. It also plays nicely in CSV or ** written as TCL brace-enclosed literals or SQL string literals. ** It is not suited for unmodified use in XML-like documents. ** ** The encoding used resembles Ascii85, but was devised by the author ** (Larry Brasfield) before Mozilla, Adobe, ZMODEM or other Ascii85 ** variant sources existed, in the 1984 timeframe on a VAX mainframe. ** Further, this is an independent implementation of a base85 system. ** Hence, the author has rightfully put this into the public domain. ** ** Base85 numerals are taken from the set of 7-bit USASCII codes, ** excluding control characters and Space ! " ' ( ) { | } ~ Del ** in code order representing digit values 0 to 84 (base 10.) ** ** Groups of 4 bytes, interpreted as big-endian 32-bit values, ** are represented as 5-digit base85 numbers with MS to LS digit ** order. Groups of 1-3 bytes are represented with 2-4 digits, ** still big-endian but 8-24 bit values. (Using big-endian yields ** the simplest transition to byte groups smaller than 4 bytes. ** These byte groups can also be considered base-256 numbers.) ** Groups of 0 bytes are represented with 0 digits and vice-versa. ** No pad characters are used; Encoded base85 numeral sequence ** (aka "group") length maps 1-to-1 to the decoded binary length. ** ** Any character not in the base85 numeral set delimits groups. ** When base85 is streamed or stored in containers of indefinite ** size, newline is used to separate it into sub-sequences of no ** more than 80 digits so that fgets() can be used to read it. ** ** Length limitations are not imposed except that the runtime ** SQLite string or blob length limits are respected. Otherwise, ** any length binary sequence can be represented and recovered. ** Base85 sequences can be concatenated by separating them with ** a non-base85 character; the conversion to binary will then ** be the concatenation of the represented binary sequences. ** The standalone program either converts base85 on stdin to create ** a binary file or converts a binary file to base85 on stdout. ** Read or make it blurt its help for invocation details. ** ** The SQLite3 extension creates a function, base85(x), which will ** either convert text base85 to a blob or a blob to text base85 ** and return the result (or throw an error for other types.) ** Unless built with OMIT_BASE85_CHECKER defined, it also creates a ** function, is_base85(t), which returns 1 iff the text t contains ** nothing other than base85 numerals and whitespace, or 0 otherwise. ** ** To build the extension: ** Set shell variable SQDIR=<your favorite SQLite checkout directory> ** and variable OPTS to -DOMIT_BASE85_CHECKER if is_base85() unwanted. ** *Nix: gcc -O2 -shared -I$SQDIR $OPTS -fPIC -o base85.so base85.c ** OSX: gcc -O2 -dynamiclib -fPIC -I$SQDIR $OPTS -o base85.dylib base85.c ** Win32: gcc -O2 -shared -I%SQDIR% %OPTS% -o base85.dll base85.c ** Win32: cl /Os -I%SQDIR% %OPTS% base85.c -link -dll -out:base85.dll ** ** To build the standalone program, define PP symbol BASE85_STANDALONE. Eg. ** *Nix or OSX: gcc -O2 -DBASE85_STANDALONE base85.c -o base85 ** Win32: gcc -O2 -DBASE85_STANDALONE -o base85.exe base85.c ** Win32: cl /Os /MD -DBASE85_STANDALONE base85.c */ #include <stdio.h> #include <memory.h> #include <string.h> #include <assert.h> #ifndef OMIT_BASE85_CHECKER # include <ctype.h> #endif #ifndef BASE85_STANDALONE # include "sqlite3ext.h" SQLITE_EXTENSION_INIT1; #else # ifdef _WIN32 # include <io.h> # include <fcntl.h> # else # define setmode(fd,m) # endif static char *zHelp = "Usage: base85 <dirFlag> <binFile>\n" " <dirFlag> is either -r to read or -w to write <binFile>,\n" " content to be converted to/from base85 on stdout/stdin.\n" " <binFile> names a binary file to be rendered or created.\n" " Or, the name '-' refers to the stdin or stdout stream.\n" ; static void sayHelp(){ printf("%s", zHelp); } #endif #ifndef U8_TYPEDEF typedef unsigned char u8; #define U8_TYPEDEF #endif /* Classify c according to interval within USASCII set w.r.t. base85 * Values of 1 and 3 are base85 numerals. Values of 0, 2, or 4 are not. */ #define B85_CLASS( c ) (((c)>='#')+((c)>'&')+((c)>='*')+((c)>'z')) /* Provide digitValue to b85Numeral offset as a function of above class. */ static u8 b85_cOffset[] = { 0, '#', 0, '*'-4, 0 }; #define B85_DNOS( c ) b85_cOffset[B85_CLASS(c)] /* Say whether c is a base85 numeral. */ #define IS_B85( c ) (B85_CLASS(c) & 1) #if 0 /* Not used, */ static u8 base85DigitValue( char c ){ u8 dv = (u8)(c - '#'); if( dv>87 ) return 0xff; return (dv > 3)? dv-3 : dv; } #endif /* Width of base64 lines. Should be an integer multiple of 5. */ #define B85_DARK_MAX 80 static char * skipNonB85( char *s ){ char c; while( (c = *s) && !IS_B85(c) ) ++s; return s; } /* Convert small integer, known to be in 0..84 inclusive, to base85 numeral. * Do not use the macro form with argument expression having a side-effect.*/ #if 0 static char base85Numeral( u8 b ){ return (b < 4)? (char)(b + '#') : (char)(b - 4 + '*'); } #else # define base85Numeral( dn )\ ((char)(((dn) < 4)? (char)((dn) + '#') : (char)((dn) - 4 + '*'))) #endif static char *putcs(char *pc, char *s){ char c; while( (c = *s++)!=0 ) *pc++ = c; return pc; } /* Encode a byte buffer into base85 text. If pSep!=0, it's a C string ** to be appended to encoded groups to limit their length to B85_DARK_MAX ** or to terminate the last group (to aid concatenation.) */ static char* toBase85( u8 *pIn, int nbIn, char *pOut, char *pSep ){ int nCol = 0; while( nbIn >= 4 ){ int nco = 5; unsigned long qbv = (pIn[0]<<24)|(pIn[1]<<16)|(pIn[2]<<8)|pIn[3]; while( nco > 0 ){ unsigned nqv = (unsigned)(qbv/85UL); unsigned char dv = qbv - 85UL*nqv; qbv = nqv; pOut[--nco] = base85Numeral(dv); } nbIn -= 4; pIn += 4; pOut += 5; if( pSep && (nCol += 5)>=B85_DARK_MAX ){ pOut = putcs(pOut, pSep); nCol = 0; } } if( nbIn > 0 ){ int nco = nbIn + 1; unsigned long qv = *pIn++; int nbe = 1; while( nbe++ < nbIn ){ qv = (qv<<8) | *pIn++; } nCol += nco; while( nco > 0 ){ u8 dv = (u8)(qv % 85); qv /= 85; pOut[--nco] = base85Numeral(dv); } pOut += (nbIn+1); } if( pSep && nCol>0 ) pOut = putcs(pOut, pSep); *pOut = 0; return pOut; } /* Decode base85 text into a byte buffer. */ static u8* fromBase85( char *pIn, int ncIn, u8 *pOut ){ if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn; while( ncIn>0 ){ static signed char nboi[] = { 0, 0, 1, 2, 3, 4 }; char *pUse = skipNonB85(pIn); unsigned long qv = 0L; int nti, nbo; ncIn -= (pUse - pIn); pIn = pUse; nti = (ncIn>5)? 5 : ncIn; nbo = nboi[nti]; if( nbo==0 ) break; while( nti>0 ){ char c = *pIn++; u8 cdo = B85_DNOS(c); --ncIn; if( cdo==0 ) break; qv = 85 * qv + (c - cdo); --nti; } nbo -= nti; /* Adjust for early (non-digit) end of group. */ switch( nbo ){ case 4: *pOut++ = (qv >> 24)&0xff; case 3: *pOut++ = (qv >> 16)&0xff; case 2: *pOut++ = (qv >> 8)&0xff; case 1: *pOut++ = qv&0xff; case 0: break; } } return pOut; } #ifndef OMIT_BASE85_CHECKER /* Say whether input char sequence is all (base85 and/or whitespace).*/ static int allBase85( char *p, int len ){ char c; while( len-- > 0 && (c = *p++) != 0 ){ if( !IS_B85(c) && !isspace(c) ) return 0; } return 1; } #endif #ifndef BASE85_STANDALONE # ifndef OMIT_BASE85_CHECKER /* This function does the work for the SQLite is_base85(t) UDF. */ static void is_base85(sqlite3_context *context, int na, sqlite3_value *av[]){ assert(na==1); switch( sqlite3_value_type(av[0]) ){ case SQLITE_TEXT: { int rv = allBase85( (char *)sqlite3_value_text(av[0]), sqlite3_value_bytes(av[0]) ); sqlite3_result_int(context, rv); } break; case SQLITE_NULL: sqlite3_result_null(context); break; default: sqlite3_result_error(context, "is_base85 accepts only text or NULL", -1); return; } } # endif /* This function does the work for the SQLite base85(x) UDF. */ static void base85(sqlite3_context *context, int na, sqlite3_value *av[]){ int nb, nc, nv = sqlite3_value_bytes(av[0]); int nvMax = sqlite3_limit(sqlite3_context_db_handle(context), SQLITE_LIMIT_LENGTH, -1); char *cBuf; u8 *bBuf; assert(na==1); switch( sqlite3_value_type(av[0]) ){ case SQLITE_BLOB: nb = nv; /* ulongs tail newlines tailenc+nul*/ nc = 5*(nv/4) + nv%4 + nv/64+1 + 2; if( nvMax < nc ){ sqlite3_result_error(context, "blob expanded to base85 too big", -1); return; } cBuf = sqlite3_malloc(nc); if( !cBuf ) goto memFail; bBuf = (u8*)sqlite3_value_blob(av[0]); nc = (int)(toBase85(bBuf, nb, cBuf, "\n") - cBuf); sqlite3_result_text(context, cBuf, nc, sqlite3_free); break; case SQLITE_TEXT: nc = nv; nb = 4*(nv/5) + nv%5; /* may overestimate */ if( nvMax < nb ){ sqlite3_result_error(context, "blob from base85 may be too big", -1); return; }else if( nb<1 ){ nb = 1; } bBuf = sqlite3_malloc(nb); if( !bBuf ) goto memFail; cBuf = (char *)sqlite3_value_text(av[0]); nb = (int)(fromBase85(cBuf, nc, bBuf) - bBuf); sqlite3_result_blob(context, bBuf, nb, sqlite3_free); break; default: sqlite3_result_error(context, "base85 accepts only blob or text.", -1); return; } return; memFail: sqlite3_result_error(context, "base85 OOM", -1); } /* ** Establish linkage to running SQLite library. */ #ifndef SQLITE_SHELL_EXTFUNCS #ifdef _WIN32 __declspec(dllexport) #endif int sqlite3_base_init #else static int sqlite3_base85_init #endif (sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi){ SQLITE_EXTENSION_INIT2(pApi); (void)pzErr; # ifndef OMIT_BASE85_CHECKER { int rc = sqlite3_create_function (db, "is_base85", 1, SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_UTF8, 0, is_base85, 0, 0); if( rc!=SQLITE_OK ) return rc; } # endif return sqlite3_create_function (db, "base85", 1, SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_DIRECTONLY|SQLITE_UTF8, 0, base85, 0, 0); } /* ** Define some macros to allow this extension to be built into the shell ** conveniently, in conjunction with use of SQLITE_SHELL_EXTFUNCS. This ** allows shell.c, as distributed, to have this extension built in. */ # define BASE85_INIT(db) sqlite3_base85_init(db, 0, 0) # define BASE85_EXPOSE(db, pzErr) /* Not needed, ..._init() does this. */ #else /* standalone program */ int main(int na, char *av[]){ int cin; int rc = 0; u8 bBuf[4*(B85_DARK_MAX/5)]; char cBuf[5*(sizeof(bBuf)/4)+2]; size_t nio; # ifndef OMIT_BASE85_CHECKER int b85Clean = 1; # endif char rw; FILE *fb = 0, *foc = 0; char fmode[3] = "xb"; if( na < 3 || av[1][0]!='-' || (rw = av[1][1])==0 || (rw!='r' && rw!='w') ){ sayHelp(); return 0; } fmode[0] = rw; if( av[2][0]=='-' && av[2][1]==0 ){ switch( rw ){ case 'r': fb = stdin; setmode(fileno(stdin), O_BINARY); break; case 'w': fb = stdout; setmode(fileno(stdout), O_BINARY); break; } }else{ fb = fopen(av[2], fmode); foc = fb; } if( !fb ){ fprintf(stderr, "Cannot open %s for %c\n", av[2], rw); rc = 1; }else{ switch( rw ){ case 'r': while( (nio = fread( bBuf, 1, sizeof(bBuf), fb))>0 ){ toBase85( bBuf, (int)nio, cBuf, 0 ); fprintf(stdout, "%s\n", cBuf); } break; case 'w': while( 0 != fgets(cBuf, sizeof(cBuf), stdin) ){ int nc = strlen(cBuf); size_t nbo = fromBase85( cBuf, nc, bBuf ) - bBuf; if( 1 != fwrite(bBuf, nbo, 1, fb) ) rc = 1; # ifndef OMIT_BASE85_CHECKER b85Clean &= allBase85( cBuf, nc ); # endif } break; default: sayHelp(); rc = 1; } if( foc ) fclose(foc); } # ifndef OMIT_BASE85_CHECKER if( !b85Clean ){ fprintf(stderr, "Base85 input had non-base85 dark or control content.\n"); } # endif return rc; } #endif |
Added ext/misc/basexx.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | /* ** 2022-11-20 ** ** 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 source allows multiple SQLite extensions to be either: combined ** into a single runtime-loadable library; or built into the SQLite shell ** using a preprocessing convention set by src/shell.c.in (and shell.c). ** ** Presently, it combines the base64.c and base85.c extensions. However, ** it can be used as a template for other combinations. ** ** Example usages: ** ** - Build a runtime-loadable extension from SQLite checkout directory: ** *Nix, OSX: gcc -O2 -shared -I. -fPIC -o basexx.so ext/misc/basexx.c ** Win32: cl /Os -I. ext/misc/basexx.c -link -dll -out:basexx.dll ** ** - Incorporate as built-in in sqlite3 shell: ** *Nix, OSX with gcc on a like platform: ** export mop1=-DSQLITE_SHELL_EXTSRC=ext/misc/basexx.c ** export mop2=-DSQLITE_SHELL_EXTFUNCS=BASEXX ** make sqlite3 "OPTS=$mop1 $mop2" ** Win32 with Microsoft toolset on Windows: ** set mop1=-DSQLITE_SHELL_EXTSRC=ext/misc/basexx.c ** set mop2=-DSQLITE_SHELL_EXTFUNCS=BASEXX ** set mops="OPTS=%mop1% %mop2%" ** nmake -f Makefile.msc sqlite3.exe %mops% */ #ifndef SQLITE_SHELL_EXTFUNCS /* Guard for #include as built-in extension. */ # include "sqlite3ext.h" SQLITE_EXTENSION_INIT1; #endif static void init_api_ptr(const sqlite3_api_routines *pApi){ SQLITE_EXTENSION_INIT2(pApi); } #undef SQLITE_EXTENSION_INIT1 #define SQLITE_EXTENSION_INIT1 /* */ #undef SQLITE_EXTENSION_INIT2 #define SQLITE_EXTENSION_INIT2(v) (void)v typedef unsigned char u8; #define U8_TYPEDEF /* These next 2 undef's are only needed because the entry point names * collide when formulated per the rules stated for loadable extension * entry point names that will be deduced from the file basenames. */ #undef sqlite3_base_init #define sqlite3_base_init sqlite3_base64_init #include "base64.c" #undef sqlite3_base_init #define sqlite3_base_init sqlite3_base85_init #include "base85.c" #ifdef _WIN32 __declspec(dllexport) #endif int sqlite3_basexx_init(sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi){ init_api_ptr(pApi); int rc1 = BASE64_INIT(db); int rc2 = BASE85_INIT(db); if( rc1==SQLITE_OK && rc2==SQLITE_OK ){ BASE64_EXPOSE(db, pzErr); BASE64_EXPOSE(db, pzErr); return SQLITE_OK; }else{ return SQLITE_ERROR; } } # define BASEXX_INIT(db) sqlite3_basexx_init(db, 0, 0) # define BASEXX_EXPOSE(db, pzErr) /* Not needed, ..._init() does this. */ |
Changes to ext/misc/decimal.c.
︙ | ︙ | |||
612 613 614 615 616 617 618 | { "decimal_mul", 2, decimalMulFunc }, }; unsigned int i; (void)pzErrMsg; /* Unused parameter */ SQLITE_EXTENSION_INIT2(pApi); | | | 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 | { "decimal_mul", 2, decimalMulFunc }, }; unsigned int i; (void)pzErrMsg; /* Unused parameter */ SQLITE_EXTENSION_INIT2(pApi); for(i=0; i<(int)(sizeof(aFunc)/sizeof(aFunc[0])) && rc==SQLITE_OK; i++){ rc = sqlite3_create_function(db, aFunc[i].zFuncName, aFunc[i].nArg, SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, 0, aFunc[i].xFunc, 0, 0); } if( rc==SQLITE_OK ){ rc = sqlite3_create_window_function(db, "decimal_sum", 1, SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, 0, |
︙ | ︙ |
Changes to ext/misc/regexp.c.
︙ | ︙ | |||
799 800 801 802 803 804 805 806 807 808 809 810 811 812 | const char *zPattern; const char *zErr; ReCompiled *pRe; sqlite3_str *pStr; int i; int n; char *z; zPattern = (const char*)sqlite3_value_text(argv[0]); if( zPattern==0 ) return; zErr = re_compile(&pRe, zPattern, sqlite3_user_data(context)!=0); if( zErr ){ re_free(pRe); sqlite3_result_error(context, zErr, -1); | > | 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 | const char *zPattern; const char *zErr; ReCompiled *pRe; sqlite3_str *pStr; int i; int n; char *z; (void)argc; zPattern = (const char*)sqlite3_value_text(argv[0]); if( zPattern==0 ) return; zErr = re_compile(&pRe, zPattern, sqlite3_user_data(context)!=0); if( zErr ){ re_free(pRe); sqlite3_result_error(context, zErr, -1); |
︙ | ︙ |
Changes to ext/misc/shathree.c.
︙ | ︙ | |||
527 528 529 530 531 532 533 | } sqlite3_result_blob(context, SHA3Final(&cx), iSize/8, SQLITE_TRANSIENT); } /* Compute a string using sqlite3_vsnprintf() with a maximum length ** of 50 bytes and add it to the hash. */ | | | 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 | } sqlite3_result_blob(context, SHA3Final(&cx), iSize/8, SQLITE_TRANSIENT); } /* Compute a string using sqlite3_vsnprintf() with a maximum length ** of 50 bytes and add it to the hash. */ static void sha3_step_vformat( SHA3Context *p, /* Add content to this context */ const char *zFormat, ... ){ va_list ap; int n; char zBuf[50]; |
︙ | ︙ | |||
623 624 625 626 627 628 629 | sqlite3_free(zMsg); return; } nCol = sqlite3_column_count(pStmt); z = sqlite3_sql(pStmt); if( z ){ n = (int)strlen(z); | | | 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 | sqlite3_free(zMsg); return; } nCol = sqlite3_column_count(pStmt); z = sqlite3_sql(pStmt); if( z ){ n = (int)strlen(z); sha3_step_vformat(&cx,"S%d:",n); SHA3Update(&cx,(unsigned char*)z,n); } /* Compute a hash over the result of the query */ while( SQLITE_ROW==sqlite3_step(pStmt) ){ SHA3Update(&cx,(const unsigned char*)"R",1); for(i=0; i<nCol; i++){ |
︙ | ︙ | |||
667 668 669 670 671 672 673 | x[0] = 'F'; SHA3Update(&cx,x,9); break; } case SQLITE_TEXT: { int n2 = sqlite3_column_bytes(pStmt, i); const unsigned char *z2 = sqlite3_column_text(pStmt, i); | | | | 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 | x[0] = 'F'; SHA3Update(&cx,x,9); break; } case SQLITE_TEXT: { int n2 = sqlite3_column_bytes(pStmt, i); const unsigned char *z2 = sqlite3_column_text(pStmt, i); sha3_step_vformat(&cx,"T%d:",n2); SHA3Update(&cx, z2, n2); break; } case SQLITE_BLOB: { int n2 = sqlite3_column_bytes(pStmt, i); const unsigned char *z2 = sqlite3_column_blob(pStmt, i); sha3_step_vformat(&cx,"B%d:",n2); SHA3Update(&cx, z2, n2); break; } } } } sqlite3_finalize(pStmt); |
︙ | ︙ |
Changes to ext/misc/stmt.c.
︙ | ︙ | |||
93 94 95 96 97 98 99 100 101 102 103 104 105 106 | #define STMT_COLUMN_NAIDX 6 /* SQLITE_STMTSTATUS_AUTOINDEX */ #define STMT_COLUMN_NSTEP 7 /* SQLITE_STMTSTATUS_VM_STEP */ #define STMT_COLUMN_REPREP 8 /* SQLITE_STMTSTATUS_REPREPARE */ #define STMT_COLUMN_RUN 9 /* SQLITE_STMTSTATUS_RUN */ #define STMT_COLUMN_MEM 10 /* SQLITE_STMTSTATUS_MEMUSED */ rc = sqlite3_declare_vtab(db, "CREATE TABLE x(sql,ncol,ro,busy,nscan,nsort,naidx,nstep," "reprep,run,mem)"); if( rc==SQLITE_OK ){ pNew = sqlite3_malloc64( sizeof(*pNew) ); *ppVtab = (sqlite3_vtab*)pNew; if( pNew==0 ) return SQLITE_NOMEM; | > > > > | 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | #define STMT_COLUMN_NAIDX 6 /* SQLITE_STMTSTATUS_AUTOINDEX */ #define STMT_COLUMN_NSTEP 7 /* SQLITE_STMTSTATUS_VM_STEP */ #define STMT_COLUMN_REPREP 8 /* SQLITE_STMTSTATUS_REPREPARE */ #define STMT_COLUMN_RUN 9 /* SQLITE_STMTSTATUS_RUN */ #define STMT_COLUMN_MEM 10 /* SQLITE_STMTSTATUS_MEMUSED */ (void)pAux; (void)argc; (void)argv; (void)pzErr; rc = sqlite3_declare_vtab(db, "CREATE TABLE x(sql,ncol,ro,busy,nscan,nsort,naidx,nstep," "reprep,run,mem)"); if( rc==SQLITE_OK ){ pNew = sqlite3_malloc64( sizeof(*pNew) ); *ppVtab = (sqlite3_vtab*)pNew; if( pNew==0 ) return SQLITE_NOMEM; |
︙ | ︙ | |||
212 213 214 215 216 217 218 219 220 221 222 223 224 225 | int argc, sqlite3_value **argv ){ stmt_cursor *pCur = (stmt_cursor *)pVtabCursor; sqlite3_stmt *p = 0; sqlite3_int64 iRowid = 1; StmtRow **ppRow = 0; stmtCsrReset(pCur); ppRow = &pCur->pRow; for(p=sqlite3_next_stmt(pCur->db, 0); p; p=sqlite3_next_stmt(pCur->db, p)){ const char *zSql = sqlite3_sql(p); sqlite3_int64 nSql = zSql ? strlen(zSql)+1 : 0; StmtRow *pNew = (StmtRow*)sqlite3_malloc64(sizeof(StmtRow) + nSql); | > > > > | 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | int argc, sqlite3_value **argv ){ stmt_cursor *pCur = (stmt_cursor *)pVtabCursor; sqlite3_stmt *p = 0; sqlite3_int64 iRowid = 1; StmtRow **ppRow = 0; (void)idxNum; (void)idxStr; (void)argc; (void)argv; stmtCsrReset(pCur); ppRow = &pCur->pRow; for(p=sqlite3_next_stmt(pCur->db, 0); p; p=sqlite3_next_stmt(pCur->db, p)){ const char *zSql = sqlite3_sql(p); sqlite3_int64 nSql = zSql ? strlen(zSql)+1 : 0; StmtRow *pNew = (StmtRow*)sqlite3_malloc64(sizeof(StmtRow) + nSql); |
︙ | ︙ | |||
267 268 269 270 271 272 273 274 275 276 277 278 279 280 | ** a query plan for each invocation and compute an estimated cost for that ** plan. */ static int stmtBestIndex( sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo ){ pIdxInfo->estimatedCost = (double)500; pIdxInfo->estimatedRows = 500; return SQLITE_OK; } /* ** This following structure defines all the methods for the | > | 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 | ** a query plan for each invocation and compute an estimated cost for that ** plan. */ static int stmtBestIndex( sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo ){ (void)tab; pIdxInfo->estimatedCost = (double)500; pIdxInfo->estimatedRows = 500; return SQLITE_OK; } /* ** This following structure defines all the methods for the |
︙ | ︙ |
Changes to ext/misc/zipfile.c.
︙ | ︙ | |||
348 349 350 351 352 353 354 355 356 357 358 359 360 361 | char **pzErr ){ int nByte = sizeof(ZipfileTab) + ZIPFILE_BUFFER_SIZE; int nFile = 0; const char *zFile = 0; ZipfileTab *pNew = 0; int rc; /* If the table name is not "zipfile", require that the argument be ** specified. This stops zipfile tables from being created as: ** ** CREATE VIRTUAL TABLE zzz USING zipfile(); ** ** It does not prevent: | > | 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 | char **pzErr ){ int nByte = sizeof(ZipfileTab) + ZIPFILE_BUFFER_SIZE; int nFile = 0; const char *zFile = 0; ZipfileTab *pNew = 0; int rc; (void)pAux; /* If the table name is not "zipfile", require that the argument be ** specified. This stops zipfile tables from being created as: ** ** CREATE VIRTUAL TABLE zzz USING zipfile(); ** ** It does not prevent: |
︙ | ︙ | |||
804 805 806 807 808 809 810 811 812 813 814 815 816 817 | FILE *pFile, /* If aBlob==0, read from this file */ i64 iOff, /* Offset of CDS record */ ZipfileEntry **ppEntry /* OUT: Pointer to new object */ ){ u8 *aRead; char **pzErr = &pTab->base.zErrMsg; int rc = SQLITE_OK; if( aBlob==0 ){ aRead = pTab->aBuffer; rc = zipfileReadData(pFile, aRead, ZIPFILE_CDS_FIXED_SZ, iOff, pzErr); }else{ aRead = (u8*)&aBlob[iOff]; } | > | 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 | FILE *pFile, /* If aBlob==0, read from this file */ i64 iOff, /* Offset of CDS record */ ZipfileEntry **ppEntry /* OUT: Pointer to new object */ ){ u8 *aRead; char **pzErr = &pTab->base.zErrMsg; int rc = SQLITE_OK; (void)nBlob; if( aBlob==0 ){ aRead = pTab->aBuffer; rc = zipfileReadData(pFile, aRead, ZIPFILE_CDS_FIXED_SZ, iOff, pzErr); }else{ aRead = (u8*)&aBlob[iOff]; } |
︙ | ︙ | |||
1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 | int argc, sqlite3_value **argv ){ ZipfileTab *pTab = (ZipfileTab*)cur->pVtab; ZipfileCsr *pCsr = (ZipfileCsr*)cur; const char *zFile = 0; /* Zip file to scan */ int rc = SQLITE_OK; /* Return Code */ int bInMemory = 0; /* True for an in-memory zipfile */ zipfileResetCursor(pCsr); if( pTab->zFile ){ zFile = pTab->zFile; }else if( idxNum==0 ){ zipfileCursorErr(pCsr, "zipfile() function requires an argument"); | > > > | 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 | int argc, sqlite3_value **argv ){ ZipfileTab *pTab = (ZipfileTab*)cur->pVtab; ZipfileCsr *pCsr = (ZipfileCsr*)cur; const char *zFile = 0; /* Zip file to scan */ int rc = SQLITE_OK; /* Return Code */ int bInMemory = 0; /* True for an in-memory zipfile */ (void)idxStr; (void)argc; zipfileResetCursor(pCsr); if( pTab->zFile ){ zFile = pTab->zFile; }else if( idxNum==0 ){ zipfileCursorErr(pCsr, "zipfile() function requires an argument"); |
︙ | ︙ | |||
1276 1277 1278 1279 1280 1281 1282 | if( rc!=SQLITE_OK ) return rc; bInMemory = 1; }else{ zFile = (const char*)sqlite3_value_text(argv[0]); } if( 0==pTab->pWriteFd && 0==bInMemory ){ | | | 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 | if( rc!=SQLITE_OK ) return rc; bInMemory = 1; }else{ zFile = (const char*)sqlite3_value_text(argv[0]); } if( 0==pTab->pWriteFd && 0==bInMemory ){ pCsr->pFile = fopen(zFile, "rb"); if( pCsr->pFile==0 ){ zipfileCursorErr(pCsr, "cannot open file: %s", zFile); rc = SQLITE_ERROR; }else{ rc = zipfileReadEOCD(pTab, 0, 0, pCsr->pFile, &pCsr->eocd); if( rc==SQLITE_OK ){ if( pCsr->eocd.nEntry==0 ){ |
︙ | ︙ | |||
1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 | static int zipfileBestIndex( sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo ){ int i; int idx = -1; int unusable = 0; for(i=0; i<pIdxInfo->nConstraint; i++){ const struct sqlite3_index_constraint *pCons = &pIdxInfo->aConstraint[i]; if( pCons->iColumn!=ZIPFILE_F_COLUMN_IDX ) continue; if( pCons->usable==0 ){ unusable = 1; }else if( pCons->op==SQLITE_INDEX_CONSTRAINT_EQ ){ | > | 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 | static int zipfileBestIndex( sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo ){ int i; int idx = -1; int unusable = 0; (void)tab; for(i=0; i<pIdxInfo->nConstraint; i++){ const struct sqlite3_index_constraint *pCons = &pIdxInfo->aConstraint[i]; if( pCons->iColumn!=ZIPFILE_F_COLUMN_IDX ) continue; if( pCons->usable==0 ){ unusable = 1; }else if( pCons->op==SQLITE_INDEX_CONSTRAINT_EQ ){ |
︙ | ︙ | |||
1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 | u8 *pFree = 0; /* Free this */ char *zFree = 0; /* Also free this */ ZipfileEntry *pOld = 0; ZipfileEntry *pOld2 = 0; int bUpdate = 0; /* True for an update that modifies "name" */ int bIsDir = 0; u32 iCrc32 = 0; if( pTab->pWriteFd==0 ){ rc = zipfileBegin(pVtab); if( rc!=SQLITE_OK ) return rc; } /* If this is a DELETE or UPDATE, find the archive entry to delete. */ | > > | 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 | u8 *pFree = 0; /* Free this */ char *zFree = 0; /* Also free this */ ZipfileEntry *pOld = 0; ZipfileEntry *pOld2 = 0; int bUpdate = 0; /* True for an update that modifies "name" */ int bIsDir = 0; u32 iCrc32 = 0; (void)pRowid; if( pTab->pWriteFd==0 ){ rc = zipfileBegin(pVtab); if( rc!=SQLITE_OK ) return rc; } /* If this is a DELETE or UPDATE, find the archive entry to delete. */ |
︙ | ︙ | |||
1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 | static int zipfileFindFunction( sqlite3_vtab *pVtab, /* Virtual table handle */ int nArg, /* Number of SQL function arguments */ const char *zName, /* Name of SQL function */ void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), /* OUT: Result */ void **ppArg /* OUT: User data for *pxFunc */ ){ if( sqlite3_stricmp("zipfile_cds", zName)==0 ){ *pxFunc = zipfileFunctionCds; *ppArg = (void*)pVtab; return 1; } return 0; } | > | 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 | static int zipfileFindFunction( sqlite3_vtab *pVtab, /* Virtual table handle */ int nArg, /* Number of SQL function arguments */ const char *zName, /* Name of SQL function */ void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), /* OUT: Result */ void **ppArg /* OUT: User data for *pxFunc */ ){ (void)nArg; if( sqlite3_stricmp("zipfile_cds", zName)==0 ){ *pxFunc = zipfileFunctionCds; *ppArg = (void*)pVtab; return 1; } return 0; } |
︙ | ︙ |
Changes to ext/rbu/rbuB.test.
︙ | ︙ | |||
44 45 46 47 48 49 50 | proc xLog {err msg} { lappend ::errlog $err } do_test 1.2 { run_rbu test.db rbu.db } {SQLITE_DONE} do_test 1.3 { set ::errlog | | | 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | proc xLog {err msg} { lappend ::errlog $err } do_test 1.2 { run_rbu test.db rbu.db } {SQLITE_DONE} do_test 1.3 { set ::errlog } {SQLITE_NOTICE_RECOVER_WAL SQLITE_NOTICE_RBU} do_execsql_test 1.4 { SELECT * FROM t1 } {1 2 3 4 5 6 7 8 9} db close sqlite3_shutdown |
︙ | ︙ |
Changes to ext/rbu/sqlite3rbu.c.
︙ | ︙ | |||
3027 3028 3029 3030 3031 3032 3033 | ** array. ** ** * Calls to xShmLock(UNLOCK) to release the exclusive shm WRITER, ** READ0 and CHECKPOINT locks taken as part of the checkpoint are ** no-ops. These locks will not be released until the connection ** is closed. ** | | | | | 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 | ** array. ** ** * Calls to xShmLock(UNLOCK) to release the exclusive shm WRITER, ** READ0 and CHECKPOINT locks taken as part of the checkpoint are ** no-ops. These locks will not be released until the connection ** is closed. ** ** * Attempting to xSync() the database file causes an SQLITE_NOTICE ** error. ** ** As a result, unless an error (i.e. OOM or SQLITE_BUSY) occurs, the ** checkpoint below fails with SQLITE_NOTICE, and leaves the aFrame[] ** array populated with a set of (frame -> page) mappings. Because the ** WRITER, CHECKPOINT and READ0 locks are still held, it is safe to copy ** data from the wal file into the database file according to the ** contents of aFrame[]. */ if( p->rc==SQLITE_OK ){ int rc2; p->eStage = RBU_STAGE_CAPTURE; rc2 = sqlite3_exec(p->dbMain, "PRAGMA main.wal_checkpoint=restart", 0, 0,0); if( rc2!=SQLITE_NOTICE ) p->rc = rc2; } if( p->rc==SQLITE_OK && p->nFrame>0 ){ p->eStage = RBU_STAGE_CKPT; p->nStep = (pState ? pState->nRow : 0); p->aBuf = rbuMalloc(p, p->pgsz); p->iWalCksum = rbuShmChecksum(p); |
︙ | ︙ | |||
3087 3088 3089 3090 3091 3092 3093 | */ static int rbuCaptureWalRead(sqlite3rbu *pRbu, i64 iOff, int iAmt){ const u32 mReq = (1<<WAL_LOCK_WRITE)|(1<<WAL_LOCK_CKPT)|(1<<WAL_LOCK_READ0); u32 iFrame; if( pRbu->mLock!=mReq ){ pRbu->rc = SQLITE_BUSY; | | | 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 | */ static int rbuCaptureWalRead(sqlite3rbu *pRbu, i64 iOff, int iAmt){ const u32 mReq = (1<<WAL_LOCK_WRITE)|(1<<WAL_LOCK_CKPT)|(1<<WAL_LOCK_READ0); u32 iFrame; if( pRbu->mLock!=mReq ){ pRbu->rc = SQLITE_BUSY; return SQLITE_NOTICE_RBU; } pRbu->pgsz = iAmt; if( pRbu->nFrame==pRbu->nFrameAlloc ){ int nNew = (pRbu->nFrameAlloc ? pRbu->nFrameAlloc : 64) * 2; RbuFrame *aNew; aNew = (RbuFrame*)sqlite3_realloc64(pRbu->aFrame, nNew * sizeof(RbuFrame)); |
︙ | ︙ | |||
4474 4475 4476 4477 4478 4479 4480 | ** all xWrite() calls on the target database file perform no IO. ** Instead the frame and page numbers that would be read and written ** are recorded. Additionally, successful attempts to obtain exclusive ** xShmLock() WRITER, CHECKPOINTER and READ0 locks on the target ** database file are recorded. xShmLock() calls to unlock the same ** locks are no-ops (so that once obtained, these locks are never ** relinquished). Finally, calls to xSync() on the target database | | | 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 | ** all xWrite() calls on the target database file perform no IO. ** Instead the frame and page numbers that would be read and written ** are recorded. Additionally, successful attempts to obtain exclusive ** xShmLock() WRITER, CHECKPOINTER and READ0 locks on the target ** database file are recorded. xShmLock() calls to unlock the same ** locks are no-ops (so that once obtained, these locks are never ** relinquished). Finally, calls to xSync() on the target database ** file fail with SQLITE_NOTICE errors. */ static void rbuUnlockShm(rbu_file *p){ assert( p->openFlags & SQLITE_OPEN_MAIN_DB ); if( p->pRbu ){ int (*xShmLock)(sqlite3_file*,int,int,int) = p->pReal->pMethods->xShmLock; int i; |
︙ | ︙ | |||
4753 4754 4755 4756 4757 4758 4759 | /* ** Sync an rbuVfs-file. */ static int rbuVfsSync(sqlite3_file *pFile, int flags){ rbu_file *p = (rbu_file *)pFile; if( p->pRbu && p->pRbu->eStage==RBU_STAGE_CAPTURE ){ if( p->openFlags & SQLITE_OPEN_MAIN_DB ){ | | | 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 | /* ** Sync an rbuVfs-file. */ static int rbuVfsSync(sqlite3_file *pFile, int flags){ rbu_file *p = (rbu_file *)pFile; if( p->pRbu && p->pRbu->eStage==RBU_STAGE_CAPTURE ){ if( p->openFlags & SQLITE_OPEN_MAIN_DB ){ return SQLITE_NOTICE_RBU; } return SQLITE_OK; } return p->pReal->pMethods->xSync(p->pReal, flags); } /* |
︙ | ︙ |
Changes to ext/recover/dbdata.c.
︙ | ︙ | |||
160 161 162 163 164 165 166 167 168 169 170 171 172 173 | int argc, const char *const*argv, sqlite3_vtab **ppVtab, char **pzErr ){ DbdataTable *pTab = 0; int rc = sqlite3_declare_vtab(db, pAux ? DBPTR_SCHEMA : DBDATA_SCHEMA); if( rc==SQLITE_OK ){ pTab = (DbdataTable*)sqlite3_malloc64(sizeof(DbdataTable)); if( pTab==0 ){ rc = SQLITE_NOMEM; }else{ memset(pTab, 0, sizeof(DbdataTable)); pTab->db = db; | > > > | 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | int argc, const char *const*argv, sqlite3_vtab **ppVtab, char **pzErr ){ DbdataTable *pTab = 0; int rc = sqlite3_declare_vtab(db, pAux ? DBPTR_SCHEMA : DBDATA_SCHEMA); (void)argc; (void)argv; (void)pzErr; if( rc==SQLITE_OK ){ pTab = (DbdataTable*)sqlite3_malloc64(sizeof(DbdataTable)); if( pTab==0 ){ rc = SQLITE_NOMEM; }else{ memset(pTab, 0, sizeof(DbdataTable)); pTab->db = db; |
︙ | ︙ | |||
764 765 766 767 768 769 770 771 772 773 774 775 776 777 | int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ DbdataCursor *pCsr = (DbdataCursor*)pCursor; DbdataTable *pTab = (DbdataTable*)pCursor->pVtab; int rc = SQLITE_OK; const char *zSchema = "main"; dbdataResetCursor(pCsr); assert( pCsr->iPgno==1 ); if( idxNum & 0x01 ){ zSchema = (const char*)sqlite3_value_text(argv[0]); if( zSchema==0 ) zSchema = ""; } | > > | 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 | int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ DbdataCursor *pCsr = (DbdataCursor*)pCursor; DbdataTable *pTab = (DbdataTable*)pCursor->pVtab; int rc = SQLITE_OK; const char *zSchema = "main"; (void)idxStr; (void)argc; dbdataResetCursor(pCsr); assert( pCsr->iPgno==1 ); if( idxNum & 0x01 ){ zSchema = (const char*)sqlite3_value_text(argv[0]); if( zSchema==0 ) zSchema = ""; } |
︙ | ︙ | |||
932 933 934 935 936 937 938 939 940 941 942 | #endif int sqlite3_dbdata_init( sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi ){ SQLITE_EXTENSION_INIT2(pApi); return sqlite3DbdataRegister(db); } #endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ | > | 937 938 939 940 941 942 943 944 945 946 947 948 | #endif int sqlite3_dbdata_init( sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi ){ SQLITE_EXTENSION_INIT2(pApi); (void)pzErrMsg; return sqlite3DbdataRegister(db); } #endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ |
Changes to ext/recover/recover1.test.
︙ | ︙ | |||
271 272 273 274 275 276 277 | do_execsql_test 15.1 { CREATE TABLE t1(x); } {} do_recover_test 15 #------------------------------------------------------------------------- reset_db | < | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | < | 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 | do_execsql_test 15.1 { CREATE TABLE t1(x); } {} do_recover_test 15 #------------------------------------------------------------------------- reset_db do_execsql_test 16.1 { PRAGMA journal_mode = wal; CREATE TABLE t1(x); INSERT INTO t1 VALUES(1), (2), (3); } {wal} do_test 16.2 { set R [sqlite3_recover_init db main test.db2] $R run $R finish } {} do_execsql_test 16.3 { SELECT * FROM t1; } {1 2 3} do_execsql_test 16.4 { BEGIN; SELECT * FROM t1; } {1 2 3} do_test 16.5 { set R [sqlite3_recover_init db main test.db2] $R run list [catch { $R finish } msg] $msg } {1 {cannot start a transaction within a transaction}} do_execsql_test 16.6 { SELECT * FROM t1; } {1 2 3} do_execsql_test 16.7 { INSERT INTO t1 VALUES(4); } do_test 16.8 { set R [sqlite3_recover_init db main test.db2] $R run list [catch { $R finish } msg] $msg } {1 {cannot start a transaction within a transaction}} do_execsql_test 16.9 { SELECT * FROM t1; COMMIT; } {1 2 3 4} finish_test |
Changes to ext/recover/sqlite3recover.c.
︙ | ︙ | |||
757 758 759 760 761 762 763 764 765 766 767 768 769 770 | */ static void recoverEscapeCrnl( sqlite3_context *context, int argc, sqlite3_value **argv ){ const char *zText = (const char*)sqlite3_value_text(argv[0]); if( zText && zText[0]=='\'' ){ int nText = sqlite3_value_bytes(argv[0]); int i; char zBuf1[20]; char zBuf2[20]; const char *zNL = 0; const char *zCR = 0; | > | 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 | */ static void recoverEscapeCrnl( sqlite3_context *context, int argc, sqlite3_value **argv ){ const char *zText = (const char*)sqlite3_value_text(argv[0]); (void)argc; if( zText && zText[0]=='\'' ){ int nText = sqlite3_value_bytes(argv[0]); int i; char zBuf1[20]; char zBuf2[20]; const char *zNL = 0; const char *zCR = 0; |
︙ | ︙ | |||
909 910 911 912 913 914 915 | sqlite3 *db2 = 0; int rc = sqlite3_open("", &db2); if( rc!=SQLITE_OK ){ recoverDbError(p, db2); return; } | | | 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 | sqlite3 *db2 = 0; int rc = sqlite3_open("", &db2); if( rc!=SQLITE_OK ){ recoverDbError(p, db2); return; } for(ii=0; ii<(int)(sizeof(aPragma)/sizeof(aPragma[0])); ii++){ const char *zPrag = aPragma[ii]; sqlite3_stmt *p1 = 0; p1 = recoverPreparePrintf(p, p->dbIn, "PRAGMA %Q.%s", p->zDb, zPrag); if( p->errCode==SQLITE_OK && sqlite3_step(p1)==SQLITE_ROW ){ const char *zArg = (const char*)sqlite3_column_text(p1, 0); char *z2 = recoverMPrintf(p, "PRAGMA %s = %Q", zPrag, zArg); recoverSqlCallback(p, z2); |
︙ | ︙ | |||
987 988 989 990 991 992 993 | ** module depends on the input handle supporting the sqlite_dbpage ** virtual table only. */ if( p->errCode==SQLITE_OK ){ p->errCode = sqlite3_dbdata_init(db, 0, 0); } /* Register the custom user-functions with the output handle. */ | > | > | 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 | ** module depends on the input handle supporting the sqlite_dbpage ** virtual table only. */ if( p->errCode==SQLITE_OK ){ p->errCode = sqlite3_dbdata_init(db, 0, 0); } /* Register the custom user-functions with the output handle. */ for(ii=0; p->errCode==SQLITE_OK && ii<(int)(sizeof(aFunc)/sizeof(aFunc[0])); ii++){ p->errCode = sqlite3_create_function(db, aFunc[ii].zName, aFunc[ii].nArg, SQLITE_UTF8, (void*)p, aFunc[ii].xFunc, 0, 0 ); } p->dbOut = db; return p->errCode; |
︙ | ︙ | |||
2384 2385 2386 2387 2388 2389 2390 | aHdr[19] = a[19]; recoverPutU32(&aHdr[28], dbsz); recoverPutU32(&aHdr[56], enc); recoverPutU16(&aHdr[105], pgsz-nReserve); if( pgsz==65536 ) pgsz = 1; recoverPutU16(&aHdr[16], pgsz); aHdr[20] = nReserve; | | | 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 | aHdr[19] = a[19]; recoverPutU32(&aHdr[28], dbsz); recoverPutU32(&aHdr[56], enc); recoverPutU16(&aHdr[105], pgsz-nReserve); if( pgsz==65536 ) pgsz = 1; recoverPutU16(&aHdr[16], pgsz); aHdr[20] = nReserve; for(ii=0; ii<(int)(sizeof(aPreserve)/sizeof(aPreserve[0])); ii++){ memcpy(&aHdr[aPreserve[ii]], &a[aPreserve[ii]], 4); } memcpy(aBuf, aHdr, sizeof(aHdr)); memset(&((u8*)aBuf)[sizeof(aHdr)], 0, nByte-sizeof(aHdr)); memcpy(p->pPage1Cache, aBuf, nByte); }else{ |
︙ | ︙ | |||
2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 | static int recoverVfsFetch( sqlite3_file *pFd, sqlite3_int64 iOff, int iAmt, void **pp ){ *pp = 0; return SQLITE_OK; } static int recoverVfsUnfetch(sqlite3_file *pFd, sqlite3_int64 iOff, void *p){ return SQLITE_OK; } /* ** Install the VFS wrapper around the file-descriptor open on the input ** database for recover handle p. Mutex RECOVER_MUTEX_ID must be held ** when this function is called. | > > > > > > | 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 | static int recoverVfsFetch( sqlite3_file *pFd, sqlite3_int64 iOff, int iAmt, void **pp ){ (void)pFd; (void)iOff; (void)iAmt; *pp = 0; return SQLITE_OK; } static int recoverVfsUnfetch(sqlite3_file *pFd, sqlite3_int64 iOff, void *p){ (void)pFd; (void)iOff; (void)p; return SQLITE_OK; } /* ** Install the VFS wrapper around the file-descriptor open on the input ** database for recover handle p. Mutex RECOVER_MUTEX_ID must be held ** when this function is called. |
︙ | ︙ |
Changes to ext/rtree/geopoly.c.
︙ | ︙ | |||
300 301 302 303 304 305 306 | sqlite3_value *pVal, /* The value to decode */ int *pRc /* Write error here */ ){ GeoPoly *p = 0; int nByte; testcase( pCtx==0 ); if( sqlite3_value_type(pVal)==SQLITE_BLOB | | | 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 | sqlite3_value *pVal, /* The value to decode */ int *pRc /* Write error here */ ){ GeoPoly *p = 0; int nByte; testcase( pCtx==0 ); if( sqlite3_value_type(pVal)==SQLITE_BLOB && (nByte = sqlite3_value_bytes(pVal))>=(int)(4+6*sizeof(GeoCoord)) ){ const unsigned char *a = sqlite3_value_blob(pVal); int nVertex; if( a==0 ){ if( pCtx ) sqlite3_result_error_nomem(pCtx); return 0; } |
︙ | ︙ | |||
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 | */ static void geopolyBlobFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ GeoPoly *p = geopolyFuncParam(context, argv[0], 0); if( p ){ sqlite3_result_blob(context, p->hdr, 4+8*p->nVertex, SQLITE_TRANSIENT); sqlite3_free(p); } } /* ** SQL function: geopoly_json(X) ** ** Interpret X as a polygon and render it as a JSON array ** of coordinates. Or, if X is not a valid polygon, return NULL. */ static void geopolyJsonFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ GeoPoly *p = geopolyFuncParam(context, argv[0], 0); if( p ){ sqlite3 *db = sqlite3_context_db_handle(context); sqlite3_str *x = sqlite3_str_new(db); int i; sqlite3_str_append(x, "[", 1); for(i=0; i<p->nVertex; i++){ sqlite3_str_appendf(x, "[%!g,%!g],", GeoX(p,i), GeoY(p,i)); | > > | 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 | */ static void geopolyBlobFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ GeoPoly *p = geopolyFuncParam(context, argv[0], 0); (void)argc; if( p ){ sqlite3_result_blob(context, p->hdr, 4+8*p->nVertex, SQLITE_TRANSIENT); sqlite3_free(p); } } /* ** SQL function: geopoly_json(X) ** ** Interpret X as a polygon and render it as a JSON array ** of coordinates. Or, if X is not a valid polygon, return NULL. */ static void geopolyJsonFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ GeoPoly *p = geopolyFuncParam(context, argv[0], 0); (void)argc; if( p ){ sqlite3 *db = sqlite3_context_db_handle(context); sqlite3_str *x = sqlite3_str_new(db); int i; sqlite3_str_append(x, "[", 1); for(i=0; i<p->nVertex; i++){ sqlite3_str_appendf(x, "[%!g,%!g],", GeoX(p,i), GeoY(p,i)); |
︙ | ︙ | |||
458 459 460 461 462 463 464 465 466 467 468 469 470 471 | double B = sqlite3_value_double(argv[2]); double C = sqlite3_value_double(argv[3]); double D = sqlite3_value_double(argv[4]); double E = sqlite3_value_double(argv[5]); double F = sqlite3_value_double(argv[6]); GeoCoord x1, y1, x0, y0; int ii; if( p ){ for(ii=0; ii<p->nVertex; ii++){ x0 = GeoX(p,ii); y0 = GeoY(p,ii); x1 = (GeoCoord)(A*x0 + B*y0 + E); y1 = (GeoCoord)(C*x0 + D*y0 + F); GeoX(p,ii) = x1; | > | 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 | double B = sqlite3_value_double(argv[2]); double C = sqlite3_value_double(argv[3]); double D = sqlite3_value_double(argv[4]); double E = sqlite3_value_double(argv[5]); double F = sqlite3_value_double(argv[6]); GeoCoord x1, y1, x0, y0; int ii; (void)argc; if( p ){ for(ii=0; ii<p->nVertex; ii++){ x0 = GeoX(p,ii); y0 = GeoY(p,ii); x1 = (GeoCoord)(A*x0 + B*y0 + E); y1 = (GeoCoord)(C*x0 + D*y0 + F); GeoX(p,ii) = x1; |
︙ | ︙ | |||
508 509 510 511 512 513 514 515 516 517 518 519 520 521 | */ static void geopolyAreaFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ GeoPoly *p = geopolyFuncParam(context, argv[0], 0); if( p ){ sqlite3_result_double(context, geopolyArea(p)); sqlite3_free(p); } } /* | > | 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 | */ static void geopolyAreaFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ GeoPoly *p = geopolyFuncParam(context, argv[0], 0); (void)argc; if( p ){ sqlite3_result_double(context, geopolyArea(p)); sqlite3_free(p); } } /* |
︙ | ︙ | |||
533 534 535 536 537 538 539 540 541 542 543 544 545 546 | */ static void geopolyCcwFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ GeoPoly *p = geopolyFuncParam(context, argv[0], 0); if( p ){ if( geopolyArea(p)<0.0 ){ int ii, jj; for(ii=1, jj=p->nVertex-1; ii<jj; ii++, jj--){ GeoCoord t = GeoX(p,ii); GeoX(p,ii) = GeoX(p,jj); GeoX(p,jj) = t; | > | 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 | */ static void geopolyCcwFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ GeoPoly *p = geopolyFuncParam(context, argv[0], 0); (void)argc; if( p ){ if( geopolyArea(p)<0.0 ){ int ii, jj; for(ii=1, jj=p->nVertex-1; ii<jj; ii++, jj--){ GeoCoord t = GeoX(p,ii); GeoX(p,ii) = GeoX(p,jj); GeoX(p,jj) = t; |
︙ | ︙ | |||
587 588 589 590 591 592 593 594 595 596 597 598 599 600 | ){ double x = sqlite3_value_double(argv[0]); double y = sqlite3_value_double(argv[1]); double r = sqlite3_value_double(argv[2]); int n = sqlite3_value_int(argv[3]); int i; GeoPoly *p; if( n<3 || r<=0.0 ) return; if( n>1000 ) n = 1000; p = sqlite3_malloc64( sizeof(*p) + (n-1)*2*sizeof(GeoCoord) ); if( p==0 ){ sqlite3_result_error_nomem(context); return; | > | 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 | ){ double x = sqlite3_value_double(argv[0]); double y = sqlite3_value_double(argv[1]); double r = sqlite3_value_double(argv[2]); int n = sqlite3_value_int(argv[3]); int i; GeoPoly *p; (void)argc; if( n<3 || r<=0.0 ) return; if( n>1000 ) n = 1000; p = sqlite3_malloc64( sizeof(*p) + (n-1)*2*sizeof(GeoCoord) ); if( p==0 ){ sqlite3_result_error_nomem(context); return; |
︙ | ︙ | |||
696 697 698 699 700 701 702 703 704 705 706 707 708 709 | */ static void geopolyBBoxFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ GeoPoly *p = geopolyBBox(context, argv[0], 0, 0); if( p ){ sqlite3_result_blob(context, p->hdr, 4+8*p->nVertex, SQLITE_TRANSIENT); sqlite3_free(p); } } | > | 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 | */ static void geopolyBBoxFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ GeoPoly *p = geopolyBBox(context, argv[0], 0, 0); (void)argc; if( p ){ sqlite3_result_blob(context, p->hdr, 4+8*p->nVertex, SQLITE_TRANSIENT); sqlite3_free(p); } } |
︙ | ︙ | |||
723 724 725 726 727 728 729 730 731 732 733 734 735 736 | static void geopolyBBoxStep( sqlite3_context *context, int argc, sqlite3_value **argv ){ RtreeCoord a[4]; int rc = SQLITE_OK; (void)geopolyBBox(context, argv[0], a, &rc); if( rc==SQLITE_OK ){ GeoBBox *pBBox; pBBox = (GeoBBox*)sqlite3_aggregate_context(context, sizeof(*pBBox)); if( pBBox==0 ) return; if( pBBox->isInit==0 ){ pBBox->isInit = 1; | > | 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 | static void geopolyBBoxStep( sqlite3_context *context, int argc, sqlite3_value **argv ){ RtreeCoord a[4]; int rc = SQLITE_OK; (void)argc; (void)geopolyBBox(context, argv[0], a, &rc); if( rc==SQLITE_OK ){ GeoBBox *pBBox; pBBox = (GeoBBox*)sqlite3_aggregate_context(context, sizeof(*pBBox)); if( pBBox==0 ) return; if( pBBox->isInit==0 ){ pBBox->isInit = 1; |
︙ | ︙ | |||
811 812 813 814 815 816 817 818 819 820 821 822 823 824 | ){ GeoPoly *p1 = geopolyFuncParam(context, argv[0], 0); double x0 = sqlite3_value_double(argv[1]); double y0 = sqlite3_value_double(argv[2]); int v = 0; int cnt = 0; int ii; if( p1==0 ) return; for(ii=0; ii<p1->nVertex-1; ii++){ v = pointBeneathLine(x0,y0,GeoX(p1,ii), GeoY(p1,ii), GeoX(p1,ii+1),GeoY(p1,ii+1)); if( v==2 ) break; cnt += v; } | > > | 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 | ){ GeoPoly *p1 = geopolyFuncParam(context, argv[0], 0); double x0 = sqlite3_value_double(argv[1]); double y0 = sqlite3_value_double(argv[2]); int v = 0; int cnt = 0; int ii; (void)argc; if( p1==0 ) return; for(ii=0; ii<p1->nVertex-1; ii++){ v = pointBeneathLine(x0,y0,GeoX(p1,ii), GeoY(p1,ii), GeoX(p1,ii+1),GeoY(p1,ii+1)); if( v==2 ) break; cnt += v; } |
︙ | ︙ | |||
850 851 852 853 854 855 856 857 858 859 860 861 862 863 | static void geopolyWithinFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ GeoPoly *p1 = geopolyFuncParam(context, argv[0], 0); GeoPoly *p2 = geopolyFuncParam(context, argv[1], 0); if( p1 && p2 ){ int x = geopolyOverlap(p1, p2); if( x<0 ){ sqlite3_result_error_nomem(context); }else{ sqlite3_result_int(context, x==2 ? 1 : x==4 ? 2 : 0); } | > | 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 | static void geopolyWithinFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ GeoPoly *p1 = geopolyFuncParam(context, argv[0], 0); GeoPoly *p2 = geopolyFuncParam(context, argv[1], 0); (void)argc; if( p1 && p2 ){ int x = geopolyOverlap(p1, p2); if( x<0 ){ sqlite3_result_error_nomem(context); }else{ sqlite3_result_int(context, x==2 ? 1 : x==4 ? 2 : 0); } |
︙ | ︙ | |||
1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 | static void geopolyOverlapFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ GeoPoly *p1 = geopolyFuncParam(context, argv[0], 0); GeoPoly *p2 = geopolyFuncParam(context, argv[1], 0); if( p1 && p2 ){ int x = geopolyOverlap(p1, p2); if( x<0 ){ sqlite3_result_error_nomem(context); }else{ sqlite3_result_int(context, x); } } sqlite3_free(p1); sqlite3_free(p2); } /* ** Enable or disable debugging output */ static void geopolyDebugFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ #ifdef GEOPOLY_ENABLE_DEBUG geo_debug = sqlite3_value_int(argv[0]); #endif } /* ** This function is the implementation of both the xConnect and xCreate ** methods of the geopoly virtual table. ** | > > > > > | 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 | static void geopolyOverlapFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ GeoPoly *p1 = geopolyFuncParam(context, argv[0], 0); GeoPoly *p2 = geopolyFuncParam(context, argv[1], 0); (void)argc; if( p1 && p2 ){ int x = geopolyOverlap(p1, p2); if( x<0 ){ sqlite3_result_error_nomem(context); }else{ sqlite3_result_int(context, x); } } sqlite3_free(p1); sqlite3_free(p2); } /* ** Enable or disable debugging output */ static void geopolyDebugFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ (void)context; (void)argc; #ifdef GEOPOLY_ENABLE_DEBUG geo_debug = sqlite3_value_int(argv[0]); #else (void)argv; #endif } /* ** This function is the implementation of both the xConnect and xCreate ** methods of the geopoly virtual table. ** |
︙ | ︙ | |||
1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 | int rc = SQLITE_OK; Rtree *pRtree; sqlite3_int64 nDb; /* Length of string argv[1] */ sqlite3_int64 nName; /* Length of string argv[2] */ sqlite3_str *pSql; char *zSql; int ii; sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1); /* Allocate the sqlite3_vtab structure */ nDb = strlen(argv[1]); nName = strlen(argv[2]); pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName+2); | > | 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 | int rc = SQLITE_OK; Rtree *pRtree; sqlite3_int64 nDb; /* Length of string argv[1] */ sqlite3_int64 nName; /* Length of string argv[2] */ sqlite3_str *pSql; char *zSql; int ii; (void)pAux; sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1); /* Allocate the sqlite3_vtab structure */ nDb = strlen(argv[1]); nName = strlen(argv[2]); pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName+2); |
︙ | ︙ | |||
1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 | int argc, sqlite3_value **argv /* Parameters to the query plan */ ){ Rtree *pRtree = (Rtree *)pVtabCursor->pVtab; RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor; RtreeNode *pRoot = 0; int rc = SQLITE_OK; int iCell = 0; rtreeReference(pRtree); /* Reset the cursor to the same state as rtreeOpen() leaves it in. */ resetCursor(pCsr); pCsr->iStrategy = idxNum; | > | 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 | int argc, sqlite3_value **argv /* Parameters to the query plan */ ){ Rtree *pRtree = (Rtree *)pVtabCursor->pVtab; RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor; RtreeNode *pRoot = 0; int rc = SQLITE_OK; int iCell = 0; (void)idxStr; rtreeReference(pRtree); /* Reset the cursor to the same state as rtreeOpen() leaves it in. */ resetCursor(pCsr); pCsr->iStrategy = idxNum; |
︙ | ︙ | |||
1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 | ** ------------------------------------------------ */ static int geopolyBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ int ii; int iRowidTerm = -1; int iFuncTerm = -1; int idxNum = 0; for(ii=0; ii<pIdxInfo->nConstraint; ii++){ struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii]; if( !p->usable ) continue; if( p->iColumn<0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ ){ iRowidTerm = ii; break; | > | 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 | ** ------------------------------------------------ */ static int geopolyBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ int ii; int iRowidTerm = -1; int iFuncTerm = -1; int idxNum = 0; (void)tab; for(ii=0; ii<pIdxInfo->nConstraint; ii++){ struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii]; if( !p->usable ) continue; if( p->iColumn<0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ ){ iRowidTerm = ii; break; |
︙ | ︙ | |||
1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 | static int geopolyFindFunction( sqlite3_vtab *pVtab, int nArg, const char *zName, void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), void **ppArg ){ if( sqlite3_stricmp(zName, "geopoly_overlap")==0 ){ *pxFunc = geopolyOverlapFunc; *ppArg = 0; return SQLITE_INDEX_CONSTRAINT_FUNCTION; } if( sqlite3_stricmp(zName, "geopoly_within")==0 ){ *pxFunc = geopolyWithinFunc; | > > | 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 | static int geopolyFindFunction( sqlite3_vtab *pVtab, int nArg, const char *zName, void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), void **ppArg ){ (void)pVtab; (void)nArg; if( sqlite3_stricmp(zName, "geopoly_overlap")==0 ){ *pxFunc = geopolyOverlapFunc; *ppArg = 0; return SQLITE_INDEX_CONSTRAINT_FUNCTION; } if( sqlite3_stricmp(zName, "geopoly_within")==0 ){ *pxFunc = geopolyWithinFunc; |
︙ | ︙ | |||
1786 1787 1788 1789 1790 1791 1792 | static const struct { void (*xStep)(sqlite3_context*,int,sqlite3_value**); void (*xFinal)(sqlite3_context*); const char *zName; } aAgg[] = { { geopolyBBoxStep, geopolyBBoxFinal, "geopoly_group_bbox" }, }; | | | 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 | static const struct { void (*xStep)(sqlite3_context*,int,sqlite3_value**); void (*xFinal)(sqlite3_context*); const char *zName; } aAgg[] = { { geopolyBBoxStep, geopolyBBoxFinal, "geopoly_group_bbox" }, }; unsigned int i; for(i=0; i<sizeof(aFunc)/sizeof(aFunc[0]) && rc==SQLITE_OK; i++){ int enc; if( aFunc[i].bPure ){ enc = SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS; }else{ enc = SQLITE_UTF8|SQLITE_DIRECTONLY; } |
︙ | ︙ |
Changes to ext/rtree/rtree.c.
︙ | ︙ | |||
497 498 499 500 501 502 503 | ** Functions to deserialize a 16 bit integer, 32 bit real number and ** 64 bit integer. The deserialized value is returned. */ static int readInt16(u8 *p){ return (p[0]<<8) + p[1]; } static void readCoord(u8 *p, RtreeCoord *pCoord){ | | | 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 | ** Functions to deserialize a 16 bit integer, 32 bit real number and ** 64 bit integer. The deserialized value is returned. */ static int readInt16(u8 *p){ return (p[0]<<8) + p[1]; } static void readCoord(u8 *p, RtreeCoord *pCoord){ assert( (((sqlite3_uint64)p)&3)==0 ); /* p is always 4-byte aligned */ #if SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 pCoord->u = _byteswap_ulong(*(u32*)p); #elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 pCoord->u = __builtin_bswap32(*(u32*)p); #elif SQLITE_BYTEORDER==4321 pCoord->u = *(u32*)p; #else |
︙ | ︙ | |||
551 552 553 554 555 556 557 | */ static void writeInt16(u8 *p, int i){ p[0] = (i>> 8)&0xFF; p[1] = (i>> 0)&0xFF; } static int writeCoord(u8 *p, RtreeCoord *pCoord){ u32 i; | | | 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 | */ static void writeInt16(u8 *p, int i){ p[0] = (i>> 8)&0xFF; p[1] = (i>> 0)&0xFF; } static int writeCoord(u8 *p, RtreeCoord *pCoord){ u32 i; assert( (((sqlite3_uint64)p)&3)==0 ); /* p is always 4-byte aligned */ assert( sizeof(RtreeCoord)==4 ); assert( sizeof(u32)==4 ); #if SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 i = __builtin_bswap32(pCoord->u); memcpy(p, &i, 4); #elif SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 i = _byteswap_ulong(pCoord->u); |
︙ | ︙ | |||
1279 1280 1281 1282 1283 1284 1285 | ** in a coordinate pair. But make pCellData point to the lower bound. */ pCellData += 8 + 4*(p->iCoord&0xfe); assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE || p->op==RTREE_FALSE ); | | | 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 | ** in a coordinate pair. But make pCellData point to the lower bound. */ pCellData += 8 + 4*(p->iCoord&0xfe); assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE || p->op==RTREE_FALSE ); assert( (((sqlite3_uint64)pCellData)&3)==0 ); /* 4-byte aligned */ switch( p->op ){ case RTREE_TRUE: return; /* Always satisfied */ case RTREE_FALSE: break; /* Never satisfied */ case RTREE_EQ: RTREE_DECODE_COORD(eInt, pCellData, val); /* val now holds the lower bound of the coordinate pair */ if( p->u.rValue>=val ){ |
︙ | ︙ | |||
1332 1333 1334 1335 1336 1337 1338 | ){ RtreeDValue xN; /* Coordinate value converted to a double */ assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE || p->op==RTREE_FALSE ); pCellData += 8 + p->iCoord*4; | | | 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 | ){ RtreeDValue xN; /* Coordinate value converted to a double */ assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE || p->op==RTREE_FALSE ); pCellData += 8 + p->iCoord*4; assert( (((sqlite3_uint64)pCellData)&3)==0 ); /* 4-byte aligned */ RTREE_DECODE_COORD(eInt, pCellData, xN); switch( p->op ){ case RTREE_TRUE: return; /* Always satisfied */ case RTREE_FALSE: break; /* Never satisfied */ case RTREE_LE: if( xN <= p->u.rValue ) return; break; case RTREE_LT: if( xN < p->u.rValue ) return; break; case RTREE_GE: if( xN >= p->u.rValue ) return; break; |
︙ | ︙ |
Changes to ext/session/sessionat.test.
︙ | ︙ | |||
239 240 241 242 243 244 245 246 247 248 249 250 251 252 | } proc xConfict {args} { return "OMIT" } do_test $tn.6.3 { sqlite3changeset_apply db $cinv xConflict execsql { SELECT * FROM t7 } } {1 1 ccc 2 2 ccc 3 3 ccc} }] } catch { db close } catch { db2 close } sqlite3_shutdown test_sqlite3_log | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 | } proc xConfict {args} { return "OMIT" } do_test $tn.6.3 { sqlite3changeset_apply db $cinv xConflict execsql { SELECT * FROM t7 } } {1 1 ccc 2 2 ccc 3 3 ccc} #----------------------------------------------------------------------- reset_test do_execsql_test $tn.7.0 { CREATE TABLE t8(a PRIMARY KEY, b, c); } do_execsql_test -db db2 $tn.7.1 { CREATE TABLE t8(a PRIMARY KEY, b, c, d DEFAULT 'D', e DEFAULT 'E'); } do_then_apply_sql { INSERT INTO t8 VALUES(1, 2, 3); INSERT INTO t8 VALUES(4, 5, 6); } do_execsql_test $tn.7.2.1 { SELECT * FROM t8 } {1 2 3 4 5 6} do_execsql_test -db db2 $tn.7.2.2 { SELECT * FROM t8 } {1 2 3 D E 4 5 6 D E} do_then_apply_sql { UPDATE t8 SET c=45 WHERE a=4; } do_execsql_test $tn.7.3.1 { SELECT * FROM t8 } {1 2 3 4 5 45} do_execsql_test -db db2 $tn.7.3.2 { SELECT * FROM t8 } {1 2 3 D E 4 5 45 D E} #----------------------------------------------------------------------- reset_test do_execsql_test $tn.8.0 { CREATE TABLE t9(a PRIMARY KEY, b, c, d, e, f, g, h); } do_execsql_test -db db2 $tn.8.1 { CREATE TABLE t9(a PRIMARY KEY, b, c, d, e, f, g, h, i, j, k, l); } do_then_apply_sql { INSERT INTO t9 VALUES(1, 2, 3, 4, 5, 6, 7, 8); } do_then_apply_sql { UPDATE t9 SET h=450 WHERE a=1 } do_execsql_test -db db2 $tn.8.2 { SELECT * FROM t9 } {1 2 3 4 5 6 7 450 {} {} {} {}} do_then_apply_sql { UPDATE t9 SET h=NULL } do_execsql_test -db db2 $tn.8.2 { SELECT * FROM t9 } {1 2 3 4 5 6 7 {} {} {} {} {}} }] } catch { db close } catch { db2 close } sqlite3_shutdown test_sqlite3_log |
︙ | ︙ |
Changes to ext/session/sqlite3session.c.
︙ | ︙ | |||
1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 | sqlite3_int64 iKey1, /* Rowid of row about to be deleted/updated */ sqlite3_int64 iKey2 /* New rowid value (for a rowid UPDATE) */ ){ sqlite3_session *pSession; int nDb = sqlite3Strlen30(zDb); assert( sqlite3_mutex_held(db->mutex) ); for(pSession=(sqlite3_session *)pCtx; pSession; pSession=pSession->pNext){ SessionTable *pTab; /* If this session is attached to a different database ("main", "temp" ** etc.), or if it is not currently enabled, there is nothing to do. Skip ** to the next session object attached to this database. */ | > > | 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 | sqlite3_int64 iKey1, /* Rowid of row about to be deleted/updated */ sqlite3_int64 iKey2 /* New rowid value (for a rowid UPDATE) */ ){ sqlite3_session *pSession; int nDb = sqlite3Strlen30(zDb); assert( sqlite3_mutex_held(db->mutex) ); (void)iKey1; (void)iKey2; for(pSession=(sqlite3_session *)pCtx; pSession; pSession=pSession->pNext){ SessionTable *pTab; /* If this session is attached to a different database ("main", "temp" ** etc.), or if it is not currently enabled, there is nothing to do. Skip ** to the next session object attached to this database. */ |
︙ | ︙ | |||
1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 | return SQLITE_OK; } static int sessionDiffCount(void *pCtx){ SessionDiffCtx *p = (SessionDiffCtx*)pCtx; return p->nOldOff ? p->nOldOff : sqlite3_column_count(p->pStmt); } static int sessionDiffDepth(void *pCtx){ return 0; } /* ** Install the diff hooks on the session object passed as the only ** argument. */ | > | 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 | return SQLITE_OK; } static int sessionDiffCount(void *pCtx){ SessionDiffCtx *p = (SessionDiffCtx*)pCtx; return p->nOldOff ? p->nOldOff : sqlite3_column_count(p->pStmt); } static int sessionDiffDepth(void *pCtx){ (void)pCtx; return 0; } /* ** Install the diff hooks on the session object passed as the only ** argument. */ |
︙ | ︙ | |||
1644 1645 1646 1647 1648 1649 1650 | zRet = sqlite3_mprintf("0"); } return zRet; } static char *sessionSelectFindNew( | < | 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 | zRet = sqlite3_mprintf("0"); } return zRet; } static char *sessionSelectFindNew( const char *zDb1, /* Pick rows in this db only */ const char *zDb2, /* But not in this one */ const char *zTbl, /* Table name */ const char *zExpr ){ char *zRet = sqlite3_mprintf( "SELECT * FROM \"%w\".\"%w\" WHERE NOT EXISTS (" |
︙ | ︙ | |||
1668 1669 1670 1671 1672 1673 1674 | sqlite3_session *pSession, SessionTable *pTab, const char *zDb1, const char *zDb2, char *zExpr ){ int rc = SQLITE_OK; | | | 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 | sqlite3_session *pSession, SessionTable *pTab, const char *zDb1, const char *zDb2, char *zExpr ){ int rc = SQLITE_OK; char *zStmt = sessionSelectFindNew(zDb1, zDb2, pTab->zName,zExpr); if( zStmt==0 ){ rc = SQLITE_NOMEM; }else{ sqlite3_stmt *pStmt; rc = sqlite3_prepare(pSession->db, zStmt, -1, &pStmt, 0); if( rc==SQLITE_OK ){ |
︙ | ︙ | |||
4185 4186 4187 4188 4189 4190 4191 | ** not require a reset(). ** ** If the iterator currently points to an INSERT record, bind values from the ** new.* record to the SELECT statement. Or, if it points to a DELETE or ** UPDATE, bind values from the old.* record. */ static int sessionSeekToRow( | < | 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 | ** not require a reset(). ** ** If the iterator currently points to an INSERT record, bind values from the ** new.* record to the SELECT statement. Or, if it points to a DELETE or ** UPDATE, bind values from the old.* record. */ static int sessionSeekToRow( sqlite3_changeset_iter *pIter, /* Changeset iterator */ u8 *abPK, /* Primary key flags array */ sqlite3_stmt *pSelect /* SELECT statement from sessionSelectRow() */ ){ int rc; /* Return code */ int nCol; /* Number of columns in table */ int op; /* Changset operation (SQLITE_UPDATE etc.) */ |
︙ | ︙ | |||
4315 4316 4317 4318 4319 4320 4321 | assert( eType==SQLITE_CHANGESET_CONFLICT || eType==SQLITE_CHANGESET_DATA ); assert( SQLITE_CHANGESET_CONFLICT+1==SQLITE_CHANGESET_CONSTRAINT ); assert( SQLITE_CHANGESET_DATA+1==SQLITE_CHANGESET_NOTFOUND ); /* Bind the new.* PRIMARY KEY values to the SELECT statement. */ if( pbReplace ){ | | | 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 | assert( eType==SQLITE_CHANGESET_CONFLICT || eType==SQLITE_CHANGESET_DATA ); assert( SQLITE_CHANGESET_CONFLICT+1==SQLITE_CHANGESET_CONSTRAINT ); assert( SQLITE_CHANGESET_DATA+1==SQLITE_CHANGESET_NOTFOUND ); /* Bind the new.* PRIMARY KEY values to the SELECT statement. */ if( pbReplace ){ rc = sessionSeekToRow(pIter, p->abPK, p->pSelect); }else{ rc = SQLITE_OK; } if( rc==SQLITE_ROW ){ /* There exists another row with the new.* primary key. */ pIter->pConflict = p->pSelect; |
︙ | ︙ | |||
4489 4490 4491 4492 4493 4494 4495 | }else{ assert( op==SQLITE_INSERT ); if( p->bStat1 ){ /* Check if there is a conflicting row. For sqlite_stat1, this needs ** to be done using a SELECT, as there is no PRIMARY KEY in the ** database schema to throw an exception if a duplicate is inserted. */ | | | 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 | }else{ assert( op==SQLITE_INSERT ); if( p->bStat1 ){ /* Check if there is a conflicting row. For sqlite_stat1, this needs ** to be done using a SELECT, as there is no PRIMARY KEY in the ** database schema to throw an exception if a duplicate is inserted. */ rc = sessionSeekToRow(pIter, p->abPK, p->pSelect); if( rc==SQLITE_ROW ){ rc = SQLITE_CONSTRAINT; sqlite3_reset(p->pSelect); } } if( rc==SQLITE_OK ){ |
︙ | ︙ |
Changes to ext/session/test_session.c.
︙ | ︙ | |||
1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 | (ClientData)pNew, test_rebaser_del ); Tcl_SetObjResult(interp, objv[1]); return TCL_OK; } /* ** */ static int sqlite3_test_changeset( int nChangeset, void *pChangeset, char **pzErr ){ sqlite3_changeset_iter *pIter = 0; | > > > > > > > > | 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 | (ClientData)pNew, test_rebaser_del ); Tcl_SetObjResult(interp, objv[1]); return TCL_OK; } /* ** Run some sanity checks on the changeset in nChangeset byte buffer ** pChangeset. If any fail, return a non-zero value and, optionally, ** set output variable (*pzErr) to point to a buffer containing an ** English language error message describing the problem. In this ** case it is the responsibility of the caller to free the buffer ** using sqlite3_free(). ** ** Or, if the changeset appears to be well-formed, this function ** returns SQLITE_OK and sets (*pzErr) to NULL. */ static int sqlite3_test_changeset( int nChangeset, void *pChangeset, char **pzErr ){ sqlite3_changeset_iter *pIter = 0; |
︙ | ︙ |
Changes to ext/wasm/GNUmakefile.
︙ | ︙ | |||
10 11 12 13 14 15 16 17 18 19 | # Primary targets: # # default, all = build in dev mode # # o0, o1, o2, o3, os, oz = full clean/rebuild with the -Ox level indicated # by the target name. Rebuild is necessary for all components to get # the desired optimization level. # # dist = create end user deliverables. Add dist.build=oX to build # with a specific optimization level, where oX is one of the | > > > > > | > > > > > > > > > > > | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | # Primary targets: # # default, all = build in dev mode # # o0, o1, o2, o3, os, oz = full clean/rebuild with the -Ox level indicated # by the target name. Rebuild is necessary for all components to get # the desired optimization level. # # quick, q = do just a minimal build (sqlite3.js/wasm, tester1) for # faster development-mode turnaround. # # qo2, qoz = a combination of quick+o2/oz. # # dist = create end user deliverables. Add dist.build=oX to build # with a specific optimization level, where oX is one of the # above-listed o? or qo? target names. # # snapshot = like dist, but uses a zip file name which clearly # marks it as a prerelease/snapshot build. # # clean = clean up # # Required tools beyond those needed for the canonical builds: # # - Emscripten SDK: https://emscripten.org/docs/getting_started/downloads.html # - The bash shell # - GNU make, GNU sed, GNU awk, GNU grep (all in the $PATH) # - wasm-strip for release builds: https://github.com/WebAssembly/wabt # - InfoZip for 'dist' zip file ######################################################################## SHELL := $(shell which bash 2>/dev/null) MAKEFILE := $(lastword $(MAKEFILE_LIST)) CLEAN_FILES := DISTCLEAN_FILES := ./--dummy-- default: all release: oz |
︙ | ︙ | |||
67 68 69 70 71 72 73 74 75 76 | # absolute. dir.wasm := $(patsubst %/,%,$(dir $(MAKEFILE))) dir.api := api dir.jacc := jaccwabyt dir.common := common dir.fiddle := fiddle dir.tool := $(dir.top)/tool ######################################################################## # dir.dout = output dir for deliverables. # | > > | | | | | | | | | 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | # absolute. dir.wasm := $(patsubst %/,%,$(dir $(MAKEFILE))) dir.api := api dir.jacc := jaccwabyt dir.common := common dir.fiddle := fiddle dir.tool := $(dir.top)/tool CLEAN_FILES += *~ $(dir.jacc)/*~ $(dir.api)/*~ $(dir.common)/*~ $(dir.fiddle)/*~ ######################################################################## # dir.dout = output dir for deliverables. # # MAINTENANCE REMINDER: the output .js and .wasm files of certain emcc # buildables must be in _this_ dir, rather than a subdir, or else # parts of the generated code get confused and cannot load # property. Specifically, when X.js loads X.wasm, whether or not X.js # uses the correct path for X.wasm depends on how it's loaded: an HTML # script tag will resolve it intuitively, whereas a Worker's call to # importScripts() will not. That's a fundamental incompatibility with # how URL resolution in JS happens between those two contexts. See: # # https://zzz.buzz/2017/03/14/relative-uris-in-web-development/ # # We unfortunately have no way, from Worker-initiated code, to # automatically resolve the path from X.js to X.wasm. # # We have an "only slightly unsightly" solution for our main builds |
︙ | ︙ | |||
100 101 102 103 104 105 106 | ifeq (,$(wildcard $(dir.dout))) dir._tmp := $(shell mkdir -p $(dir.dout)) endif ifeq (,$(wildcard $(dir.tmp))) dir._tmp := $(shell mkdir -p $(dir.tmp)) endif | < < < > > | | > > | > > > > > | < > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | > | | 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 | ifeq (,$(wildcard $(dir.dout))) dir._tmp := $(shell mkdir -p $(dir.dout)) endif ifeq (,$(wildcard $(dir.tmp))) dir._tmp := $(shell mkdir -p $(dir.tmp)) endif sqlite3.c := $(dir.top)/sqlite3.c sqlite3.h := $(dir.top)/sqlite3.h # Most SQLITE_OPT flags are set in sqlite3-wasm.c but we need them # made explicit here for building speedtest1.c. SQLITE_OPT = \ -DSQLITE_ENABLE_FTS5 \ -DSQLITE_ENABLE_RTREE \ -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION \ -DSQLITE_ENABLE_STMTVTAB \ -DSQLITE_ENABLE_DBPAGE_VTAB \ -DSQLITE_ENABLE_DBSTAT_VTAB \ -DSQLITE_ENABLE_BYTECODE_VTAB \ -DSQLITE_ENABLE_OFFSET_SQL_FUNC \ -DSQLITE_OMIT_LOAD_EXTENSION \ -DSQLITE_OMIT_DEPRECATED \ -DSQLITE_OMIT_UTF16 \ -DSQLITE_OMIT_SHARED_CACHE \ -DSQLITE_OMIT_WAL \ -DSQLITE_THREADSAFE=0 \ -DSQLITE_TEMP_STORE=3 \ -DSQLITE_OS_KV_OPTIONAL=1 \ '-DSQLITE_DEFAULT_UNIX_VFS="unix-none"' \ -DSQLITE_USE_URI=1 \ -DSQLITE_WASM_ENABLE_C_TESTS $(sqlite3.c) $(sqlite3.h): $(MAKE) -C $(dir.top) sqlite3.c .PHONY: clean distclean clean: -rm -f $(CLEAN_FILES) distclean: clean -rm -f $(DISTCLEAN_FILES) ifeq (release,$(filter release,$(MAKECMDGOALS))) ifeq (,$(wasm-strip)) $(error Cannot make release-quality binary because wasm-strip is not available. \ See notes in the warning above) endif else $(info Development build. Use '$(MAKE) release' for a smaller release build.) endif # bin.version-info = binary to output various sqlite3 version info for # embedding in the JS files and in building the distribution zip file. # It must NOT be in $(dir.tmp) because we need it to survive the # cleanup process for the dist build to work properly. bin.version-info := $(dir.wasm)/version-info $(bin.version-info): $(dir.wasm)/version-info.c $(sqlite3.h) $(MAKEFILE) $(CC) -O0 -I$(dir.top) -o $@ $< DISTCLEAN_FILES += $(bin.version-info) # bin.stripcomments is used for stripping C/C++-style comments from JS # files. The JS files contain large chunks of documentation which we # don't need for all builds. That app's -k flag is of particular # importance here, as it allows us to retain the opening comment # blocks, which contain the license header and version info. bin.stripccomments := $(dir.tool)/stripccomments $(bin.stripccomments): $(bin.stripccomments).c $(MAKEFILE) $(CC) -o $@ $< DISTCLEAN_FILES += $(bin.stripccomments) ######################################################################## # C-PP.FILTER: a $(call)able to transform $(1) to $(2) via ./c-pp -f # $(1) ... # # Historical notes: # # - We first attempted to use gcc and/or clang to preprocess JS files # in the same way we would normally do C files, but C-specific quirks # of each makes that untennable. # # - We implemented c-pp.c (the C-Minus Pre-processor) as a custom # generic/file-format-agnostic preprocessor to enable us to pack # code for different target builds into the same JS files. Most # notably, some ES6 module (a.k.a. ESM) features cannot legally be # referenced at all in non-ESM code, e.g. the "import" and "export" # keywords. This preprocessing step permits us to swap out sections # of code where necessary for ESM and non-ESM (a.k.a. vanilla JS) # require different implementations. The alternative to such # preprocessing, would be to have separate source files for ES6 # builds, which would have a higher maintenance burden than c-pp.c # seems likely to. # # c-pp.c was written specifically for the sqlite project's JavaScript # builds but is maintained as a standalone project: # https://fossil.wanderinghorse.net/r/c-pp bin.c-pp := ./c-pp $(bin.c-pp): c-pp.c $(sqlite3.c) $(MAKEFILE) $(CC) -O0 -o $@ c-pp.c $(sqlite3.c) '-DCMPP_DEFAULT_DELIM="//#"' -I$(dir.top) \ -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_UTF16 \ -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_WAL -DSQLITE_THREADSAFE=0 \ -DSQLITE_TEMP_STORE=3 define C-PP.FILTER # Create $2 from $1 using $(bin.c-pp) # $1 = Input file: c-pp -f $(1).js # $2 = Output file: c-pp -o $(2).js # $3 = optional c-pp -D... flags $(2): $(1) $$(MAKEFILE) $$(bin.c-pp) $$(bin.c-pp) -f $(1) -o $$@ $(3) CLEAN_FILES += $(2) endef c-pp.D.vanilla ?= c-pp.D.esm ?= -Dtarget=es6-module # /end C-PP.FILTER ######################################################################## # cflags.common = C compiler flags for all builds cflags.common := -I. -I.. -I$(dir.top) # emcc.WASM_BIGINT = 1 for BigInt (C int64) support, else 0. The API # disables certain features if BigInt is not enabled and such builds # _are not tested_ on any regular basis. emcc.WASM_BIGINT ?= 1 # emcc_opt = optimization-related flags. These are primarily used by # the various oX targets. build times for -O levels higher than 0 are # painful at dev-time. emcc_opt ?= -O0 # When passing emcc_opt from the CLI, += and re-assignment have no # effect, so emcc_opt+=-g3 doesn't work. So... emcc_opt_full := $(emcc_opt) -g3 # ^^^ ALWAYS use -g3. See below for why. # # ^^^ -flto improves runtime speed at -O0 considerably but doubles # build time. |
︙ | ︙ | |||
161 162 163 164 165 166 167 | # tools package (https://github.com/WebAssembly/wabt), to strip the # debugging symbols. That results in a small build with unmangled # symbol names. -Oz gives ever-so-slightly better compression than # -Os: not quite 1% in some completely unscientific tests. Runtime # speed for the unit tests is all over the place either way so it's # difficult to say whether -Os gives any speed benefit over -Oz. # | | > > | < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < | > > > > > > | | | > > > | | | > > > > | | < < < < < < < < < < < < < < < < < < < < < < < < < | > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > | | | | > > > > | < > > | < | | | > | < | 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 | # tools package (https://github.com/WebAssembly/wabt), to strip the # debugging symbols. That results in a small build with unmangled # symbol names. -Oz gives ever-so-slightly better compression than # -Os: not quite 1% in some completely unscientific tests. Runtime # speed for the unit tests is all over the place either way so it's # difficult to say whether -Os gives any speed benefit over -Oz. # # Much practice has demonstrated that -O2 consistently gives the best # runtime speeds, but not by a large enough factor to rule out use of # -Oz when small deliverable size is a priority. ######################################################################## # EXPORTED_FUNCTIONS.* = files for use with Emscripten's # -sEXPORTED_FUNCTION flag. EXPORTED_FUNCTIONS.api.in := $(abspath $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api) EXPORTED_FUNCTIONS.api := $(dir.tmp)/EXPORTED_FUNCTIONS.api $(EXPORTED_FUNCTIONS.api): $(EXPORTED_FUNCTIONS.api.in) $(MAKEFILE) cp $(EXPORTED_FUNCTIONS.api.in) $@ # sqlite3-license-version.js = generated JS file with the license # header and version info. sqlite3-license-version.js := $(dir.tmp)/sqlite3-license-version.js # sqlite3-license-version-header.js = JS file containing only the # license header. sqlite3-license-version-header.js := $(dir.api)/sqlite3-license-version-header.js # sqlite3-api-build-version.js = generated JS file which populates the # sqlite3.version object using $(bin.version-info). sqlite3-api-build-version.js := $(dir.tmp)/sqlite3-api-build-version.js # sqlite3-api.jses = the list of JS files which make up # $(sqlite3-api.js.in), in the order they need to be assembled. sqlite3-api.jses := $(sqlite3-license-version.js) sqlite3-api.jses += $(dir.api)/sqlite3-api-prologue.js sqlite3-api.jses += $(dir.common)/whwasmutil.js sqlite3-api.jses += $(dir.jacc)/jaccwabyt.js sqlite3-api.jses += $(dir.api)/sqlite3-api-glue.js sqlite3-api.jses += $(sqlite3-api-build-version.js) sqlite3-api.jses += $(dir.api)/sqlite3-api-oo1.js sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.js sqlite3-api.jses += $(dir.api)/sqlite3-v-helper.js sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js # "External" API files which are part of our distribution # but not part of the sqlite3-api.js amalgamation. SOAP.js := $(dir.api)/sqlite3-opfs-async-proxy.js sqlite3-worker1.js := $(dir.api)/sqlite3-worker1.js sqlite3-worker1-promiser.js := $(dir.api)/sqlite3-worker1-promiser.js # COPY_XAPI = a $(call)able function to copy $1 to $(dir.dout), where # $1 must be one of the "external" JS API files. define COPY_XAPI sqlite3-api.ext.jses += $$(dir.dout)/$$(notdir $(1)) $$(dir.dout)/$$(notdir $(1)): $(1) $$(MAKEFILE) cp $$< $$@ endef $(foreach X,$(SOAP.js) $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js),\ $(eval $(call COPY_XAPI,$(X)))) all quick: $(sqlite3-api.ext.jses) q: quick # sqlite3-api.js.in = the generated sqlite3-api.js before it gets # preprocessed. It contains all of $(sqlite3-api.jses) but none of the # Emscripten-specific headers and footers. sqlite3-api.js.in := $(dir.tmp)/sqlite3-api.c-pp.js $(sqlite3-api.js.in): $(sqlite3-api.jses) $(MAKEFILE) @echo "Making $@..." @for i in $(sqlite3-api.jses); do \ echo "/* BEGIN FILE: $$i */"; \ cat $$i; \ echo "/* END FILE: $$i */"; \ done > $@ $(sqlite3-api-build-version.js): $(bin.version-info) $(MAKEFILE) @echo "Making $@..." @{ \ echo 'self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){'; \ echo -n ' sqlite3.version = '; \ $(bin.version-info) --json; \ echo ';'; \ echo '});'; \ } > $@ $(sqlite3-license-version.js): $(sqlite3.h) $(sqlite3-license-version-header.js) \ $(MAKEFILE) @echo "Making $@..."; { \ cat $(sqlite3-license-version-header.js); \ echo '/*'; \ echo '** This code was built from sqlite3 version...'; \ echo "** "; \ awk -e '/define SQLITE_VERSION/{$$1=""; print "**" $$0}' \ -e '/define SQLITE_SOURCE_ID/{$$1=""; print "**" $$0}' $(sqlite3.h); \ echo '*/'; \ } > $@ ######################################################################## # --post-js and --pre-js are emcc flags we use to append/prepend JS to # the generated emscripten module file. The following rules generate # various versions of those files for the vanilla and ESM builds. pre-js.js.in := $(dir.api)/pre-js.c-pp.js pre-js.js.esm := $(dir.tmp)/pre-js.esm.js pre-js.js.vanilla := $(dir.tmp)/pre-js.vanilla.js $(eval $(call C-PP.FILTER,$(pre-js.js.in),$(pre-js.js.vanilla),$(c-pp.D.vanilla))) $(eval $(call C-PP.FILTER,$(pre-js.js.in),$(pre-js.js.esm),$(c-pp.D.esm))) post-js.js.in := $(dir.tmp)/post-js.c-pp.js post-js.js.vanilla := $(dir.tmp)/post-js.vanilla.js post-js.js.esm := $(dir.tmp)/post-js.esm.js post-jses.js := \ $(dir.api)/post-js-header.js \ $(sqlite3-api.js.in) \ $(dir.api)/post-js-footer.js $(post-js.js.in): $(post-jses.js) $(MAKEFILE) @echo "Making $@..." @for i in $(post-jses.js); do \ echo "/* BEGIN FILE: $$i */"; \ cat $$i; \ echo "/* END FILE: $$i */"; \ done > $@ $(eval $(call C-PP.FILTER,$(post-js.js.in),$(post-js.js.vanilla),$(c-pp.D.vanilla))) $(eval $(call C-PP.FILTER,$(post-js.js.in),$(post-js.js.esm),$(c-pp.D.esm))) # extern-post-js* and extern-pre-js* are files for use with # Emscripten's --extern-pre-js and --extern-post-js flags. These # rules make different copies for the vanilla and ESM builds. extern-post-js.js.in := $(dir.api)/extern-post-js.c-pp.js extern-post-js.js.vanilla := $(dir.tmp)/extern-post-js.vanilla.js extern-post-js.js.esm := $(dir.tmp)/extern-post-js.esm.js $(eval $(call C-PP.FILTER,$(extern-post-js.js.in),$(extern-post-js.js.vanilla),$(c-pp.D.vanilla))) $(eval $(call C-PP.FILTER,$(extern-post-js.js.in),$(extern-post-js.js.esm),$(c-pp.D.esm))) extern-pre-js.js := $(dir.api)/extern-pre-js.js # Emscripten flags for --[extern-][pre|post]-js=... for the # various builds. pre-post-common.flags := \ --extern-pre-js=$(sqlite3-license-version.js) pre-post-common.flags.vanilla := \ $(pre-post-common.flags) \ --post-js=$(post-js.js.vanilla) \ --extern-post-js=$(extern-post-js.js.vanilla) pre-post-common.flags.esm := \ $(pre-post-common.flags) \ --post-js=$(post-js.js.esm) \ --extern-post-js=$(extern-post-js.js.esm) # pre-post-jses.deps.* = a list of dependencies for the # --[extern-][pre/post]-js files. pre-post-jses.deps.common := $(extern-pre-js.js) $(sqlite3-license-version.js) pre-post-jses.deps.vanilla := $(pre-post-jses.deps.common) \ $(post-js.js.vanilla) $(extern-post-js.js.vanilla) pre-post-jses.deps.esm := $(pre-post-jses.deps.common) \ $(post-js.js.esm) $(extern-post-js.js.esm) ######################################################################## # call-make-pre-js is a $(call)able which creates rules for # pre-js-$(1).js. $1 = the base name of the JS file on whose behalf # this pre-js is for. $2 is the build mode: one of (vanilla, esm). # This sets up --[extern-][pre/post]-js flags in # $(pre-post-$(1).flags.$(2)) and dependencies in # $(pre-post-$(1).deps.$(2)). define call-make-pre-js pre-post-$(1).flags.$(2) ?= $$(dir.tmp)/pre-js-$(1)-$(2).js: $$(pre-js.js.$(2)) $$(MAKEFILE) cp $$(pre-js.js.$(2)) $$@ @if [ sqlite3-wasmfs = $(1) ]; then \ echo "delete Module[xNameOfInstantiateWasm] /*for WASMFS build*/;"; \ elif [ sqlite3 != $(1) ]; then \ echo "Module[xNameOfInstantiateWasm].uri = '$(1).wasm';"; \ fi >> $$@ pre-post-$(1).deps.$(2) := \ $$(pre-post-jses.deps.$(2)) \ $$(dir.tmp)/pre-js-$(1)-$(2).js pre-post-$(1).flags.$(2) += \ $$(pre-post-common.flags.$(2)) \ --pre-js=$$(dir.tmp)/pre-js-$(1)-$(2).js endef # /post-js and pre-js ######################################################################## ######################################################################## # emcc flags for .c/.o/.wasm/.js. emcc.flags := ifeq (1,$(emcc.verbose)) emcc.flags += -v # -v is _very_ loud but also informative about what it's doing endif ######################################################################## # emcc flags for .c/.o. emcc.cflags := emcc.cflags += -std=c99 -fPIC # -------------^^^^^^^^ we need c99 for $(sqlite3-wasm.c). emcc.cflags += -I. -I$(dir.top) ######################################################################## # emcc flags specific to building .js/.wasm files... emcc.jsflags := -fPIC emcc.jsflags += --minify 0 emcc.jsflags += --no-entry emcc.jsflags += -sWASM_BIGINT=$(emcc.WASM_BIGINT) emcc.jsflags += -sMODULARIZE emcc.jsflags += -sSTRICT_JS emcc.jsflags += -sDYNAMIC_EXECUTION=0 emcc.jsflags += -sNO_POLYFILL emcc.jsflags += -sEXPORTED_FUNCTIONS=@$(EXPORTED_FUNCTIONS.api) emcc.exportedRuntimeMethods := \ -sEXPORTED_RUNTIME_METHODS=wasmMemory # wasmMemory ==> required by our code for use with -sIMPORTED_MEMORY emcc.jsflags += $(emcc.exportedRuntimeMethods) emcc.jsflags += -sUSE_CLOSURE_COMPILER=0 emcc.jsflags += -sIMPORTED_MEMORY emcc.environment := -sENVIRONMENT=web,worker ######################################################################## # -sINITIAL_MEMORY: How much memory we need to start with is governed |
︙ | ︙ | |||
362 363 364 365 366 367 368 | $(error emcc.INITIAL_MEMORY must be one of: 8, 16, 32, 64, 96, 128 (megabytes)) endif emcc.jsflags += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.$(emcc.INITIAL_MEMORY)) # /INITIAL_MEMORY ######################################################################## emcc.jsflags += $(emcc.environment) | | | > > > | | | < | > > > > > > > > > > > > | | | > | > | | < | > > | > | | | < > > | | < < < < < < < < < < | | | | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > | | | > | | > | 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 | $(error emcc.INITIAL_MEMORY must be one of: 8, 16, 32, 64, 96, 128 (megabytes)) endif emcc.jsflags += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.$(emcc.INITIAL_MEMORY)) # /INITIAL_MEMORY ######################################################################## emcc.jsflags += $(emcc.environment) emcc.jsflags += -sSTACK_SIZE=512KB # ^^^ ACHTUNG: emsdk 3.1.27 reduced the default stack size from 5MB to # a mere 64KB, which leads to silent memory corruption via the kvvfs # VFS, which requires twice that for its xRead() and xWrite() methods. ######################################################################## # $(sqlite3.js.init-func) is the name Emscripten assigns our exported # module init/load function. This symbol name is hard-coded in # $(extern-post-js.js) as well as in numerous docs. # # "sqlite3InitModule" is the symbol we document for client use, so # that's the symbol name which must be exported, whether it comes from # Emscripten or our own code in extern-post-js.js. # # That said... we can change $(sqlite3.js.init-func) as long as the # name "sqlite3InitModule" is the one which gets exposed via the # resulting JS files. That can be accomplished via # extern-post-js.js. However... using a temporary symbol name here # and then adding sqlite3InitModule() ourselves results in 2 global # symbols: we cannot "delete" the Emscripten-defined # $(sqlite3.js.init-func) because it's declared with "var". sqlite3.js.init-func := sqlite3InitModule emcc.jsflags += -sEXPORT_NAME=$(sqlite3.js.init-func) emcc.jsflags += -sGLOBAL_BASE=4096 # HYPOTHETICALLY keep func table indexes from overlapping w/ heap addr. #emcc.jsflags += -sSTRICT # fails due to missing __syscall_...() #emcc.jsflags += -sALLOW_UNIMPLEMENTED_SYSCALLS #emcc.jsflags += -sFILESYSTEM=0 # only for experimentation. sqlite3 needs the FS API #emcc.jsflags += -sABORTING_MALLOC # only for experimentation emcc.jsflags += -sALLOW_TABLE_GROWTH # ^^^^ -sALLOW_TABLE_GROWTH is required for installing new SQL UDFs emcc.jsflags += -Wno-limited-postlink-optimizations # ^^^^ emcc likes to warn when we have "limited optimizations" via the # -g3 flag. # emcc.jsflags += -sSTANDALONE_WASM # causes OOM errors, not sure why. # Re. undefined symbol handling, see: https://lld.llvm.org/WebAssembly.html emcc.jsflags += -sERROR_ON_UNDEFINED_SYMBOLS=1 emcc.jsflags += -sLLD_REPORT_UNDEFINED #emcc.jsflags += --allow-undefined #emcc.jsflags += --import-undefined #emcc.jsflags += --unresolved-symbols=import-dynamic --experimental-pic #emcc.jsflags += --experimental-pic --unresolved-symbols=ingore-all --import-undefined #emcc.jsflags += --unresolved-symbols=ignore-all ######################################################################## # -sMEMORY64=1 fails to load, erroring with: # invalid memory limits flags 0x5 # (enable via --experimental-wasm-memory64) # # ^^^^ MEMORY64=2 builds and loads but dies when we do things like: # # new Uint8Array(wasm.heap8u().buffer, ptr, n) # # because ptr is now a BigInt, so is invalid for passing to arguments # which have strict must-be-a-Number requirements. That aspect will # make any eventual port to 64-bit address space extremely painful, as # such constructs are found all over the place in the source code. ######################################################################## ######################################################################## # -sSINGLE_FILE: # https://github.com/emscripten-core/emscripten/blob/main/src/settings.js # # -sSINGLE_FILE=1 would be _really_ nice but we have to build with -g3 # for -O2 and higher to work (else minification breaks the code) and # cannot wasm-strip the binary before it gets encoded into the JS # file. The result is that the generated JS file is, because of the # -g3 debugging info, _huge_. ######################################################################## sqlite3.js := $(dir.dout)/sqlite3.js sqlite3.mjs := $(dir.dout)/sqlite3.mjs # Undocumented Emscripten feature: if the target file extension is # "mjs", it defaults to ES6 module builds: # https://github.com/emscripten-core/emscripten/issues/14383 sqlite3.wasm := $(dir.dout)/sqlite3.wasm sqlite3-wasm.c := $(dir.api)/sqlite3-wasm.c # sqlite3-wasm.o vs sqlite3-wasm.c: building against the latter # (predictably) results in a slightly faster binary, but we're close # enough to the target speed requirements that the 500ms makes a # difference. Thus we build all binaries against sqlite3-wasm.c # instead of building a shared copy of sqlite3-wasm.o. $(eval $(call call-make-pre-js,sqlite3,vanilla)) $(eval $(call call-make-pre-js,sqlite3,esm)) $(sqlite3.js) $(sqlite3.mjs): $(MAKEFILE) $(sqlite3-wasm.c) \ $(EXPORTED_FUNCTIONS.api) $(sqlite3.js): $(pre-post-sqlite3.deps.vanilla) $(sqlite3.mjs): $(pre-post-sqlite3.deps.esm) ######################################################################## # SQLITE3.xJS.RECIPE = the $(call)able recipe body for $(sqlite3.js) # and $(sqlite3.mjs). $1 = one of (vanilla, esm). # # Reminder for ESM builds: even if we use -sEXPORT_ES6=0, emcc _still_ # adds: # # export default $(sqlite3.js.init-func); # # when building *.mjs, which is bad because we need to export an # overwritten version of that function and cannot "export default" # twice. Because of this, we have to sed $(sqlite3.mjs) to remove the # _first_ instance (only) of /^export default/. # # Upstream RFE: # https://github.com/emscripten-core/emscripten/issues/18237 ######################################################################## # SQLITE3.xJS.EXPORT-DEFAULT is part of SQLITE3[-WASMFS].xJS.RECIPE, # factored into a separate piece to avoid code duplication. $1 is # the build mode: one of (vanilla, esm). define SQLITE3.xJS.ESM-EXPORT-DEFAULT if [ esm = $(1) ]; then \ echo "Fragile workaround for an Emscripten annoyance. See SQLITE3.xJS.RECIPE."; \ sed -i -e '0,/^export default/{/^export default/d;}' $@ || exit $$?; \ if ! grep -q '^export default' $@; then \ echo "Cannot find export default." 1>&2; \ exit 1; \ fi; \ fi endef define SQLITE3.xJS.RECIPE @echo "Building $@ ..." $(emcc.bin) -o $@ $(emcc_opt_full) $(emcc.flags) \ $(emcc.jsflags) \ $(pre-post-sqlite3.flags.$(1)) $(emcc.flags.sqlite3.$(1)) \ $(cflags.common) $(SQLITE_OPT) $(sqlite3-wasm.c) @$(call SQLITE3.xJS.ESM-EXPORT-DEFAULT,$(1)) chmod -x $(sqlite3.wasm) $(maybe-wasm-strip) $(sqlite3.wasm) @ls -la $@ $(sqlite3.wasm) endef emcc.flags.sqlite3.vanilla := emcc.flags.sqlite3.esm := -sEXPORT_ES6 -sUSE_ES6_IMPORT_META $(sqlite3.js): $(call SQLITE3.xJS.RECIPE,vanilla) $(sqlite3.mjs): $(call SQLITE3.xJS.RECIPE,esm) ######################################################################## # We have to ensure that we do not build both $(sqlite3.js) and # $(sqlite3.mjs) in parallel because both result in the creation of # $(sqlite3.wasm). We have no(?) way to build just the .mjs file # without also building the .wasm file. i.e. we're building # $(sqlite3.wasm) twice, but that's apparently unavoidable (and # harmless, just a waste of build time). $(sqlite3.wasm): $(sqlite3.js) $(sqlite3.mjs): $(sqlite3.js) CLEAN_FILES += $(sqlite3.js) $(sqlite3.mjs) $(sqlite3.wasm) all: $(sqlite3.js) $(sqlite3.mjs) quick: $(sqlite3.js) quick: $(sqlite3.mjs) # for the sake of the snapshot build # End main $(sqlite3.js) build ######################################################################## ######################################################################## # batch-runner.js is part of one of the test apps which reads in SQL # dumps generated by $(speedtest1) and executes them. dir.sql := sql speedtest1 := ../../speedtest1 speedtest1.c := ../../test/speedtest1.c speedtest1.sql := $(dir.sql)/speedtest1.sql speedtest1.cliflags := --size 25 --big-transactions $(speedtest1): $(MAKE) -C ../.. speedtest1 |
︙ | ︙ | |||
477 478 479 480 481 482 483 | # ^^^ we don't do this along with 'clean' because we clean/rebuild on # a regular basis with different -Ox flags and rebuilding the batch # pieces each time is an unnecessary time sink. batch: batch-runner.list all: batch # end batch-runner.js ######################################################################## | | > | | | | | | | | | | | | | | > | | | | | | | < | 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 | # ^^^ we don't do this along with 'clean' because we clean/rebuild on # a regular basis with different -Ox flags and rebuilding the batch # pieces each time is an unnecessary time sink. batch: batch-runner.list all: batch # end batch-runner.js ######################################################################## # Wasmified speedtest1 is our primary benchmarking tool. # # emcc.speedtest1.common = emcc flags used by multiple builds of speedtest1 # emcc.speedtest1 = emcc flags used by main build of speedtest1 emcc.speedtest1.common := $(emcc_opt_full) emcc.speedtest1 := emcc.speedtest1 += -sENVIRONMENT=web emcc.speedtest1 += -sALLOW_MEMORY_GROWTH emcc.speedtest1 += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.$(emcc.INITIAL_MEMORY)) emcc.speedtest1.common += -sINVOKE_RUN=0 emcc.speedtest1.common += --no-entry #emcc.speedtest1.common += -flto emcc.speedtest1.common += -sABORTING_MALLOC emcc.speedtest1.common += -sSTRICT_JS emcc.speedtest1.common += -sMODULARIZE emcc.speedtest1.common += -Wno-limited-postlink-optimizations EXPORTED_FUNCTIONS.speedtest1 := $(abspath $(dir.tmp)/EXPORTED_FUNCTIONS.speedtest1) emcc.speedtest1.common += -sSTACK_SIZE=512KB emcc.speedtest1.common += -sEXPORTED_FUNCTIONS=@$(EXPORTED_FUNCTIONS.speedtest1) emcc.speedtest1.common += $(emcc.exportedRuntimeMethods) emcc.speedtest1.common += -sALLOW_TABLE_GROWTH emcc.speedtest1.common += -sDYNAMIC_EXECUTION=0 emcc.speedtest1.common += --minify 0 emcc.speedtest1.common += -sEXPORT_NAME=$(sqlite3.js.init-func) emcc.speedtest1.common += -sWASM_BIGINT=$(emcc.WASM_BIGINT) speedtest1.exit-runtime0 := -sEXIT_RUNTIME=0 speedtest1.exit-runtime1 := -sEXIT_RUNTIME=1 # Re -sEXIT_RUNTIME=1 vs 0: if it's 1 and speedtest1 crashes, we get # this error from emscripten: # # > native function `free` called after runtime exit (use # NO_EXIT_RUNTIME to keep it alive after main() exits)) |
︙ | ︙ | |||
524 525 526 527 528 529 530 | # -sEXIT_RUNTIME=1 but we need EXIT_RUNTIME=0 for the worker-based app # which runs speedtest1 multiple times. $(EXPORTED_FUNCTIONS.speedtest1): $(EXPORTED_FUNCTIONS.api) @echo "Making $@ ..." @{ echo _wasm_main; cat $(EXPORTED_FUNCTIONS.api); } > $@ speedtest1.js := $(dir.dout)/speedtest1.js | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > | > | | | > > | > > > > > > > | < < < < < < | > > | | > > | > > > > > | | | | | < < < < < > > > > > > > > > > | 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 | # -sEXIT_RUNTIME=1 but we need EXIT_RUNTIME=0 for the worker-based app # which runs speedtest1 multiple times. $(EXPORTED_FUNCTIONS.speedtest1): $(EXPORTED_FUNCTIONS.api) @echo "Making $@ ..." @{ echo _wasm_main; cat $(EXPORTED_FUNCTIONS.api); } > $@ speedtest1.js := $(dir.dout)/speedtest1.js speedtest1.wasm := $(dir.dout)/speedtest1.wasm cflags.speedtest1 := $(cflags.common) -DSQLITE_SPEEDTEST1_WASM speedtest1.cses := $(speedtest1.c) $(sqlite3-wasm.c) $(eval $(call call-make-pre-js,speedtest1,vanilla)) $(speedtest1.js): $(MAKEFILE) $(speedtest1.cses) \ $(pre-post-speedtest1.deps.vanilla) \ $(EXPORTED_FUNCTIONS.speedtest1) @echo "Building $@ ..." $(emcc.bin) \ $(emcc.speedtest1) $(emcc.speedtest1.common) \ $(cflags.speedtest1) $(pre-post-speedtest1.flags.vanilla) \ $(SQLITE_OPT) \ $(speedtest1.exit-runtime0) \ -o $@ $(speedtest1.cses) -lm $(maybe-wasm-strip) $(speedtest1.wasm) ls -la $@ $(speedtest1.wasm) speedtest1: $(speedtest1.js) all: speedtest1 CLEAN_FILES += $(speedtest1.js) $(speedtest1.wasm) # end speedtest1.js ######################################################################## ######################################################################## # tester1 is the main unit and regression test application and needs # to be able to run in 4 separate modes to cover the primary # client-side use cases: # # 1) Load sqlite3 in the main UI thread of a conventional script. # 2) Load sqlite3 in a conventional Worker thread. # 3) Load sqlite3 as an ES6 module (ESM) in the main thread. # 4) Load sqlite3 as an ESM worker. (Not all browsers support this.) # # To that end, we require two separate builds of tester1.js: # # tester1.js: cases 1 and 2 # tester1.mjs: cases 3 and 4 # # To create those, we filter tester1.c-pp.js with $(bin.c-pp)... $(eval $(call C-PP.FILTER,tester1.c-pp.js,tester1.js)) $(eval $(call C-PP.FILTER,tester1.c-pp.js,tester1.mjs,$(c-pp.D.esm))) $(eval $(call C-PP.FILTER,tester1.c-pp.html,tester1.html)) $(eval $(call C-PP.FILTER,tester1.c-pp.html,tester1-esm.html,$(c-pp.D.esm))) tester1: tester1.js tester1.mjs tester1.html tester1-esm.html all quick: tester1 ######################################################################## # Convenience rules to rebuild with various -Ox levels. Much # experimentation shows -O2 to be the clear winner in terms of speed. # Note that build times with anything higher than -O0 are somewhat # painful. .PHONY: o0 o1 o2 o3 os oz o-xtra := #o-xtra ?= -flto # ^^^^ -flto can have a considerably performance boost at -O0 but # doubles the build time and seems to have negligible, if any, effect # on higher optimization levels. o0: clean $(MAKE) -e "emcc_opt=-O0" o1: clean $(MAKE) -e "emcc_opt=-O1 $(o-xtra)" o2: clean $(MAKE) -j2 -e "emcc_opt=-O2 $(o-xtra)" qo2: clean $(MAKE) -j2 -e "emcc_opt=-O2 $(o-xtra)" quick o3: clean $(MAKE) -e "emcc_opt=-O3 $(o-xtra)" os: clean @echo "WARNING: -Os can result in a build with mysteriously missing pieces!" $(MAKE) -e "emcc_opt=-Os $(o-xtra)" oz: clean $(MAKE) -j2 -e "emcc_opt=-Oz $(o-xtra)" qoz: clean $(MAKE) -j2 -e "emcc_opt=-Oz $(o-xtra)" quick ######################################################################## # Sub-makes... # sqlite.org/fiddle application... include fiddle.make # Only add wasmfs if wasmfs.enable=1 or we're running (dist)clean ifneq (,$(filter wasmfs,$(MAKECMDGOALS))) wasmfs.enable ?= 1 else wasmfs.enable ?= $(if $(filter %clean,$(MAKECMDGOALS)),1,0) endif ifeq (1,$(wasmfs.enable)) # wasmfs build disabled 2022-10-19 per /chat discussion. # OPFS-over-wasmfs was initially a stopgap measure and a convenient # point of comparison for the OPFS sqlite3_vfs's performance, but it # currently doubles our deliverables and build maintenance burden for # little benefit. # ######################################################################## # Some platforms do not support the WASMFS build. Raspberry Pi OS is one # of them. As such platforms are discovered, add their (uname -m) name # to PLATFORMS_WITH_NO_WASMFS to exclude the wasmfs build parts. PLATFORMS_WITH_NO_WASMFS := aarch64 # add any others here THIS_ARCH := $(shell /usr/bin/uname -m) ifneq (,$(filter $(THIS_ARCH),$(PLATFORMS_WITH_NO_WASMFS))) $(info This platform does not support the WASMFS build.) HAVE_WASMFS := 0 else HAVE_WASMFS := 1 include wasmfs.make endif endif # /wasmfs ######################################################################## ######################################################################## # Push files to public wasm-testing.sqlite.org server wasm-testing.include = *.js *.mjs *.html \ ./tests \ batch-runner.list \ $(dir.dout) $(dir.sql) $(dir.common) $(dir.fiddle) $(dir.jacc) wasm-testing.exclude = sql/speedtest1.sql wasm-testing.dir = /jail/sites/wasm-testing wasm-testing.dest ?= wasm-testing:$(wasm-testing.dir) # ---------------------^^^^^^^^^^^^ ssh alias .PHONY: push-testing push-testing: rsync -z -e ssh --ignore-times --chown=stephan:www-data --group -r \ $(patsubst %,--exclude=%,$(wasm-testing.exclude)) \ $(wasm-testing.include) $(wasm-testing.dest) @echo "Updating gzipped copies..."; \ ssh wasm-testing 'cd $(wasm-testing.dir) && bash .gzip' || \ echo "SSH failed: it's likely that stale content will be served via old gzip files." ######################################################################## # If we find a copy of the sqlite.org/wasm docs checked out, copy # certain files over to it, noting that some need automatable edits... wasm.docs.home ?= ../../../wasm wasm.docs.found = $(if $(wildcard $(wasm.docs.home)/api-index.md),\ $(wildcard $(wasm.docs.home)),) .PHONY: update-docs ifeq (,$(wasm.docs.found)) update-docs: @echo "Cannot find wasm docs checkout."; \ echo "Pass wasm.docs.home=/path/to/wasm/docs/checkout or edit this makefile to suit."; \ exit 127 else wasm.docs.jswasm := $(wasm.docs.home)/jswasm update-docs: $(bin.stripccomments) $(sqlite3.js) $(sqlite3.wasm) @echo "Copying files to the /wasm docs. Be sure to use an -Oz build for this!" cp $(sqlite3.wasm) $(wasm.docs.jswasm)/. $(bin.stripccomments) -k -k < $(sqlite3.js) \ | sed -e '/^[ \t]*$$/d' > $(wasm.docs.jswasm)/sqlite3.js cp demo-123.js demo-123.html demo-123-worker.html $(wasm.docs.home) sed -n -e '/EXTRACT_BEGIN/,/EXTRACT_END/p' \ module-symbols.html > $(wasm.docs.home)/module-symbols.html endif # end /wasm docs ######################################################################## ######################################################################## # Create main client downloadable zip file: ifneq (,$(filter dist snapshot,$(MAKECMDGOALS))) include dist.make endif # Run local web server for the test/demo pages. httpd: althttpd -max-age 1 -enable-sab -page index.html |
Changes to ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | _sqlite3_aggregate_context _sqlite3_bind_blob _sqlite3_bind_double _sqlite3_bind_int _sqlite3_bind_int64 _sqlite3_bind_null _sqlite3_bind_parameter_count _sqlite3_bind_parameter_index _sqlite3_bind_text _sqlite3_changes _sqlite3_changes64 _sqlite3_clear_bindings _sqlite3_close_v2 _sqlite3_column_blob _sqlite3_column_bytes _sqlite3_column_count _sqlite3_column_count _sqlite3_column_double _sqlite3_column_int _sqlite3_column_int64 _sqlite3_column_name _sqlite3_column_text _sqlite3_column_type _sqlite3_compileoption_get _sqlite3_compileoption_used _sqlite3_create_function _sqlite3_create_function_v2 _sqlite3_create_window_function _sqlite3_data_count _sqlite3_db_filename _sqlite3_db_handle _sqlite3_db_name _sqlite3_deserialize _sqlite3_errmsg _sqlite3_error_offset _sqlite3_errstr _sqlite3_exec _sqlite3_expanded_sql _sqlite3_extended_errcode _sqlite3_extended_result_codes _sqlite3_file_control _sqlite3_finalize _sqlite3_free _sqlite3_initialize _sqlite3_libversion _sqlite3_libversion_number _sqlite3_malloc _sqlite3_malloc64 _sqlite3_msize _sqlite3_open _sqlite3_open_v2 _sqlite3_prepare_v2 _sqlite3_prepare_v3 _sqlite3_randomness _sqlite3_realloc _sqlite3_realloc64 _sqlite3_reset _sqlite3_result_blob _sqlite3_result_double _sqlite3_result_error _sqlite3_result_error_code _sqlite3_result_error_nomem _sqlite3_result_error_toobig _sqlite3_result_int _sqlite3_result_int64 _sqlite3_result_null _sqlite3_result_text _sqlite3_serialize _sqlite3_shutdown _sqlite3_sourceid _sqlite3_sql _sqlite3_step _sqlite3_strglob _sqlite3_strlike _sqlite3_total_changes _sqlite3_total_changes64 _sqlite3_trace_v2 _sqlite3_uri_boolean _sqlite3_uri_int64 _sqlite3_uri_key _sqlite3_uri_parameter _sqlite3_user_data _sqlite3_value_blob _sqlite3_value_bytes _sqlite3_value_double _sqlite3_value_int _sqlite3_value_int64 _sqlite3_value_text _sqlite3_value_type _sqlite3_vfs_find _sqlite3_vfs_register _sqlite3_vfs_unregister | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | _malloc _free _realloc _sqlite3_aggregate_context _sqlite3_bind_blob _sqlite3_bind_double _sqlite3_bind_int _sqlite3_bind_int64 _sqlite3_bind_null _sqlite3_bind_parameter_count _sqlite3_bind_parameter_index _sqlite3_bind_pointer _sqlite3_bind_text _sqlite3_busy_handler _sqlite3_busy_timeout _sqlite3_changes _sqlite3_changes64 _sqlite3_clear_bindings _sqlite3_close_v2 _sqlite3_collation_needed _sqlite3_column_blob _sqlite3_column_bytes _sqlite3_column_count _sqlite3_column_count _sqlite3_column_double _sqlite3_column_int _sqlite3_column_int64 _sqlite3_column_name _sqlite3_column_text _sqlite3_column_type _sqlite3_column_value _sqlite3_compileoption_get _sqlite3_compileoption_used _sqlite3_complete _sqlite3_context_db_handle _sqlite3_create_collation _sqlite3_create_collation_v2 _sqlite3_create_function _sqlite3_create_function_v2 _sqlite3_create_module _sqlite3_create_module_v2 _sqlite3_create_window_function _sqlite3_data_count _sqlite3_db_filename _sqlite3_db_handle _sqlite3_db_name _sqlite3_db_status _sqlite3_declare_vtab _sqlite3_deserialize _sqlite3_drop_modules _sqlite3_errcode _sqlite3_errmsg _sqlite3_error_offset _sqlite3_errstr _sqlite3_exec _sqlite3_expanded_sql _sqlite3_extended_errcode _sqlite3_extended_result_codes _sqlite3_file_control _sqlite3_finalize _sqlite3_free _sqlite3_get_auxdata _sqlite3_initialize _sqlite3_keyword_count _sqlite3_keyword_name _sqlite3_keyword_check _sqlite3_last_insert_rowid _sqlite3_libversion _sqlite3_libversion_number _sqlite3_limit _sqlite3_malloc _sqlite3_malloc64 _sqlite3_msize _sqlite3_open _sqlite3_open_v2 _sqlite3_overload_function _sqlite3_prepare_v2 _sqlite3_prepare_v3 _sqlite3_progress_handler _sqlite3_randomness _sqlite3_realloc _sqlite3_realloc64 _sqlite3_reset _sqlite3_result_blob _sqlite3_result_double _sqlite3_result_error _sqlite3_result_error_code _sqlite3_result_error_nomem _sqlite3_result_error_toobig _sqlite3_result_int _sqlite3_result_int64 _sqlite3_result_null _sqlite3_result_pointer _sqlite3_result_subtype _sqlite3_result_text _sqlite3_result_zeroblob _sqlite3_result_zeroblob64 _sqlite3_serialize _sqlite3_set_authorizer _sqlite3_set_auxdata _sqlite3_set_last_insert_rowid _sqlite3_shutdown _sqlite3_sourceid _sqlite3_sql _sqlite3_status _sqlite3_status64 _sqlite3_step _sqlite3_stmt_isexplain _sqlite3_stmt_readonly _sqlite3_stmt_status _sqlite3_strglob _sqlite3_stricmp _sqlite3_strlike _sqlite3_strnicmp _sqlite3_table_column_metadata _sqlite3_total_changes _sqlite3_total_changes64 _sqlite3_trace_v2 _sqlite3_txn_state _sqlite3_uri_boolean _sqlite3_uri_int64 _sqlite3_uri_key _sqlite3_uri_parameter _sqlite3_user_data _sqlite3_value_blob _sqlite3_value_bytes _sqlite3_value_double _sqlite3_value_dup _sqlite3_value_free _sqlite3_value_frombind _sqlite3_value_int _sqlite3_value_int64 _sqlite3_value_nochange _sqlite3_value_numeric_type _sqlite3_value_pointer _sqlite3_value_subtype _sqlite3_value_text _sqlite3_value_type _sqlite3_vfs_find _sqlite3_vfs_register _sqlite3_vfs_unregister _sqlite3_vtab_collation _sqlite3_vtab_distinct _sqlite3_vtab_in _sqlite3_vtab_in_first _sqlite3_vtab_in_next _sqlite3_vtab_nochange _sqlite3_vtab_on_conflict _sqlite3_vtab_rhs_value _sqlite3changegroup_add _sqlite3changegroup_add_strm _sqlite3changegroup_delete _sqlite3changegroup_new _sqlite3changegroup_output _sqlite3changegroup_output_strm _sqlite3changeset_apply _sqlite3changeset_apply_strm _sqlite3changeset_apply_v2 _sqlite3changeset_apply_v2_strm _sqlite3changeset_concat _sqlite3changeset_concat_strm _sqlite3changeset_conflict _sqlite3changeset_finalize _sqlite3changeset_fk_conflicts _sqlite3changeset_invert _sqlite3changeset_invert_strm _sqlite3changeset_new _sqlite3changeset_next _sqlite3changeset_old _sqlite3changeset_op _sqlite3changeset_pk _sqlite3changeset_start _sqlite3changeset_start_strm _sqlite3changeset_start_v2 _sqlite3changeset_start_v2_strm _sqlite3session_attach _sqlite3session_changeset _sqlite3session_changeset_size _sqlite3session_changeset_strm _sqlite3session_config _sqlite3session_create _sqlite3session_delete _sqlite3session_diff _sqlite3session_enable _sqlite3session_indirect _sqlite3session_isempty _sqlite3session_memory_used _sqlite3session_object_config _sqlite3session_patchset _sqlite3session_patchset_strm _sqlite3session_table_filter |
Changes to ext/wasm/api/README.md.
︙ | ︙ | |||
13 14 15 16 17 18 19 | build environments. e.g. the files `post-js-*.js` are for use with Emscripten's `--post-js` feature, and nowhere else. 3. Certain components must be in their own standalone files in order to be loaded as JS Workers. Note that the structure described here is the current state of things, | > | | | | | | | | > > > | | | | | | > > > > | > > > > > > > | | > > > > > > > > > > > | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | build environments. e.g. the files `post-js-*.js` are for use with Emscripten's `--post-js` feature, and nowhere else. 3. Certain components must be in their own standalone files in order to be loaded as JS Workers. Note that the structure described here is the current state of things, as of this writing, but is not set in stone forever and may change at any time. The overall idea is that the following files get concatenated together, in the listed order, the resulting file is loaded by a browser client: - **`sqlite3-api-prologue.js`**\ Contains the initial bootstrap setup of the sqlite3 API objects. This is exposed as a function, rather than objects, so that the next step can pass in a config object which abstracts away parts of the WASM environment, to facilitate plugging it in to arbitrary WASM toolchains. - **`../common/whwasmutil.js`**\ A semi-third-party collection of JS/WASM utility code intended to replace much of the Emscripten glue. The sqlite3 APIs internally use these APIs instead of their Emscripten counterparts, in order to be more portable to arbitrary WASM toolchains. This API is configurable, in principle, for use with arbitrary WASM toolchains. It is "semi-third-party" in that it was created in order to support this tree but is standalone and maintained together with... - **`../jaccwabyt/jaccwabyt.js`**\ Another semi-third-party API which creates bindings between JS and C structs, such that changes to the struct state from either JS or C are visible to the other end of the connection. This is also an independent spinoff project, conceived for the sqlite3 project but maintained separately. - **`sqlite3-api-glue.js`**\ Invokes functionality exposed by the previous two files to flesh out low-level parts of `sqlite3-api-prologue.js`. Most of these pieces related to populating the `sqlite3.capi.wasm` object. This file also deletes most global-scope symbols the above files create, effectively moving them into the scope being used for initializing the API. - **`<build>/sqlite3-api-build-version.js`**\ Gets created by the build process and populates the `sqlite3.version` object. This part is not critical, but records the version of the library against which this module was built. - **`sqlite3-api-oo1.js`**\ Provides a high-level object-oriented wrapper to the lower-level C API, colloquially known as OO API #1. Its API is similar to other high-level sqlite3 JS wrappers and should feel relatively familiar to anyone familiar with such APIs. That said, it is not a "required component" and can be elided from builds which do not want it. - **`sqlite3-api-worker1.js`**\ A Worker-thread-based API which uses OO API #1 to provide an interface to a database which can be driven from the main Window thread via the Worker message-passing interface. Like OO API #1, this is an optional component, offering one of any number of potential implementations for such an API. - **`sqlite3-worker1.js`**\ Is not part of the amalgamated sources and is intended to be loaded by a client Worker thread. It loads the sqlite3 module and runs the Worker #1 API which is implemented in `sqlite3-api-worker1.js`. - **`sqlite3-worker1-promiser.js`**\ Is likewise not part of the amalgamated sources and provides a Promise-based interface into the Worker #1 API. This is a far user-friendlier way to interface with databases running in a Worker thread. - **`sqlite3-v-helper.js`**\ Installs `sqlite3.vfs` and `sqlite3.vtab`, namespaces which contain helpers for use by downstream code which creates `sqlite3_vfs` and `sqlite3_module` implementations. - **`sqlite3-vfs-opfs.c-pp.js`**\ is an sqlite3 VFS implementation which supports Google Chrome's Origin-Private FileSystem (OPFS) as a storage layer to provide persistent storage for database files in a browser. It requires... - **`sqlite3-opfs-async-proxy.js`**\ is the asynchronous backend part of the OPFS proxy. It speaks directly to the (async) OPFS API and channels those results back to its synchronous counterpart. This file, because it must be started in its own Worker, is not part of the amalgamation. - **`api/sqlite3-api-cleanup.js`**\ The previous files do not immediately extend the library. Instead they add callback functions to be called during its bootstrapping. Some also temporarily create global objects in order to communicate their state to the files which follow them. This file cleans up any dangling globals and runs the API bootstrapping process, which is what finally executes the initialization code installed by the previous files. As of this writing, this code ensures that the previous files leave no more than a single global symbol installed. When adapting the API for non-Emscripten toolchains, this "should" be the only file where changes are needed. **Files with the extension `.c-pp.js`** are intended [to be processed with `c-pp`](#c-pp), noting that such preprocessing may be applied after all of the relevant files are concatenated. That extension is used primarily to keep the code maintainers cognisant of the fact that those files contain constructs which will not run as-is in JavaScript. The build process glues those files together, resulting in `sqlite3-api.js`, which is everything except for the `post-js-*.js` files, and `sqlite3.js`, which is the Emscripten-generated amalgamated output and includes the `post-js-*.js` parts, as well as the Emscripten-provided module loading pieces. The non-JS outlier file is `sqlite3-wasm.c`: it is a proxy for `sqlite3.c` which `#include`'s that file and adds a couple more WASM-specific helper functions, at least one of which requires access to private/static `sqlite3.c` internals. `sqlite3.wasm` is compiled from this file rather than `sqlite3.c`. The following files are part of the build process but are injected into the build-generated `sqlite3.js` along with `sqlite3-api.js`. - `extern-pre-js.js`\ Emscripten-specific header for Emscripten's `--extern-pre-js` flag. As of this writing, that file is only used for experimentation purposes and holds no code relevant to the production deliverables. - `pre-js.c-pp.js`\ Emscripten-specific header for Emscripten's `--pre-js` flag. This file is intended as a place to override certain Emscripten behavior before it starts up, but corner-case Emscripten bugs keep that from being a reality. - `post-js-header.js`\ Emscripten-specific header for the `--post-js` input. It opens up a lexical scope by starting a post-run handler for Emscripten. - `post-js-footer.js`\ Emscripten-specific footer for the `--post-js` input. This closes off the lexical scope opened by `post-js-header.js`. - `extern-post-js.c-pp.js`\ Emscripten-specific header for Emscripten's `--extern-post-js` flag. This file overwrites the Emscripten-installed `sqlite3InitModule()` function with one which, after the module is loaded, also initializes the asynchronous parts of the sqlite3 module. For example, the OPFS VFS support. <a id='c-pp'></a> Preprocessing of Source Files ------------------------------------------------------------------------ Certain files in the build require preprocessing to filter in/out parts which differ between vanilla JS builds and ES6 Module (a.k.a. esm) builds. The preprocessor application itself is in [`c-pp.c`](/file/ext/wasm/c-pp.c) and the complete technical details of such preprocessing are maintained in [`GNUMakefile`](/file/ext/wasm/GNUmakefile). |
Added ext/wasm/api/extern-post-js.c-pp.js.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | /* ^^^^ ACHTUNG: blank line at the start is necessary because Emscripten will not add a newline in some cases and we need a blank line for a sed-based kludge for the ES6 build. */ /* extern-post-js.js must be appended to the resulting sqlite3.js file. It gets its name from being used as the value for the --extern-post-js=... Emscripten flag. Note that this code, unlike most of the associated JS code, runs outside of the Emscripten-generated module init scope, in the current global scope. */ //#if target=es6-module const toExportForES6 = //#endif (function(){ /** In order to hide the sqlite3InitModule()'s resulting Emscripten module from downstream clients (and simplify our documentation by being able to elide those details), we hide that function and expose a hand-written sqlite3InitModule() to return the sqlite3 object (most of the time). Unfortunately, we cannot modify the module-loader/exporter-based impls which Emscripten installs at some point in the file above this. */ const originalInit = /* Maintenance reminder: DO NOT use `self.` here. It's correct for non-ES6 Module cases but wrong for ES6 modules because those resolve this symbol differently. */ sqlite3InitModule; if(!originalInit){ throw new Error("Expecting self.sqlite3InitModule to be defined by the Emscripten build."); } /** We need to add some state which our custom Module.locateFile() can see, but an Emscripten limitation currently prevents us from attaching it to the sqlite3InitModule function object: https://github.com/emscripten-core/emscripten/issues/18071 The only(?) current workaround is to temporarily stash this state into the global scope and delete it when sqlite3InitModule() is called. */ const initModuleState = self.sqlite3InitModuleState = Object.assign(Object.create(null),{ moduleScript: self?.document?.currentScript, isWorker: ('undefined' !== typeof WorkerGlobalScope), location: self.location, urlParams: new URL(self.location.href).searchParams }); initModuleState.debugModule = (new URL(self.location.href).searchParams).has('sqlite3.debugModule') ? (...args)=>console.warn('sqlite3.debugModule:',...args) : ()=>{}; if(initModuleState.urlParams.has('sqlite3.dir')){ initModuleState.sqlite3Dir = initModuleState.urlParams.get('sqlite3.dir') +'/'; }else if(initModuleState.moduleScript){ const li = initModuleState.moduleScript.src.split('/'); li.pop(); initModuleState.sqlite3Dir = li.join('/') + '/'; } self.sqlite3InitModule = function ff(...args){ //console.warn("Using replaced sqlite3InitModule()",self.location); return originalInit(...args).then((EmscriptenModule)=>{ if(self.window!==self && (EmscriptenModule['ENVIRONMENT_IS_PTHREAD'] || EmscriptenModule['_pthread_self'] || 'function'===typeof threadAlert || self.location.pathname.endsWith('.worker.js') )){ /** Workaround for wasmfs-generated worker, which calls this routine from each individual thread and requires that its argument be returned. All of the criteria above are fragile, based solely on inspection of the offending code, not public Emscripten details. */ return EmscriptenModule; } const s = EmscriptenModule.sqlite3; s.scriptInfo = initModuleState; //console.warn("sqlite3.scriptInfo =",s.scriptInfo); if(ff.__isUnderTest) s.__isUnderTest = true; const f = s.asyncPostInit; delete s.asyncPostInit; return f(); }).catch((e)=>{ console.error("Exception loading sqlite3 module:",e); throw e; }); }; self.sqlite3InitModule.ready = originalInit.ready; if(self.sqlite3InitModuleState.moduleScript){ const sim = self.sqlite3InitModuleState; let src = sim.moduleScript.src.split('/'); src.pop(); sim.scriptDir = src.join('/') + '/'; } initModuleState.debugModule('sqlite3InitModuleState =',initModuleState); if(0){ console.warn("Replaced sqlite3InitModule()"); console.warn("self.location.href =",self.location.href); if('undefined' !== typeof document){ console.warn("document.currentScript.src =", document?.currentScript?.src); } } /* Replace the various module exports performed by the Emscripten glue... */ if (typeof exports === 'object' && typeof module === 'object'){ module.exports = sqlite3InitModule; }else if (typeof exports === 'object'){ exports["sqlite3InitModule"] = sqlite3InitModule; } /* AMD modules get injected in a way we cannot override, so we can't handle those here. */ return self.sqlite3InitModule /* required for ESM */; })(); //#if target=es6-module export default toExportForES6; //#endif |
Deleted ext/wasm/api/extern-post-js.js.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to ext/wasm/api/post-js-header.js.
︙ | ︙ | |||
15 16 17 18 19 20 21 | - post-js-header.js (this file) - sqlite3-api-prologue.js => Bootstrapping bits to attach the rest to - common/whwasmutil.js => Replacements for much of Emscripten's glue - jaccwaby/jaccwabyt.js => Jaccwabyt (C/JS struct binding) - sqlite3-api-glue.js => glues previous parts together - sqlite3-api-oo.js => SQLite3 OO API #1 - sqlite3-api-worker1.js => Worker-based API | > | | 15 16 17 18 19 20 21 22 23 24 25 26 | - post-js-header.js (this file) - sqlite3-api-prologue.js => Bootstrapping bits to attach the rest to - common/whwasmutil.js => Replacements for much of Emscripten's glue - jaccwaby/jaccwabyt.js => Jaccwabyt (C/JS struct binding) - sqlite3-api-glue.js => glues previous parts together - sqlite3-api-oo.js => SQLite3 OO API #1 - sqlite3-api-worker1.js => Worker-based API - sqlite3-vfs-helper.js => Internal-use utilities for... - sqlite3-vfs-opfs.js => OPFS VFS - sqlite3-api-cleanup.js => final API cleanup - post-js-footer.js => closes this postRun() function */ |
Added ext/wasm/api/pre-js.c-pp.js.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | /** BEGIN FILE: api/pre-js.js This file is intended to be prepended to the sqlite3.js build using Emscripten's --pre-js=THIS_FILE flag (or equivalent). */ // See notes in extern-post-js.js const sqlite3InitModuleState = self.sqlite3InitModuleState || Object.create(null); delete self.sqlite3InitModuleState; sqlite3InitModuleState.debugModule('self.location =',self.location); /** This custom locateFile() tries to figure out where to load `path` from. The intent is to provide a way for foo/bar/X.js loaded from a Worker constructor or importScripts() to be able to resolve foo/bar/X.wasm (in the latter case, with some help): 1) If URL param named the same as `path` is set, it is returned. 2) If sqlite3InitModuleState.sqlite3Dir is set, then (thatName + path) is returned (note that it's assumed to end with '/'). 3) If this code is running in the main UI thread AND it was loaded from a SCRIPT tag, the directory part of that URL is used as the prefix. (This form of resolution unfortunately does not function for scripts loaded via importScripts().) 4) If none of the above apply, (prefix+path) is returned. */ Module['locateFile'] = function(path, prefix) { //#if target=es6-module return new URL(path, import.meta.url).href; //#else 'use strict'; let theFile; const up = this.urlParams; if(up.has(path)){ theFile = up.get(path); }else if(this.sqlite3Dir){ theFile = this.sqlite3Dir + path; }else if(this.scriptDir){ theFile = this.scriptDir + path; }else{ theFile = prefix + path; } sqlite3InitModuleState.debugModule( "locateFile(",arguments[0], ',', arguments[1],")", 'sqlite3InitModuleState.scriptDir =',this.scriptDir, 'up.entries() =',Array.from(up.entries()), "result =", theFile ); return theFile; //#endif /* SQLITE_JS_EMS */ }.bind(sqlite3InitModuleState); /** Bug warning: a custom Module.instantiateWasm() does not work in WASMFS builds: https://github.com/emscripten-core/emscripten/issues/17951 In such builds we must disable this. */ const xNameOfInstantiateWasm = true ? 'instantiateWasm' : 'emscripten-bug-17951'; Module[xNameOfInstantiateWasm] = function callee(imports,onSuccess){ imports.env.foo = function(){}; const uri = Module.locateFile( callee.uri, ( ('undefined'===typeof scriptDirectory/*var defined by Emscripten glue*/) ? "" : scriptDirectory) ); sqlite3InitModuleState.debugModule( "instantiateWasm() uri =", uri ); const wfetch = ()=>fetch(uri, {credentials: 'same-origin'}); const loadWasm = WebAssembly.instantiateStreaming ? async ()=>{ return WebAssembly.instantiateStreaming(wfetch(), imports) .then((arg)=>onSuccess(arg.instance, arg.module)); } : async ()=>{ // Safari < v15 return wfetch() .then(response => response.arrayBuffer()) .then(bytes => WebAssembly.instantiate(bytes, imports)) .then((arg)=>onSuccess(arg.instance, arg.module)); }; loadWasm(); return {}; }; /* It is literally impossible to reliably get the name of _this_ script at runtime, so impossible to derive X.wasm from script name X.js. Thus we need, at build-time, to redefine Module[xNameOfInstantiateWasm].uri by appending it to a build-specific copy of this file with the name of the wasm file. This is apparently why Emscripten hard-codes the name of the wasm file into their glue scripts. */ Module[xNameOfInstantiateWasm].uri = 'sqlite3.wasm'; /* END FILE: api/pre-js.js, noting that the build process may add a line after this one to change the above .uri to a build-specific one. */ |
Deleted ext/wasm/api/pre-js.js.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to ext/wasm/api/sqlite3-api-cleanup.js.
︙ | ︙ | |||
18 19 20 21 22 23 24 | 'use strict'; if('undefined' !== typeof Module){ // presumably an Emscripten build /** Install a suitable default configuration for sqlite3ApiBootstrap(). */ const SABC = Object.assign( Object.create(null), { | < < | | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 'use strict'; if('undefined' !== typeof Module){ // presumably an Emscripten build /** Install a suitable default configuration for sqlite3ApiBootstrap(). */ const SABC = Object.assign( Object.create(null), { exports: Module['asm'], memory: Module.wasmMemory /* gets set if built with -sIMPORT_MEMORY */ }, self.sqlite3ApiConfig || {} ); /** For current (2022-08-22) purposes, automatically call sqlite3ApiBootstrap(). That decision will be revisited at some point, as we really want client code to be able to call this to configure certain parts. Clients may modify |
︙ | ︙ | |||
54 55 56 57 58 59 60 | } if(self.location && +self.location.port > 1024){ console.warn("Installing sqlite3 bits as global S for local dev/test purposes."); self.S = sqlite3; } | < < | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | } if(self.location && +self.location.port > 1024){ console.warn("Installing sqlite3 bits as global S for local dev/test purposes."); self.S = sqlite3; } Module.sqlite3 = sqlite3 /* Needed for customized sqlite3InitModule() to be able to pass the sqlite3 object off to the client. */; }else{ console.warn("This is not running in an Emscripten module context, so", "self.sqlite3ApiBootstrap() is _not_ being called due to lack", "of config info for the WASM environment.", "It must be called manually."); } |
Changes to ext/wasm/api/sqlite3-api-glue.js.
︙ | ︙ | |||
20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 'use strict'; const toss = (...args)=>{throw new Error(args.join(' '))}; const toss3 = sqlite3.SQLite3Error.toss; const capi = sqlite3.capi, wasm = sqlite3.wasm, util = sqlite3.util; self.WhWasmUtilInstaller(wasm); delete self.WhWasmUtilInstaller; /** Install JS<->C struct bindings for the non-opaque struct types we need... */ sqlite3.StructBinder = self.Jaccwabyt({ heap: 0 ? wasm.memory : wasm.heap8u, alloc: wasm.alloc, dealloc: wasm.dealloc, | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < | > | | < | | > > | > > | | > | < < < | > > > > > > > > | | < > > > > > > > > | > > | < | | | | > | < > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > | > | | | > | | | | > > | | | > > > > > > > > > > > > > > > > > > > > > > > > > > < < < < < < < < < < < < < > > > > > > > > > > > | < < < < > > | > | | > | > | < | < | | > > | < < < < | < < < | < < < < | | < < < < | | < > | < < < | | > > | | > > > > | | | > | < < < < | < < | < < > > > > > | > > > > > > | | | | | | < < | | > | < < < < > | > > | > | > | | > > | | < < > | > | > > > > | | | | | > | | | < | > > | < | < > | > | < > | | | < < | > | < | > | | < | > > > > > > > < < | < < < < < | > | < < < < | > | > > > > > | > > > > > | | > > > | > > > | | < > > | < | < > | < | < | > | | | > > | < | > | | < < < < | | | | > | > > | | > > > > > | < | < | > > > | > > > | | | | < < | > > > > > > > | | > > > | | > > > | > | | | | | < | < < < < < < < < < < < < | | > > | | | | | < | > > | | > > > > | < | < | > | < | < < < | < > > | < < > > > > > | < < < | > > > > < < < < < | | < < < | < < > | | | > > > > < < < < < | | > < < < | < < < < < < < < < < < < < < < < | | < | < < < < < < < | < < | < < < < < | < < < | < < > | > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | | | | | | | | | | | | | | | | | | > | | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < > > > | > > | > | > | > | | > > > | | | | < < | | > > > > > | > > > > > > > > > > > > > > > > | | < | | | | | | | | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 | 'use strict'; const toss = (...args)=>{throw new Error(args.join(' '))}; const toss3 = sqlite3.SQLite3Error.toss; const capi = sqlite3.capi, wasm = sqlite3.wasm, util = sqlite3.util; self.WhWasmUtilInstaller(wasm); delete self.WhWasmUtilInstaller; { /** Find a mapping for SQLITE_WASM_DEALLOC, which the API guarantees is a WASM pointer to the same underlying function as wasm.dealloc() (noting that wasm.dealloc() is permitted to be a JS wrapper around the WASM function). There is unfortunately no O(1) algorithm for finding this pointer: we have to walk the WASM indirect function table to find it. However, experience indicates that that particular function is always very close to the front of the table (it's been entry #3 in all relevant tests). */ const dealloc = wasm.exports[sqlite3.config.deallocExportName]; const nFunc = wasm.functionTable().length; let i; for(i = 0; i < nFunc; ++i){ const e = wasm.functionEntry(i); if(dealloc === e){ capi.SQLITE_WASM_DEALLOC = i; break; } } if(dealloc !== wasm.functionEntry(capi.SQLITE_WASM_DEALLOC)){ toss("Internal error: cannot find function pointer for SQLITE_WASM_DEALLOC."); } } /** Signatures for the WASM-exported C-side functions. Each entry is an array with 2+ elements: [ "c-side name", "result type" (wasm.xWrap() syntax), [arg types in xWrap() syntax] // ^^^ this needn't strictly be an array: it can be subsequent // elements instead: [x,y,z] is equivalent to x,y,z ] Note that support for the API-specific data types in the result/argument type strings gets plugged in at a later phase in the API initialization process. */ wasm.bindingSignatures = [ // Please keep these sorted by function name! ["sqlite3_aggregate_context","void*", "sqlite3_context*", "int"], /* sqlite3_bind_blob() and sqlite3_bind_text() have hand-written bindings to permit more flexible inputs. */ ["sqlite3_bind_double","int", "sqlite3_stmt*", "int", "f64"], ["sqlite3_bind_int","int", "sqlite3_stmt*", "int", "int"], ["sqlite3_bind_null",undefined, "sqlite3_stmt*", "int"], ["sqlite3_bind_parameter_count", "int", "sqlite3_stmt*"], ["sqlite3_bind_parameter_index","int", "sqlite3_stmt*", "string"], ["sqlite3_bind_pointer", "int", "sqlite3_stmt*", "int", "*", "string:static", "*"], ["sqlite3_busy_handler","int", [ "sqlite3*", new wasm.xWrap.FuncPtrAdapter({ signature: 'i(pi)', contextKey: (argv,argIndex)=>argv[0/* sqlite3* */] }), "*" ]], ["sqlite3_busy_timeout","int", "sqlite3*", "int"], /*[sqlite3_close_v2() is implemented by hand to perform some extra work. "sqlite3_close_v2", "int", "sqlite3*"],*/ ["sqlite3_changes", "int", "sqlite3*"], ["sqlite3_clear_bindings","int", "sqlite3_stmt*"], ["sqlite3_collation_needed", "int", "sqlite3*", "*", "*"/*=>v(ppis)*/], ["sqlite3_column_blob","*", "sqlite3_stmt*", "int"], ["sqlite3_column_bytes","int", "sqlite3_stmt*", "int"], ["sqlite3_column_count", "int", "sqlite3_stmt*"], ["sqlite3_column_double","f64", "sqlite3_stmt*", "int"], ["sqlite3_column_int","int", "sqlite3_stmt*", "int"], ["sqlite3_column_name","string", "sqlite3_stmt*", "int"], ["sqlite3_column_text","string", "sqlite3_stmt*", "int"], ["sqlite3_column_type","int", "sqlite3_stmt*", "int"], ["sqlite3_column_value","sqlite3_value*", "sqlite3_stmt*", "int"], ["sqlite3_compileoption_get", "string", "int"], ["sqlite3_compileoption_used", "int", "string"], ["sqlite3_complete", "int", "string:flexible"], ["sqlite3_context_db_handle", "sqlite3*", "sqlite3_context*"], /* sqlite3_create_function(), sqlite3_create_function_v2(), and sqlite3_create_window_function() use hand-written bindings to simplify handling of their function-type arguments. */ /* sqlite3_create_collation() and sqlite3_create_collation_v2() use hand-written bindings to simplify passing of the callback function. ["sqlite3_create_collation", "int", "sqlite3*", "string", "int",//SQLITE_UTF8 is the only legal value "*", "*"], ["sqlite3_create_collation_v2", "int", "sqlite3*", "string", "int",//SQLITE_UTF8 is the only legal value "*", "*", "*"], */ ["sqlite3_data_count", "int", "sqlite3_stmt*"], ["sqlite3_db_filename", "string", "sqlite3*", "string"], ["sqlite3_db_handle", "sqlite3*", "sqlite3_stmt*"], ["sqlite3_db_name", "string", "sqlite3*", "int"], ["sqlite3_db_status", "int", "sqlite3*", "int", "*", "*", "int"], ["sqlite3_errcode", "int", "sqlite3*"], ["sqlite3_errmsg", "string", "sqlite3*"], ["sqlite3_error_offset", "int", "sqlite3*"], ["sqlite3_errstr", "string", "int"], ["sqlite3_exec", "int", [ "sqlite3*", "string:flexible", new wasm.xWrap.FuncPtrAdapter({ signature: 'i(pipp)', bindScope: 'transient', callProxy: (callback)=>{ let aNames; return (pVoid, nCols, pColVals, pColNames)=>{ try { const aVals = wasm.cArgvToJs(nCols, pColVals); if(!aNames) aNames = wasm.cArgvToJs(nCols, pColNames); return callback(aVals, aNames) | 0; }catch(e){ /* If we set the db error state here, the higher-level exec() call replaces it with its own, so we have no way of reporting the exception message except the console. We must not propagate exceptions through the C API. Though we make an effort to report OOM here, sqlite3_exec() translates that into SQLITE_ABORT as well. */ return e.resultCode || capi.SQLITE_ERROR; } } } }), "*", "**" ]], ["sqlite3_expanded_sql", "string", "sqlite3_stmt*"], ["sqlite3_extended_errcode", "int", "sqlite3*"], ["sqlite3_extended_result_codes", "int", "sqlite3*", "int"], ["sqlite3_file_control", "int", "sqlite3*", "string", "int", "*"], ["sqlite3_finalize", "int", "sqlite3_stmt*"], ["sqlite3_free", undefined,"*"], ["sqlite3_get_auxdata", "*", "sqlite3_context*", "int"], ["sqlite3_initialize", undefined], /*["sqlite3_interrupt", undefined, "sqlite3*" ^^^ we cannot actually currently support this because JS is single-threaded and we don't have a portable way to access a DB from 2 SharedWorkers concurrently. ],*/ ["sqlite3_keyword_count", "int"], ["sqlite3_keyword_name", "int", ["int", "**", "*"]], ["sqlite3_keyword_check", "int", ["string", "int"]], ["sqlite3_libversion", "string"], ["sqlite3_libversion_number", "int"], ["sqlite3_limit", "int", ["sqlite3*", "int", "int"]], ["sqlite3_malloc", "*","int"], ["sqlite3_open", "int", "string", "*"], ["sqlite3_open_v2", "int", "string", "*", "int", "string"], /* sqlite3_prepare_v2() and sqlite3_prepare_v3() are handled separately due to us requiring two different sets of semantics for those, depending on how their SQL argument is provided. */ /* sqlite3_randomness() uses a hand-written wrapper to extend the range of supported argument types. */ ["sqlite3_progress_handler", undefined, [ "sqlite3*", "int", new wasm.xWrap.FuncPtrAdapter({ name: 'xProgressHandler', signature: 'i(p)', bindScope: 'context', contextKey: (argv,argIndex)=>argv[0/* sqlite3* */] }), "*" ]], ["sqlite3_realloc", "*","*","int"], ["sqlite3_reset", "int", "sqlite3_stmt*"], ["sqlite3_result_blob", undefined, "sqlite3_context*", "*", "int", "*"], ["sqlite3_result_double", undefined, "sqlite3_context*", "f64"], ["sqlite3_result_error", undefined, "sqlite3_context*", "string", "int"], ["sqlite3_result_error_code", undefined, "sqlite3_context*", "int"], ["sqlite3_result_error_nomem", undefined, "sqlite3_context*"], ["sqlite3_result_error_toobig", undefined, "sqlite3_context*"], ["sqlite3_result_int", undefined, "sqlite3_context*", "int"], ["sqlite3_result_null", undefined, "sqlite3_context*"], ["sqlite3_result_pointer", undefined, "sqlite3_context*", "*", "string:static", "*"], ["sqlite3_result_subtype", undefined, "sqlite3_value*", "int"], ["sqlite3_result_text", undefined, "sqlite3_context*", "string", "int", "*"], ["sqlite3_result_zeroblob", undefined, "sqlite3_context*", "int"], ["sqlite3_set_authorizer", "int", [ "sqlite3*", new wasm.xWrap.FuncPtrAdapter({ name: "sqlite3_set_authorizer::xAuth", signature: "i(pi"+"ssss)", contextKey: (argv, argIndex)=>argv[0/*(sqlite3*)*/], callProxy: (callback)=>{ return (pV, iCode, s0, s1, s2, s3)=>{ try{ s0 = s0 && wasm.cstrToJs(s0); s1 = s1 && wasm.cstrToJs(s1); s2 = s2 && wasm.cstrToJs(s2); s3 = s3 && wasm.cstrToJs(s3); return callback(pV, iCode, s0, s1, s2, s3) || 0; }catch(e){ return e.resultCode || capi.SQLITE_ERROR; } } } }), "*"/*pUserData*/ ]], ["sqlite3_set_auxdata", undefined, [ "sqlite3_context*", "int", "*", new wasm.xWrap.FuncPtrAdapter({ name: 'xDestroyAuxData', signature: 'v(*)', contextKey: (argv, argIndex)=>argv[0/* sqlite3_context* */] }) ]], ["sqlite3_shutdown", undefined], ["sqlite3_sourceid", "string"], ["sqlite3_sql", "string", "sqlite3_stmt*"], ["sqlite3_status", "int", "int", "*", "*", "int"], ["sqlite3_step", "int", "sqlite3_stmt*"], ["sqlite3_stmt_isexplain", "int", ["sqlite3_stmt*"]], ["sqlite3_stmt_readonly", "int", ["sqlite3_stmt*"]], ["sqlite3_stmt_status", "int", "sqlite3_stmt*", "int", "int"], ["sqlite3_strglob", "int", "string","string"], ["sqlite3_stricmp", "int", "string", "string"], ["sqlite3_strlike", "int", "string", "string","int"], ["sqlite3_strnicmp", "int", "string", "string", "int"], ["sqlite3_table_column_metadata", "int", "sqlite3*", "string", "string", "string", "**", "**", "*", "*", "*"], ["sqlite3_total_changes", "int", "sqlite3*"], ["sqlite3_trace_v2", "int", [ "sqlite3*", "int", new wasm.xWrap.FuncPtrAdapter({ name: 'sqlite3_trace_v2::callback', signature: 'i(ippp)', contextKey: (argv,argIndex)=>argv[0/* sqlite3* */] }), "*" ]], ["sqlite3_txn_state", "int", ["sqlite3*","string"]], /* Note that sqlite3_uri_...() have very specific requirements for their first C-string arguments, so we cannot perform any value conversion on those. */ ["sqlite3_uri_boolean", "int", "sqlite3_filename", "string", "int"], ["sqlite3_uri_key", "string", "sqlite3_filename", "int"], ["sqlite3_uri_parameter", "string", "sqlite3_filename", "string"], ["sqlite3_user_data","void*", "sqlite3_context*"], ["sqlite3_value_blob", "*", "sqlite3_value*"], ["sqlite3_value_bytes","int", "sqlite3_value*"], ["sqlite3_value_double","f64", "sqlite3_value*"], ["sqlite3_value_dup", "sqlite3_value*", "sqlite3_value*"], ["sqlite3_value_free", undefined, "sqlite3_value*"], ["sqlite3_value_frombind", "int", "sqlite3_value*"], ["sqlite3_value_int","int", "sqlite3_value*"], ["sqlite3_value_nochange", "int", "sqlite3_value*"], ["sqlite3_value_numeric_type", "int", "sqlite3_value*"], ["sqlite3_value_pointer", "*", "sqlite3_value*", "string:static"], ["sqlite3_value_subtype", "int", "sqlite3_value*"], ["sqlite3_value_text", "string", "sqlite3_value*"], ["sqlite3_value_type", "int", "sqlite3_value*"], ["sqlite3_vfs_find", "*", "string"], ["sqlite3_vfs_register", "int", "sqlite3_vfs*", "int"], ["sqlite3_vfs_unregister", "int", "sqlite3_vfs*"] ]/*wasm.bindingSignatures*/; if(false && wasm.compileOptionUsed('SQLITE_ENABLE_NORMALIZE')){ /* ^^^ "the problem" is that this is an option feature and the build-time function-export list does not currently take optional features into account. */ wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]); } /** Functions which require BigInt (int64) support are separated from the others because we need to conditionally bind them or apply dummy impls, depending on the capabilities of the environment. Note that not all of these functions directly require int64 but are only for use with APIs which require int64. For example, the vtab-related functions. */ wasm.bindingSignatures.int64 = [ ["sqlite3_bind_int64","int", ["sqlite3_stmt*", "int", "i64"]], ["sqlite3_changes64","i64", ["sqlite3*"]], ["sqlite3_column_int64","i64", ["sqlite3_stmt*", "int"]], ["sqlite3_create_module", "int", ["sqlite3*","string","sqlite3_module*","*"]], ["sqlite3_create_module_v2", "int", ["sqlite3*","string","sqlite3_module*","*","*"]], ["sqlite3_declare_vtab", "int", ["sqlite3*", "string:flexible"]], ["sqlite3_deserialize", "int", "sqlite3*", "string", "*", "i64", "i64", "int"] /* Careful! Short version: de/serialize() are problematic because they might use a different allocator than the user for managing the deserialized block. de/serialize() are ONLY safe to use with sqlite3_malloc(), sqlite3_free(), and its 64-bit variants. */, ["sqlite3_drop_modules", "int", ["sqlite3*", "**"]], ["sqlite3_last_insert_rowid", "i64", ["sqlite3*"]], ["sqlite3_malloc64", "*","i64"], ["sqlite3_msize", "i64", "*"], ["sqlite3_overload_function", "int", ["sqlite3*","string","int"]], ["sqlite3_realloc64", "*","*", "i64"], ["sqlite3_result_int64", undefined, "*", "i64"], ["sqlite3_result_zeroblob64", "int", "*", "i64"], ["sqlite3_serialize","*", "sqlite3*", "string", "*", "int"], ["sqlite3_set_last_insert_rowid", undefined, ["sqlite3*", "i64"]], ["sqlite3_status64", "int", "int", "*", "*", "int"], ["sqlite3_total_changes64", "i64", ["sqlite3*"]], ["sqlite3_uri_int64", "i64", ["sqlite3_filename", "string", "i64"]], ["sqlite3_value_int64","i64", "sqlite3_value*"], ["sqlite3_vtab_collation","string","sqlite3_index_info*","int"], ["sqlite3_vtab_distinct","int", "sqlite3_index_info*"], ["sqlite3_vtab_in","int", "sqlite3_index_info*", "int", "int"], ["sqlite3_vtab_in_first", "int", "sqlite3_value*", "**"], ["sqlite3_vtab_in_next", "int", "sqlite3_value*", "**"], /*["sqlite3_vtab_config" is variadic and requires a hand-written proxy.] */ ["sqlite3_vtab_nochange","int", "sqlite3_context*"], ["sqlite3_vtab_on_conflict","int", "sqlite3*"], ["sqlite3_vtab_rhs_value","int", "sqlite3_index_info*", "int", "**"] ]; // Add session/changeset APIs... if(wasm.bigIntEnabled && !!wasm.exports.sqlite3changegroup_add){ /* ACHTUNG: 2022-12-23: the session/changeset API bindings are COMPLETELY UNTESTED. */ /** FuncPtrAdapter options for session-related callbacks with the native signature "i(ps)". This proxy converts the 2nd argument from a C string to a JS string before passing the arguments on to the client-provided JS callback. */ const __ipsProxy = { signature: 'i(ps)', callProxy:(callback)=>{ return (p,s)=>{ try{return callback(p, wasm.cstrToJs(s)) | 0} catch(e){return e.resultCode || capi.SQLITE_ERROR} } } }; wasm.bindingSignatures.int64.push(...[ ['sqlite3changegroup_add', 'int', ['sqlite3_changegroup*', 'int', 'void*']], ['sqlite3changegroup_add_strm', 'int', [ 'sqlite3_changegroup*', new wasm.xWrap.FuncPtrAdapter({ name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' }), 'void*' ]], ['sqlite3changegroup_delete', undefined, ['sqlite3_changegroup*']], ['sqlite3changegroup_new', 'int', ['**']], ['sqlite3changegroup_output', 'int', ['sqlite3_changegroup*', 'int*', '**']], ['sqlite3changegroup_output_strm', 'int', [ 'sqlite3_changegroup*', new wasm.xWrap.FuncPtrAdapter({ name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient' }), 'void*' ]], ['sqlite3changeset_apply', 'int', [ 'sqlite3*', 'int', 'void*', new wasm.xWrap.FuncPtrAdapter({ name: 'xFilter', bindScope: 'transient', ...__ipsProxy }), new wasm.xWrap.FuncPtrAdapter({ name: 'xConflict', signature: 'i(pip)', bindScope: 'transient' }), 'void*' ]], ['sqlite3changeset_apply_strm', 'int', [ 'sqlite3*', new wasm.xWrap.FuncPtrAdapter({ name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' }), 'void*', new wasm.xWrap.FuncPtrAdapter({ name: 'xFilter', bindScope: 'transient', ...__ipsProxy }), new wasm.xWrap.FuncPtrAdapter({ name: 'xConflict', signature: 'i(pip)', bindScope: 'transient' }), 'void*' ]], ['sqlite3changeset_apply_v2', 'int', [ 'sqlite3*', 'int', 'void*', new wasm.xWrap.FuncPtrAdapter({ name: 'xFilter', bindScope: 'transient', ...__ipsProxy }), new wasm.xWrap.FuncPtrAdapter({ name: 'xConflict', signature: 'i(pip)', bindScope: 'transient' }), 'void*', '**', 'int*', 'int' ]], ['sqlite3changeset_apply_v2_strm', 'int', [ 'sqlite3*', new wasm.xWrap.FuncPtrAdapter({ name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' }), 'void*', new wasm.xWrap.FuncPtrAdapter({ name: 'xFilter', bindScope: 'transient', ...__ipsProxy }), new wasm.xWrap.FuncPtrAdapter({ name: 'xConflict', signature: 'i(pip)', bindScope: 'transient' }), 'void*', '**', 'int*', 'int' ]], ['sqlite3changeset_concat', 'int', ['int','void*', 'int', 'void*', 'int*', '**']], ['sqlite3changeset_concat_strm', 'int', [ new wasm.xWrap.FuncPtrAdapter({ name: 'xInputA', signature: 'i(ppp)', bindScope: 'transient' }), 'void*', new wasm.xWrap.FuncPtrAdapter({ name: 'xInputB', signature: 'i(ppp)', bindScope: 'transient' }), 'void*', new wasm.xWrap.FuncPtrAdapter({ name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient' }), 'void*' ]], ['sqlite3changeset_conflict', 'int', ['sqlite3_changeset_iter*', 'int', '**']], ['sqlite3changeset_finalize', 'int', ['sqlite3_changeset_iter*']], ['sqlite3changeset_fk_conflicts', 'int', ['sqlite3_changeset_iter*', 'int*']], ['sqlite3changeset_invert', 'int', ['int', 'void*', 'int*', '**']], ['sqlite3changeset_invert_strm', 'int', [ new wasm.xWrap.FuncPtrAdapter({ name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' }), 'void*', new wasm.xWrap.FuncPtrAdapter({ name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient' }), 'void*' ]], ['sqlite3changeset_new', 'int', ['sqlite3_changeset_iter*', 'int', '**']], ['sqlite3changeset_next', 'int', ['sqlite3_changeset_iter*']], ['sqlite3changeset_old', 'int', ['sqlite3_changeset_iter*', 'int', '**']], ['sqlite3changeset_op', 'int', [ 'sqlite3_changeset_iter*', '**', 'int*', 'int*','int*' ]], ['sqlite3changeset_pk', 'int', ['sqlite3_changeset_iter*', '**', 'int*']], ['sqlite3changeset_start', 'int', ['**', 'int', '*']], ['sqlite3changeset_start_strm', 'int', [ '**', new wasm.xWrap.FuncPtrAdapter({ name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' }), 'void*' ]], ['sqlite3changeset_start_v2', 'int', ['**', 'int', '*', 'int']], ['sqlite3changeset_start_v2_strm', 'int', [ '**', new wasm.xWrap.FuncPtrAdapter({ name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' }), 'void*', 'int' ]], ['sqlite3session_attach', 'int', ['sqlite3_session*', 'string']], ['sqlite3session_changeset', 'int', ['sqlite3_session*', 'int*', '**']], ['sqlite3session_changeset_size', 'i64', ['sqlite3_session*']], ['sqlite3session_changeset_strm', 'int', [ 'sqlite3_session*', new wasm.xWrap.FuncPtrAdapter({ name: 'xOutput', signature: 'i(ppp)', bindScope: 'transient' }), 'void*' ]], ['sqlite3session_config', 'int', ['int', 'void*']], ['sqlite3session_create', 'int', ['sqlite3*', 'string', '**']], //sqlite3session_delete() is bound manually ['sqlite3session_diff', 'int', ['sqlite3_session*', 'string', 'string', '**']], ['sqlite3session_enable', 'int', ['sqlite3_session*', 'int']], ['sqlite3session_indirect', 'int', ['sqlite3_session*', 'int']], ['sqlite3session_isempty', 'int', ['sqlite3_session*']], ['sqlite3session_memory_used', 'i64', ['sqlite3_session*']], ['sqlite3session_object_config', 'int', ['sqlite3_session*', 'int', 'void*']], ['sqlite3session_patchset', 'int', ['sqlite3_session*', '*', '**']], ['sqlite3session_patchset_strm', 'int', [ 'sqlite3_session*', new wasm.xWrap.FuncPtrAdapter({ name: 'xOutput', signature: 'i(ppp)', bindScope: 'transient' }), 'void*' ]], ['sqlite3session_table_filter', undefined, [ 'sqlite3_session*', new wasm.xWrap.FuncPtrAdapter({ name: 'xFilter', ...__ipsProxy, contextKey: (argv,argIndex)=>argv[0/* (sqlite3_session*) */] }), '*' ]] ]); }/*session/changeset APIs*/ /** Functions which are intended solely for API-internal use by the WASM components, not client code. These get installed into sqlite3.wasm. Some of them get exposed to clients via variants named sqlite3_js_...(). */ wasm.bindingSignatures.wasm = [ ["sqlite3_wasm_db_reset", "int", "sqlite3*"], ["sqlite3_wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"], ["sqlite3_wasm_vfs_create_file", "int", "sqlite3_vfs*","string","*", "int"], ["sqlite3_wasm_vfs_unlink", "int", "sqlite3_vfs*","string"] ]; /** Install JS<->C struct bindings for the non-opaque struct types we need... */ sqlite3.StructBinder = self.Jaccwabyt({ heap: 0 ? wasm.memory : wasm.heap8u, alloc: wasm.alloc, dealloc: wasm.dealloc, bigIntEnabled: wasm.bigIntEnabled, memberPrefix: /* Never change this: this prefix is baked into any amount of code and client-facing docs. */ '$' }); delete self.Jaccwabyt; {/* Convert Arrays and certain TypedArrays to strings for 'string:flexible'-type arguments */ const __xString = wasm.xWrap.argAdapter('string'); wasm.xWrap.argAdapter( 'string:flexible', (v)=>__xString(util.flexibleString(v)) ); /** The 'string:static' argument adapter treats its argument as either... - WASM pointer: assumed to be a long-lived C-string which gets returned as-is. - Anything else: gets coerced to a JS string for use as a map key. If a matching entry is found (as described next), it is returned, else wasm.allocCString() is used to create a a new string, map its pointer to (''+v) for the remainder of the application's life, and returns that pointer value for this call and all future calls which are passed a string-equivalent argument. Use case: sqlite3_bind_pointer() and sqlite3_result_pointer() call for "a static string and preferably a string literal". This converter is used to ensure that the string value seen by those functions is long-lived and behaves as they need it to. */ wasm.xWrap.argAdapter( 'string:static', function(v){ if(wasm.isPtr(v)) return v; v = ''+v; let rc = this[v]; return rc || (this[v] = wasm.allocCString(v)); }.bind(Object.create(null)) ); }/* special-case string-type argument conversions */ if(1){// wasm.xWrap() bindings... /** Add some descriptive xWrap() aliases for '*' intended to (A) initially improve readability/correctness of wasm.bindingSignatures and (B) provide automatic conversion from higher-level representations, e.g. capi.sqlite3_vfs to `sqlite3_vfs*` via capi.sqlite3_vfs.pointer. */ const aPtr = wasm.xWrap.argAdapter('*'); const nilType = function(){}; wasm.xWrap.argAdapter('sqlite3_filename', aPtr) ('sqlite3_context*', aPtr) ('sqlite3_value*', aPtr) ('void*', aPtr) ('sqlite3_changegroup*', aPtr) ('sqlite3_changeset_iter*', aPtr) //('sqlite3_rebaser*', aPtr) ('sqlite3_session*', aPtr) ('sqlite3_stmt*', (v)=> aPtr((v instanceof (sqlite3?.oo1?.Stmt || nilType)) ? v.pointer : v)) ('sqlite3*', (v)=> aPtr((v instanceof (sqlite3?.oo1?.DB || nilType)) ? v.pointer : v)) ('sqlite3_index_info*', (v)=> aPtr((v instanceof (capi.sqlite3_index_info || nilType)) ? v.pointer : v)) ('sqlite3_module*', (v)=> aPtr((v instanceof (capi.sqlite3_module || nilType)) ? v.pointer : v)) /** `sqlite3_vfs*`: - v is-a string: use the result of sqlite3_vfs_find(v) but throw if it returns 0. - v is-a capi.sqlite3_vfs: use v.pointer. - Else return the same as the `'*'` argument conversion. */ ('sqlite3_vfs*', (v)=>{ if('string'===typeof v){ /* A NULL sqlite3_vfs pointer will be treated as the default VFS in many contexts. We specifically do not want that behavior here. */ return capi.sqlite3_vfs_find(v) || sqlite3.SQLite3Error.toss( capi.SQLITE_NOTFOUND, "Unknown sqlite3_vfs name:", v ); } return aPtr((v instanceof (capi.sqlite3_vfs || nilType)) ? v.pointer : v); }); const __xRcPtr = wasm.xWrap.resultAdapter('*'); wasm.xWrap.resultAdapter('sqlite3*', __xRcPtr) ('sqlite3_context*', __xRcPtr) ('sqlite3_stmt*', __xRcPtr) ('sqlite3_value*', __xRcPtr) ('sqlite3_vfs*', __xRcPtr) ('void*', __xRcPtr); /** Populate api object with sqlite3_...() by binding the "raw" wasm exports into type-converting proxies using wasm.xWrap(). */ for(const e of wasm.bindingSignatures){ capi[e[0]] = wasm.xWrap.apply(null, e); } for(const e of wasm.bindingSignatures.wasm){ wasm[e[0]] = wasm.xWrap.apply(null, e); } /* For C API functions which cannot work properly unless wasm.bigIntEnabled is true, install a bogus impl which throws if called when bigIntEnabled is false. The alternative would be to elide these functions altogether, which seems likely to cause more confusion. */ const fI64Disabled = function(fname){ return ()=>toss(fname+"() is unavailable due to lack", "of BigInt support in this build."); }; for(const e of wasm.bindingSignatures.int64){ capi[e[0]] = wasm.bigIntEnabled ? wasm.xWrap.apply(null, e) : fI64Disabled(e[0]); } /* There's no need to expose bindingSignatures to clients, implicitly making it part of the public interface. */ delete wasm.bindingSignatures; if(wasm.exports.sqlite3_wasm_db_error){ const __db_err = wasm.xWrap( 'sqlite3_wasm_db_error', 'int', 'sqlite3*', 'int', 'string' ); /** Sets the given db's error state. Accepts: - (sqlite3*, int code, string msg) - (sqlite3*, Error e [,string msg = ''+e]) If passed a WasmAllocError, the message is ignored and the result code is SQLITE_NOMEM. If passed any other Error type, the result code defaults to SQLITE_ERROR unless the Error object has a resultCode property, in which case that is used (e.g. SQLite3Error has that). If passed a non-WasmAllocError exception, the message string defaults to theError.message. Returns the resulting code. Pass (pDb,0,0) to clear the error state. */ util.sqlite3_wasm_db_error = function(pDb, resultCode, message){ if(resultCode instanceof sqlite3.WasmAllocError){ resultCode = capi.SQLITE_NOMEM; message = 0 /*avoid allocating message string*/; }else if(resultCode instanceof Error){ message = message || ''+resultCode; resultCode = (resultCode.resultCode || capi.SQLITE_ERROR); } return __db_err(pDb, resultCode, message); }; }else{ util.sqlite3_wasm_db_error = function(pDb,errCode,msg){ console.warn("sqlite3_wasm_db_error() is not exported.",arguments); return errCode; }; } }/*xWrap() bindings*/; /** Internal helper to assist in validating call argument counts in the hand-written sqlite3_xyz() wrappers. We do this only for consistency with non-special-case wrappings. */ const __dbArgcMismatch = (pDb,f,n)=>{ return sqlite3.util.sqlite3_wasm_db_error(pDb, capi.SQLITE_MISUSE, f+"() requires "+n+" argument"+ (1===n?"":'s')+"."); }; /** Code duplication reducer for functions which take an encoding argument and require SQLITE_UTF8. Sets the db error code to SQLITE_FORMAT and returns that code. */ const __errEncoding = (pDb)=>{ return util.sqlite3_wasm_db_error( pDb, capi.SQLITE_FORMAT, "SQLITE_UTF8 is the only supported encoding." ); }; /** __dbCleanupMap is infrastructure for recording registration of UDFs and collations so that sqlite3_close_v2() can clean up any automated JS-to-WASM function conversions installed by those. */ const __argPDb = (pDb)=>wasm.xWrap.argAdapter('sqlite3*')(pDb); const __argStr = (str)=>wasm.isPtr(str) ? wasm.cstrToJs(str) : str; const __dbCleanupMap = function( pDb, mode/*0=remove, >0=create if needed, <0=do not create if missing*/ ){ pDb = __argPDb(pDb); let m = this.dbMap.get(pDb); if(!mode){ this.dbMap.delete(pDb); return m; }else if(!m && mode>0){ this.dbMap.set(pDb, (m = Object.create(null))); } return m; }.bind(Object.assign(Object.create(null),{ dbMap: new Map })); __dbCleanupMap.addCollation = function(pDb, name){ const m = __dbCleanupMap(pDb, 1); if(!m.collation) m.collation = new Set; m.collation.add(__argStr(name).toLowerCase()); }; /** Intended to be called _only_ from sqlite3_close_v2(), passed its non-0 db argument. This function freees up certain automatically-installed WASM function bindings which were installed on behalf of the given db, as those may otherwise leak. Notable caveat: this is only ever run via sqlite3.capi.sqlite3_close_v2(). If a client, for whatever reason, uses sqlite3.wasm.exports.sqlite3_close_v2() (the function directly exported from WASM), this cleanup will not happen. This is not a silver bullet for avoiding automation-related leaks but represents "an honest effort." The issue being addressed here is covered at: https://sqlite.org/wasm/doc/trunk/api-c-style.md#convert-func-ptr */ __dbCleanupMap.cleanup = function(pDb){ pDb = __argPDb(pDb); //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true; /** Installing NULL functions in the C API will remove those bindings. The FuncPtrAdapter which sits between us and the C API will also treat that as an opportunity to wasm.uninstallFunction() any WASM function bindings it has installed for pDb. */ try{capi.sqlite3_busy_handler(pDb, 0, 0)} catch(e){/*ignored*/} try{capi.sqlite3_progress_handler(pDb, 0, 0, 0)} catch(e){/*ignored*/} try{capi.sqlite3_trace_v2(pDb, 0, 0, 0, 0)} catch(e){/*ignored*/} try{capi.sqlite3_set_authorizer(pDb, 0, 0)} catch(e){/*ignored*/} const m = __dbCleanupMap(pDb, 0); if(!m) return; if(m.collation){ for(const name of m.collation){ try{ capi.sqlite3_create_collation_v2( pDb, name, capi.SQLITE_UTF8, 0, 0, 0 ); }catch(e){ /*ignored*/ } } delete m.collation; } if(m.udf){ //TODO: map and clean up UDFs. } }; {/* Binding of sqlite3_close_v2() */ const __sqlite3CloseV2 = wasm.xWrap("sqlite3_close_v2", "int", "sqlite3*"); capi.sqlite3_close_v2 = function(pDb){ if(1!==arguments.length) return __dbArgcMismatch(pDb, 'sqlite3_close_v2', 1); if(pDb){ try{__dbCleanupMap.cleanup(pDb)} catch(e){/*ignored*/} } return __sqlite3CloseV2(pDb); }; }/*sqlite3_close_v2()*/ if(capi.sqlite3session_table_filter){ const __sqlite3SessionDelete = wasm.xWrap( 'sqlite3session_delete', undefined, ['sqlite3_session*'] ); capi.sqlite3session_delete = function(pSession){ if(1!==arguments.length){ return __dbArgcMismatch(pDb, 'sqlite3session_delete', 1); /* Yes, we're returning a value from a void function. That seems like the lesser evil compared to not maintaining arg-count consistency as we do with other similar bindings. */ } else if(pSession){ //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true; capi.sqlite3session_table_filter(pSession, 0, 0); } __sqlite3SessionDelete(pSession); }; } {/* Bindings for sqlite3_create_collation[_v2]() */ // contextKey() impl for wasm.xWrap.FuncPtrAdapter const contextKey = (argv,argIndex)=>{ return 'argv['+argIndex+']:'+argv[0/* sqlite3* */]+ ':'+wasm.cstrToJs(argv[1/* collation name */]).toLowerCase() }; const __sqlite3CreateCollationV2 = wasm.xWrap( 'sqlite3_create_collation_v2', 'int', [ 'sqlite3*', 'string', 'int', '*', new wasm.xWrap.FuncPtrAdapter({ /* int(*xCompare)(void*,int,const void*,int,const void*) */ name: 'xCompare', signature: 'i(pipip)', contextKey }), new wasm.xWrap.FuncPtrAdapter({ /* void(*xDestroy(void*) */ name: 'xDestroy', signature: 'v(p)', contextKey }) ] ); /** Works exactly like C's sqlite3_create_collation_v2() except that: 1) It returns capi.SQLITE_FORMAT if the 3rd argument contains any encoding-related value other than capi.SQLITE_UTF8. No other encodings are supported. As a special case, if the bottom 4 bits of that argument are 0, SQLITE_UTF8 is assumed. 2) It accepts JS functions for its function-pointer arguments, for which it will install WASM-bound proxies. The bindings are "permanent," in that they will stay in the WASM environment until it shuts down unless the client calls this again with the same collation name and a value of 0 or null for the the function pointer(s). For consistency with the C API, it requires the same number of arguments. It returns capi.SQLITE_MISUSE if passed any other argument count. Returns 0 on success, non-0 on error, in which case the error state of pDb (of type `sqlite3*` or argument-convertible to it) may contain more information. */ capi.sqlite3_create_collation_v2 = function(pDb,zName,eTextRep,pArg,xCompare,xDestroy){ if(6!==arguments.length) return __dbArgcMismatch(pDb, 'sqlite3_create_collation_v2', 6); else if( 0 === (eTextRep & 0xf) ){ eTextRep |= capi.SQLITE_UTF8; }else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){ return __errEncoding(pDb); } try{ const rc = __sqlite3CreateCollationV2(pDb, zName, eTextRep, pArg, xCompare, xDestroy); if(xCompare) __dbCleanupMap.addCollation(pDb, zName); return rc; }catch(e){ return util.sqlite3_wasm_db_error(pDb, e); } }; capi.sqlite3_create_collation = (pDb,zName,eTextRep,pArg,xCompare)=>{ return (5===arguments.length) ? capi.sqlite3_create_collation_v2(pDb,zName,eTextRep,pArg,xCompare,0) : __dbArgcMismatch(pDb, 'sqlite3_create_collation', 5); }; }/*sqlite3_create_collation() and friends*/ {/* Special-case handling of sqlite3_create_function_v2() and sqlite3_create_window_function(). */ /** FuncPtrAdapter for contextKey() for sqlite3_create_function() and friends. */ const contextKey = function(argv,argIndex){ return ( argv[0/* sqlite3* */] +':'+(argv[2/*number of UDF args*/] < 0 ? -1 : argv[2]) +':'+argIndex/*distinct for each xAbc callback type*/ +':'+wasm.cstrToJs(argv[1]).toLowerCase() ) }; /** JS proxies for the various sqlite3_create[_window]_function() callbacks, structured in a form usable by wasm.xWrap.FuncPtrAdapter. */ const __cfProxy = Object.assign(Object.create(null), { xInverseAndStep: { signature:'v(pip)', contextKey, callProxy: (callback)=>{ return (pCtx, argc, pArgv)=>{ try{ callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv)) } catch(e){ capi.sqlite3_result_error_js(pCtx, e) } }; } }, xFinalAndValue: { signature:'v(p)', contextKey, callProxy: (callback)=>{ return (pCtx)=>{ try{ capi.sqlite3_result_js(pCtx, callback(pCtx)) } catch(e){ capi.sqlite3_result_error_js(pCtx, e) } }; } }, xFunc: { signature:'v(pip)', contextKey, callProxy: (callback)=>{ return (pCtx, argc, pArgv)=>{ try{ capi.sqlite3_result_js( pCtx, callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv)) ); }catch(e){ //console.error('xFunc() caught:',e); capi.sqlite3_result_error_js(pCtx, e); } }; } }, xDestroy: { signature:'v(p)', contextKey, //Arguable: a well-behaved destructor doesn't require a proxy. callProxy: (callback)=>{ return (pVoid)=>{ try{ callback(pVoid) } catch(e){ console.error("UDF xDestroy method threw:",e) } }; } } })/*__cfProxy*/; const __sqlite3CreateFunction = wasm.xWrap( "sqlite3_create_function_v2", "int", [ "sqlite3*", "string"/*funcName*/, "int"/*nArg*/, "int"/*eTextRep*/, "*"/*pApp*/, new wasm.xWrap.FuncPtrAdapter({name: 'xFunc', ...__cfProxy.xFunc}), new wasm.xWrap.FuncPtrAdapter({name: 'xStep', ...__cfProxy.xInverseAndStep}), new wasm.xWrap.FuncPtrAdapter({name: 'xFinal', ...__cfProxy.xFinalAndValue}), new wasm.xWrap.FuncPtrAdapter({name: 'xDestroy', ...__cfProxy.xDestroy}) ] ); const __sqlite3CreateWindowFunction = wasm.xWrap( "sqlite3_create_window_function", "int", [ "sqlite3*", "string"/*funcName*/, "int"/*nArg*/, "int"/*eTextRep*/, "*"/*pApp*/, new wasm.xWrap.FuncPtrAdapter({name: 'xStep', ...__cfProxy.xInverseAndStep}), new wasm.xWrap.FuncPtrAdapter({name: 'xFinal', ...__cfProxy.xFinalAndValue}), new wasm.xWrap.FuncPtrAdapter({name: 'xValue', ...__cfProxy.xFinalAndValue}), new wasm.xWrap.FuncPtrAdapter({name: 'xInverse', ...__cfProxy.xInverseAndStep}), new wasm.xWrap.FuncPtrAdapter({name: 'xDestroy', ...__cfProxy.xDestroy}) ] ); /* Documented in the api object's initializer. */ capi.sqlite3_create_function_v2 = function f( pDb, funcName, nArg, eTextRep, pApp, xFunc, //void (*xFunc)(sqlite3_context*,int,sqlite3_value**) xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**) xFinal, //void (*xFinal)(sqlite3_context*) xDestroy //void (*xDestroy)(void*) ){ if( f.length!==arguments.length ){ return __dbArgcMismatch(pDb,"sqlite3_create_function_v2",f.length); }else if( 0 === (eTextRep & 0xf) ){ eTextRep |= capi.SQLITE_UTF8; }else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){ return __errEncoding(pDb); } try{ return __sqlite3CreateFunction(pDb, funcName, nArg, eTextRep, pApp, xFunc, xStep, xFinal, xDestroy); }catch(e){ console.error("sqlite3_create_function_v2() setup threw:",e); return util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e); } }; /* Documented in the api object's initializer. */ capi.sqlite3_create_function = function f( pDb, funcName, nArg, eTextRep, pApp, xFunc, xStep, xFinal ){ return (f.length===arguments.length) ? capi.sqlite3_create_function_v2(pDb, funcName, nArg, eTextRep, pApp, xFunc, xStep, xFinal, 0) : __dbArgcMismatch(pDb,"sqlite3_create_function",f.length); }; /* Documented in the api object's initializer. */ capi.sqlite3_create_window_function = function f( pDb, funcName, nArg, eTextRep, pApp, xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**) xFinal, //void (*xFinal)(sqlite3_context*) xValue, //void (*xValue)(sqlite3_context*) xInverse,//void (*xInverse)(sqlite3_context*,int,sqlite3_value**) xDestroy //void (*xDestroy)(void*) ){ if( f.length!==arguments.length ){ return __dbArgcMismatch(pDb,"sqlite3_create_window_function",f.length); }else if( 0 === (eTextRep & 0xf) ){ eTextRep |= capi.SQLITE_UTF8; }else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){ return __errEncoding(pDb); } try{ return __sqlite3CreateWindowFunction(pDb, funcName, nArg, eTextRep, pApp, xStep, xFinal, xValue, xInverse, xDestroy); }catch(e){ console.error("sqlite3_create_window_function() setup threw:",e); return util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e); } }; /** A _deprecated_ alias for capi.sqlite3_result_js() which predates the addition of that function in the public API. */ capi.sqlite3_create_function_v2.udfSetResult = capi.sqlite3_create_function.udfSetResult = capi.sqlite3_create_window_function.udfSetResult = capi.sqlite3_result_js; /** A _deprecated_ alias for capi.sqlite3_values_to_js() which predates the addition of that function in the public API. */ capi.sqlite3_create_function_v2.udfConvertArgs = capi.sqlite3_create_function.udfConvertArgs = capi.sqlite3_create_window_function.udfConvertArgs = capi.sqlite3_values_to_js; /** A _deprecated_ alias for capi.sqlite3_result_error_js() which predates the addition of that function in the public API. */ capi.sqlite3_create_function_v2.udfSetError = capi.sqlite3_create_function.udfSetError = capi.sqlite3_create_window_function.udfSetError = capi.sqlite3_result_error_js; }/*sqlite3_create_function_v2() and sqlite3_create_window_function() proxies*/; if(1){/* Special-case handling of sqlite3_prepare_v2() and sqlite3_prepare_v3() */ /** Helper for string:flexible conversions which require a byte-length counterpart argument. Passed a value and its ostensible length, this function returns [V,N], where V is either v or a transformed copy of v and N is either n, -1, or the byte length of v (if it's a byte array). */ const __flexiString = (v,n)=>{ if('string'===typeof v){ n = -1; }else if(util.isSQLableTypedArray(v)){ n = v.byteLength; v = util.typedArrayToString(v); }else if(Array.isArray(v)){ v = v.join(""); n = -1; } return [v, n]; }; /** Scope-local holder of the two impls of sqlite3_prepare_v2/v3(). */ const __prepare = { /** This binding expects a JS string as its 2nd argument and null as its final argument. In order to compile multiple statements from a single string, the "full" impl (see below) must be used. */ basic: wasm.xWrap('sqlite3_prepare_v3', "int", ["sqlite3*", "string", "int"/*ignored for this impl!*/, "int", "**", "**"/*MUST be 0 or null or undefined!*/]), /** Impl which requires that the 2nd argument be a pointer to the SQL string, instead of being converted to a string. This variant is necessary for cases where we require a non-NULL value for the final argument (exec()'ing multiple statements from one input string). For simpler cases, where only the first statement in the SQL string is required, the wrapper named sqlite3_prepare_v2() is sufficient and easier to use because it doesn't require dealing with pointers. */ full: wasm.xWrap('sqlite3_prepare_v3', "int", ["sqlite3*", "*", "int", "int", "**", "**"]) }; /* Documented in the capi object's initializer. */ capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){ if(f.length!==arguments.length){ return __dbArgcMismatch(pDb,"sqlite3_prepare_v3",f.length); } const [xSql, xSqlLen] = __flexiString(sql, sqlLen); switch(typeof xSql){ case 'string': return __prepare.basic(pDb, xSql, xSqlLen, prepFlags, ppStmt, null); case 'number': return __prepare.full(pDb, xSql, xSqlLen, prepFlags, ppStmt, pzTail); default: return util.sqlite3_wasm_db_error( pDb, capi.SQLITE_MISUSE, "Invalid SQL argument type for sqlite3_prepare_v2/v3()." ); } }; /* Documented in the capi object's initializer. */ capi.sqlite3_prepare_v2 = function f(pDb, sql, sqlLen, ppStmt, pzTail){ return (f.length===arguments.length) ? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail) : __dbArgcMismatch(pDb,"sqlite3_prepare_v2",f.length); }; }/*sqlite3_prepare_v2/v3()*/ {/*sqlite3_bind_text/blob()*/ const __bindText = wasm.xWrap("sqlite3_bind_text", "int", [ "sqlite3_stmt*", "int", "string", "int", "*" ]); const __bindBlob = wasm.xWrap("sqlite3_bind_blob", "int", [ "sqlite3_stmt*", "int", "*", "int", "*" ]); /** Documented in the capi object's initializer. */ capi.sqlite3_bind_text = function f(pStmt, iCol, text, nText, xDestroy){ if(f.length!==arguments.length){ return __dbArgcMismatch(capi.sqlite3_db_handle(pStmt), "sqlite3_bind_text", f.length); }else if(wasm.isPtr(text) || null===text){ return __bindText(pStmt, iCol, text, nText, xDestroy); }else if(text instanceof ArrayBuffer){ text = new Uint8Array(text); }else if(Array.isArray(pMem)){ text = pMem.join(''); } let p, n; try{ if(util.isSQLableTypedArray(text)){ p = wasm.allocFromTypedArray(text); n = text.byteLength; }else if('string'===typeof text){ [p, n] = wasm.allocCString(text); }else{ return util.sqlite3_wasm_db_error( capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE, "Invalid 3rd argument type for sqlite3_bind_text()." ); } return __bindText(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC); }catch(e){ wasm.dealloc(p); return util.sqlite3_wasm_db_error( capi.sqlite3_db_handle(pStmt), e ); } }/*sqlite3_bind_text()*/; /** Documented in the capi object's initializer. */ capi.sqlite3_bind_blob = function f(pStmt, iCol, pMem, nMem, xDestroy){ if(f.length!==arguments.length){ return __dbArgcMismatch(capi.sqlite3_db_handle(pStmt), "sqlite3_bind_blob", f.length); }else if(wasm.isPtr(pMem) || null===pMem){ return __bindBlob(pStmt, iCol, pMem, nMem, xDestroy); }else if(pMem instanceof ArrayBuffer){ pMem = new Uint8Array(pMem); }else if(Array.isArray(pMem)){ pMem = pMem.join(''); } let p, n; try{ if(util.isBindableTypedArray(pMem)){ p = wasm.allocFromTypedArray(pMem); n = nMem>=0 ? nMem : pMem.byteLength; }else if('string'===typeof pMem){ [p, n] = wasm.allocCString(pMem); }else{ return util.sqlite3_wasm_db_error( capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE, "Invalid 3rd argument type for sqlite3_bind_blob()." ); } return __bindBlob(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC); }catch(e){ wasm.dealloc(p); return util.sqlite3_wasm_db_error( capi.sqlite3_db_handle(pStmt), e ); } }/*sqlite3_bind_blob()*/; }/*sqlite3_bind_text/blob()*/ {/* sqlite3_config() */ /** Wraps a small subset of the C API's sqlite3_config() options. Unsupported options trigger the return of capi.SQLITE_NOTFOUND. Passing fewer than 2 arguments triggers return of capi.SQLITE_MISUSE. */ capi.sqlite3_config = function(op, ...args){ if(arguments.length<2) return capi.SQLITE_MISUSE; switch(op){ case capi.SQLITE_CONFIG_COVERING_INDEX_SCAN: // 20 /* int */ case capi.SQLITE_CONFIG_MEMSTATUS:// 9 /* boolean */ case capi.SQLITE_CONFIG_SMALL_MALLOC: // 27 /* boolean */ case capi.SQLITE_CONFIG_SORTERREF_SIZE: // 28 /* int nByte */ case capi.SQLITE_CONFIG_STMTJRNL_SPILL: // 26 /* int nByte */ case capi.SQLITE_CONFIG_URI:// 17 /* int */ return wasm.exports.sqlite3_wasm_config_i(op, args[0]); case capi.SQLITE_CONFIG_LOOKASIDE: // 13 /* int int */ return wasm.exports.sqlite3_wasm_config_ii(op, args[0], args[1]); case capi.SQLITE_CONFIG_MEMDB_MAXSIZE: // 29 /* sqlite3_int64 */ return wasm.exports.sqlite3_wasm_config_j(op, args[0]); case capi.SQLITE_CONFIG_GETMALLOC: // 5 /* sqlite3_mem_methods* */ case capi.SQLITE_CONFIG_GETMUTEX: // 11 /* sqlite3_mutex_methods* */ case capi.SQLITE_CONFIG_GETPCACHE2: // 19 /* sqlite3_pcache_methods2* */ case capi.SQLITE_CONFIG_GETPCACHE: // 15 /* no-op */ case capi.SQLITE_CONFIG_HEAP: // 8 /* void*, int nByte, int min */ case capi.SQLITE_CONFIG_LOG: // 16 /* xFunc, void* */ case capi.SQLITE_CONFIG_MALLOC:// 4 /* sqlite3_mem_methods* */ case capi.SQLITE_CONFIG_MMAP_SIZE: // 22 /* sqlite3_int64, sqlite3_int64 */ case capi.SQLITE_CONFIG_MULTITHREAD: // 2 /* nil */ case capi.SQLITE_CONFIG_MUTEX: // 10 /* sqlite3_mutex_methods* */ case capi.SQLITE_CONFIG_PAGECACHE: // 7 /* void*, int sz, int N */ case capi.SQLITE_CONFIG_PCACHE2: // 18 /* sqlite3_pcache_methods2* */ case capi.SQLITE_CONFIG_PCACHE: // 14 /* no-op */ case capi.SQLITE_CONFIG_PCACHE_HDRSZ: // 24 /* int *psz */ case capi.SQLITE_CONFIG_PMASZ: // 25 /* unsigned int szPma */ case capi.SQLITE_CONFIG_SERIALIZED: // 3 /* nil */ case capi.SQLITE_CONFIG_SINGLETHREAD: // 1 /* nil */: case capi.SQLITE_CONFIG_SQLLOG: // 21 /* xSqllog, void* */ case capi.SQLITE_CONFIG_WIN32_HEAPSIZE: // 23 /* int nByte */ default: return capi.SQLITE_NOTFOUND; } }; }/* sqlite3_config() */ {/* Import C-level constants and structs... */ const cJson = wasm.xCall('sqlite3_wasm_enum_json'); if(!cJson){ toss("Maintenance required: increase sqlite3_wasm_enum_json()'s", "static buffer size!"); } //console.debug('wasm.ctype length =',wasm.cstrlen(cJson)); wasm.ctype = JSON.parse(wasm.cstrToJs(cJson)); // Groups of SQLITE_xyz macros... const defineGroups = ['access', 'authorizer', 'blobFinalizers', 'changeset', 'config', 'dataTypes', 'dbConfig', 'dbStatus', 'encodings', 'fcntl', 'flock', 'ioCap', 'limits', 'openFlags', 'prepareFlags', 'resultCodes', 'sqlite3Status', 'stmtStatus', 'syncFlags', 'trace', 'txnState', 'udfFlags', 'version' ]; if(wasm.bigIntEnabled){ defineGroups.push('serialize', 'session', 'vtab'); } for(const t of defineGroups){ for(const e of Object.entries(wasm.ctype[t])){ // ^^^ [k,v] there triggers a buggy code transformation via // one of the Emscripten-driven optimizers. capi[e[0]] = e[1]; } } const __rcMap = Object.create(null); for(const t of ['resultCodes']){ for(const e of Object.entries(wasm.ctype[t])){ __rcMap[e[1]] = e[0]; } } /** For the given integer, returns the SQLITE_xxx result code as a string, or undefined if no such mapping is found. */ capi.sqlite3_js_rc_str = (rc)=>__rcMap[rc]; /* Bind all registered C-side structs... */ const notThese = Object.assign(Object.create(null),{ // For each struct to NOT register, map its name to true: WasmTestStruct: true, /* We unregister the kvvfs VFS from Worker threads below. */ sqlite3_kvvfs_methods: !util.isUIThread(), /* sqlite3_index_info and friends require int64: */ sqlite3_index_info: !wasm.bigIntEnabled, sqlite3_index_constraint: !wasm.bigIntEnabled, sqlite3_index_orderby: !wasm.bigIntEnabled, sqlite3_index_constraint_usage: !wasm.bigIntEnabled }); for(const s of wasm.ctype.structs){ if(!notThese[s.name]){ capi[s.name] = sqlite3.StructBinder(s); } } if(capi.sqlite3_index_info){ /* Move these inner structs into sqlite3_index_info. Binding ** them to WASM requires that we create global-scope structs to ** model them with, but those are no longer needed after we've ** passed them to StructBinder. */ for(const k of ['sqlite3_index_constraint', 'sqlite3_index_orderby', 'sqlite3_index_constraint_usage']){ capi.sqlite3_index_info[k] = capi[k]; delete capi[k]; } capi.sqlite3_vtab_config = wasm.xWrap( 'sqlite3_wasm_vtab_config','int',[ 'sqlite3*', 'int', 'int'] ); }/* end vtab-related setup */ }/*end C constant and struct imports*/ const pKvvfs = capi.sqlite3_vfs_find("kvvfs"); if( pKvvfs ){/* kvvfs-specific glue */ if(util.isUIThread()){ const kvvfsMethods = new capi.sqlite3_kvvfs_methods( wasm.exports.sqlite3_wasm_kvvfs_methods() ); delete capi.sqlite3_kvvfs_methods; const kvvfsMakeKey = wasm.exports.sqlite3_wasm_kvvfsMakeKeyOnPstack, pstack = wasm.pstack; const kvvfsStorage = (zClass)=> ((115/*=='s'*/===wasm.peek(zClass)) ? sessionStorage : localStorage); /** Implementations for members of the object referred to by sqlite3_wasm_kvvfs_methods(). We swap out the native implementations with these, which use localStorage or sessionStorage for their backing store. */ const kvvfsImpls = { xRead: (zClass, zKey, zBuf, nBuf)=>{ const stack = pstack.pointer, astack = wasm.scopedAllocPush(); try { const zXKey = kvvfsMakeKey(zClass,zKey); if(!zXKey) return -3/*OOM*/; const jKey = wasm.cstrToJs(zXKey); const jV = kvvfsStorage(zClass).getItem(jKey); if(!jV) return -1; const nV = jV.length /* Note that we are relying 100% on v being ASCII so that jV.length is equal to the C-string's byte length. */; if(nBuf<=0) return nV; else if(1===nBuf){ wasm.poke(zBuf, 0); return nV; } const zV = wasm.scopedAllocCString(jV); if(nBuf > nV + 1) nBuf = nV + 1; wasm.heap8u().copyWithin(zBuf, zV, zV + nBuf - 1); wasm.poke(zBuf + nBuf - 1, 0); return nBuf - 1; }catch(e){ console.error("kvstorageRead()",e); return -2; }finally{ pstack.restore(stack); wasm.scopedAllocPop(astack); } }, xWrite: (zClass, zKey, zData)=>{ const stack = pstack.pointer; try { const zXKey = kvvfsMakeKey(zClass,zKey); if(!zXKey) return 1/*OOM*/; const jKey = wasm.cstrToJs(zXKey); kvvfsStorage(zClass).setItem(jKey, wasm.cstrToJs(zData)); return 0; }catch(e){ console.error("kvstorageWrite()",e); return capi.SQLITE_IOERR; }finally{ pstack.restore(stack); } }, xDelete: (zClass, zKey)=>{ const stack = pstack.pointer; try { const zXKey = kvvfsMakeKey(zClass,zKey); if(!zXKey) return 1/*OOM*/; kvvfsStorage(zClass).removeItem(wasm.cstrToJs(zXKey)); return 0; }catch(e){ console.error("kvstorageDelete()",e); return capi.SQLITE_IOERR; }finally{ pstack.restore(stack); } |
︙ | ︙ | |||
713 714 715 716 717 718 719 720 | /* Worker thread: unregister kvvfs to avoid it being used for anything other than local/sessionStorage. It "can" be used that way but it's not really intended to be. */ capi.sqlite3_vfs_unregister(pKvvfs); } }/*pKvvfs*/ }); | > | 1458 1459 1460 1461 1462 1463 1464 1465 1466 | /* Worker thread: unregister kvvfs to avoid it being used for anything other than local/sessionStorage. It "can" be used that way but it's not really intended to be. */ capi.sqlite3_vfs_unregister(pKvvfs); } }/*pKvvfs*/ wasm.xWrap.FuncPtrAdapter.warnOnUse = true; }); |
Changes to ext/wasm/api/sqlite3-api-oo1.js.
︙ | ︙ | |||
51 52 53 54 55 56 57 | }; // Documented in DB.checkRc() const checkSqlite3Rc = function(dbPtr, sqliteResultCode){ if(sqliteResultCode){ if(dbPtr instanceof DB) dbPtr = dbPtr.pointer; toss3( | | > | | | | > > | 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | }; // Documented in DB.checkRc() const checkSqlite3Rc = function(dbPtr, sqliteResultCode){ if(sqliteResultCode){ if(dbPtr instanceof DB) dbPtr = dbPtr.pointer; toss3( "sqlite3 result code",sqliteResultCode+":", (dbPtr ? capi.sqlite3_errmsg(dbPtr) : capi.sqlite3_errstr(sqliteResultCode)) ); } return arguments[0]; }; /** sqlite3_trace_v2() callback which gets installed by the DB ctor if its open-flags contain "t". */ const __dbTraceToConsole = wasm.installFunction('i(ippp)', function(t,c,p,x){ if(capi.SQLITE_TRACE_STMT===t){ // x == SQL, p == sqlite3_stmt* console.log("SQL TRACE #"+(++this.counter)+' via sqlite3@'+c+':', wasm.cstrToJs(x)); } }.bind({counter: 0})); /** A map of sqlite3_vfs pointers to SQL code or a callback function to run when the DB constructor opens a database with the given VFS. In the latter case, the call signature is (theDbObject,sqlite3Namespace) and the callback is expected to throw on error. */ const __vfsPostOpenSql = Object.create(null); /** A proxy for DB class constructors. It must be called with the being-construct DB object as its "this". See the DB constructor for the argument docs. This is split into a separate function |
︙ | ︙ | |||
132 133 134 135 136 137 138 | let fn = opt.filename, vfsName = opt.vfs, flagsStr = opt.flags; if(('string'!==typeof fn && 'number'!==typeof fn) || 'string'!==typeof flagsStr || (vfsName && ('string'!==typeof vfsName && 'number'!==typeof vfsName))){ console.error("Invalid DB ctor args",opt,arguments); toss3("Invalid arguments for DB constructor."); } | | | > | < < < < < < < < < > > > > > > > > > > > > > > > > | > > > > > | > | | < | 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | let fn = opt.filename, vfsName = opt.vfs, flagsStr = opt.flags; if(('string'!==typeof fn && 'number'!==typeof fn) || 'string'!==typeof flagsStr || (vfsName && ('string'!==typeof vfsName && 'number'!==typeof vfsName))){ console.error("Invalid DB ctor args",opt,arguments); toss3("Invalid arguments for DB constructor."); } let fnJs = ('number'===typeof fn) ? wasm.cstrToJs(fn) : fn; const vfsCheck = ctor._name2vfs[fnJs]; if(vfsCheck){ vfsName = vfsCheck.vfs; fn = fnJs = vfsCheck.filename(fnJs); } let pDb, oflags = 0; if( flagsStr.indexOf('c')>=0 ){ oflags |= capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE; } if( flagsStr.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE; if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY; oflags |= capi.SQLITE_OPEN_EXRESCODE; const stack = wasm.pstack.pointer; try { const pPtr = wasm.pstack.allocPtr() /* output (sqlite3**) arg */; let rc = capi.sqlite3_open_v2(fn, pPtr, oflags, vfsName || 0); pDb = wasm.peekPtr(pPtr); checkSqlite3Rc(pDb, rc); capi.sqlite3_extended_result_codes(pDb, 1); if(flagsStr.indexOf('t')>=0){ capi.sqlite3_trace_v2(pDb, capi.SQLITE_TRACE_STMT, __dbTraceToConsole, pDb); } }catch( e ){ if( pDb ) capi.sqlite3_close_v2(pDb); throw e; }finally{ wasm.pstack.restore(stack); } this.filename = fnJs; __ptrMap.set(this, pDb); __stmtMap.set(this, Object.create(null)); try{ // Check for per-VFS post-open SQL/callback... const pVfs = capi.sqlite3_js_db_vfs(pDb); if(!pVfs) toss3("Internal error: cannot get VFS for new db handle."); const postInitSql = __vfsPostOpenSql[pVfs]; if(postInitSql instanceof Function){ postInitSql(this, sqlite3); }else if(postInitSql){ checkSqlite3Rc( pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0) ); } }catch(e){ this.close(); throw e; } }; /** Sets SQL which should be exec()'d on a DB instance after it is opened with the given VFS pointer. The SQL may be any type supported by the "string:flexible" function argument conversion. Alternately, the 2nd argument may be a function, in which case it is called with (theOo1DbObject,sqlite3Namespace) at the end of the DB() constructor. The function must throw on error, in which case the db is closed and the exception is propagated. This function is intended only for use by DB subclasses or sqlite3_vfs implementations. */ dbCtorHelper.setVfsPostOpenSql = function(pVfs, sql){ __vfsPostOpenSql[pVfs] = sql; }; /** A helper for DB constructors. It accepts either a single config-style object or up to 3 arguments (filename, dbOpenFlags, dbVfsName). It returns a new object containing: { filename: ..., flags: ..., vfs: ... } If passed an object, any additional properties it has are copied as-is into the new object. */ dbCtorHelper.normalizeArgs = function(filename=':memory:',flags = 'c',vfs = null){ const arg = {}; if(1===arguments.length && arguments[0] && 'object'===typeof arguments[0]){ Object.assign(arg, arguments[0]); if(undefined===arg.flags) arg.flags = 'c'; if(undefined===arg.vfs) arg.vfs = null; if(undefined===arg.filename) arg.filename = ':memory:'; }else{ arg.filename = filename; arg.flags = flags; arg.vfs = vfs; |
︙ | ︙ | |||
317 318 319 320 321 322 323 | - `columnCount`: the number of result columns in the query, or 0 for queries which cannot return results. - `parameterCount`: the number of bindable paramters in the query. */ const Stmt = function(){ if(BindTypes!==arguments[2]){ | | | 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 | - `columnCount`: the number of result columns in the query, or 0 for queries which cannot return results. - `parameterCount`: the number of bindable paramters in the query. */ const Stmt = function(){ if(BindTypes!==arguments[2]){ toss3(capi.SQLITE_MISUSE, "Do not call the Stmt constructor directly. Use DB.prepare()."); } this.db = arguments[0]; __ptrMap.set(this, arguments[1]); this.columnCount = capi.sqlite3_column_count(this.pointer); this.parameterCount = capi.sqlite3_bind_parameter_count(this.pointer); }; |
︙ | ︙ | |||
419 420 421 422 423 424 425 | } out.cbArg = (stmt)=>stmt; break; default: if(util.isInt32(opt.rowMode)){ out.cbArg = (stmt)=>stmt.get(opt.rowMode); break; | | > > | > > > | < | | | < < > | > | | < | | | | < | < > > > > > > > > > | | | | | | > > | | 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 | } out.cbArg = (stmt)=>stmt; break; default: if(util.isInt32(opt.rowMode)){ out.cbArg = (stmt)=>stmt.get(opt.rowMode); break; }else if('string'===typeof opt.rowMode && opt.rowMode.length>1 && '$'===opt.rowMode[0]){ /* "$X": fetch column named "X" (case-sensitive!). Prior to 2022-12-14 ":X" and "@X" were also permitted, but having so many options is unnecessary and likely to cause confusion. */ const $colName = opt.rowMode.substr(1); out.cbArg = (stmt)=>{ const rc = stmt.get(Object.create(null))[$colName]; return (undefined===rc) ? toss3(capi.SQLITE_NOTFOUND, "exec(): unknown result column:",$colName) : rc; }; break; } toss3("Invalid rowMode:",opt.rowMode); } } return out; }; /** Internal impl of the DB.selectValue(), selectArray(), and selectObject() methods. */ const __selectFirstRow = (db, sql, bind, ...getArgs)=>{ const stmt = db.prepare(sql); try { return stmt.bind(bind).step() ? stmt.get(...getArgs) : undefined; }finally{ stmt.finalize(); } }; /** Internal impl of the DB.selectArrays() and selectObjects() methods. */ const __selectAll = (db, sql, bind, rowMode)=>db.exec({ sql, bind, rowMode, returnValue: 'resultRows' }); /** Expects to be given a DB instance or an `sqlite3*` pointer (may be null) and an sqlite3 API result code. If the result code is not falsy, this function throws an SQLite3Error with an error message from sqlite3_errmsg(), using db (or, if db is-a DB, db.pointer) as the db handle, or sqlite3_errstr() if db is falsy. Note that if it's passed a non-error code like SQLITE_ROW or SQLITE_DONE, it will still throw but the error string might be "Not an error." The various non-0 non-error codes need to be checked for in client code where they are expected. If it does not throw, it returns its first argument. */ DB.checkRc = (db,resultCode)=>checkSqlite3Rc(db,resultCode); DB.prototype = { /** Returns true if this db handle is open, else false. */ isOpen: function(){ return !!this.pointer; }, /** Throws if this given DB has been closed, else returns `this`. */ |
︙ | ︙ | |||
490 491 492 493 494 495 496 | If this.onclose.before is a function then it is called before any close-related cleanup. If this.onclose.after is a function then it is called after the db is closed but before auxiliary state like this.filename is cleared. | | > > | | | | 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 | If this.onclose.before is a function then it is called before any close-related cleanup. If this.onclose.after is a function then it is called after the db is closed but before auxiliary state like this.filename is cleared. Both onclose handlers are passed this object, with the onclose object as their "this," noting that the db will have been closed when onclose.after is called. If this db is not opened when close() is called, neither of the handlers are called. Any exceptions the handlers throw are ignored because "destructors must not throw." Note that garbage collection of a db handle, if it happens at all, will never trigger close(), so onclose handlers are not a reliable way to implement close-time cleanup or maintenance of a db. */ close: function(){ |
︙ | ︙ | |||
570 571 572 573 574 575 576 | dbVfsName: function(dbName=0){ let rc; const pVfs = capi.sqlite3_js_db_vfs( affirmDbOpen(this).pointer, dbName ); if(pVfs){ const v = new capi.sqlite3_vfs(pVfs); | | | | 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 | dbVfsName: function(dbName=0){ let rc; const pVfs = capi.sqlite3_js_db_vfs( affirmDbOpen(this).pointer, dbName ); if(pVfs){ const v = new capi.sqlite3_vfs(pVfs); try{ rc = wasm.cstrToJs(v.$zName) } finally { v.dispose() } } return rc; }, /** Compiles the given SQL and returns a prepared Stmt. This is the only way to create new Stmt objects. Throws on error. The given SQL must be a string, a Uint8Array holding SQL, a WASM pointer to memory holding the NUL-terminated SQL string, |
︙ | ︙ | |||
604 605 606 607 608 609 610 | prepare: function(sql){ affirmDbOpen(this); const stack = wasm.pstack.pointer; let ppStmt, pStmt; try{ ppStmt = wasm.pstack.alloc(8)/* output (sqlite3_stmt**) arg */; DB.checkRc(this, capi.sqlite3_prepare_v2(this.pointer, sql, -1, ppStmt, null)); | | | 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 | prepare: function(sql){ affirmDbOpen(this); const stack = wasm.pstack.pointer; let ppStmt, pStmt; try{ ppStmt = wasm.pstack.alloc(8)/* output (sqlite3_stmt**) arg */; DB.checkRc(this, capi.sqlite3_prepare_v2(this.pointer, sql, -1, ppStmt, null)); pStmt = wasm.peekPtr(ppStmt); } finally { wasm.pstack.restore(stack); } if(!pStmt) toss3("Cannot prepare empty SQL."); const stmt = new Stmt(this, pStmt, BindTypes); __stmtMap.get(this)[pStmt] = stmt; |
︙ | ︙ | |||
662 663 664 665 666 667 668 | - `columnNames`: if this is an array, the column names of the result set are stored in this array before the callback (if any) is triggered (regardless of whether the query produces any result rows). If no statement has result columns, this value is unchanged. Achtung: an SQL result may have multiple columns with identical names. | | | | > > > > | > > | 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 | - `columnNames`: if this is an array, the column names of the result set are stored in this array before the callback (if any) is triggered (regardless of whether the query produces any result rows). If no statement has result columns, this value is unchanged. Achtung: an SQL result may have multiple columns with identical names. - `callback` = a function which gets called for each row of the result set, but only if that statement has any result _rows_. The callback's "this" is the options object, noting that this function synthesizes one if the caller does not pass one to exec(). The second argument passed to the callback is always the current Stmt object, as it's needed if the caller wants to fetch the column names or some such (noting that they could also be fetched via `this.columnNames`, if the client provides the `columnNames` option). If the callback returns a literal `false` (as opposed to any other falsy value, e.g. an implicit `undefined` return), any ongoing statement-`step()` iteration stops without an error. The return value of the callback is otherwise ignored. ACHTUNG: The callback MUST NOT modify the Stmt object. Calling any of the Stmt.get() variants, Stmt.getColumnName(), or similar, is legal, but calling step() or finalize() is not. Member methods which are illegal in this context will trigger an exception, but clients must also refrain from using any lower-level (C-style) APIs which might modify the statement. The first argument passed to the callback defaults to an array of values from the current result row but may be changed with ... - `rowMode` = specifies the type of he callback's first argument. It may be any of... |
︙ | ︙ | |||
707 708 709 710 711 712 713 | `resultRows` is an array because appending the statement to the array would be downright unhelpful. B) An integer, indicating a zero-based column in the result row. Only that one single value will be passed on. C) A string with a minimum length of 2 and leading character of | | | | | | | | | < < | | 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 | `resultRows` is an array because appending the statement to the array would be downright unhelpful. B) An integer, indicating a zero-based column in the result row. Only that one single value will be passed on. C) A string with a minimum length of 2 and leading character of '$' will fetch the row as an object, extract that one field, and pass that field's value to the callback. Note that these keys are case-sensitive so must match the case used in the SQL. e.g. `"select a A from t"` with a `rowMode` of `'$A'` would work but `'$a'` would not. A reference to a column not in the result set will trigger an exception on the first row (as the check is not performed until rows are fetched). Note also that `$` is a legal identifier character in JS so need not be quoted. Any other `rowMode` value triggers an exception. - `resultRows`: if this is an array, it functions similarly to the `callback` option: each row of the result set (if any), with the exception that the `rowMode` 'stmt' is not legal. It is legal to use both `resultRows` and `callback`, but |
︙ | ︙ | |||
766 767 768 769 770 771 772 | } const opt = arg.opt; const callback = opt.callback; const resultRows = Array.isArray(opt.resultRows) ? opt.resultRows : undefined; let stmt; let bind = opt.bind; | | > > > | > | < > | | | < | | < | < | > | | | | > > > > | | | < | | > | > > | 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 | } const opt = arg.opt; const callback = opt.callback; const resultRows = Array.isArray(opt.resultRows) ? opt.resultRows : undefined; let stmt; let bind = opt.bind; let evalFirstResult = !!( arg.cbArg || opt.columnNames || resultRows ) /* true to step through the first result-returning statement */; const stack = wasm.scopedAllocPush(); const saveSql = Array.isArray(opt.saveSql) ? opt.saveSql : undefined; try{ const isTA = util.isSQLableTypedArray(arg.sql) /* Optimization: if the SQL is a TypedArray we can save some string conversion costs. */; /* Allocate the two output pointers (ppStmt, pzTail) and heap space for the SQL (pSql). When prepare_v2() returns, pzTail will point to somewhere in pSql. */ let sqlByteLen = isTA ? arg.sql.byteLength : wasm.jstrlen(arg.sql); const ppStmt = wasm.scopedAlloc( /* output (sqlite3_stmt**) arg and pzTail */ (2 * wasm.ptrSizeof) + (sqlByteLen + 1/* SQL + NUL */) ); const pzTail = ppStmt + wasm.ptrSizeof /* final arg to sqlite3_prepare_v2() */; let pSql = pzTail + wasm.ptrSizeof; const pSqlEnd = pSql + sqlByteLen; if(isTA) wasm.heap8().set(arg.sql, pSql); else wasm.jstrcpy(arg.sql, wasm.heap8(), pSql, sqlByteLen, false); wasm.poke(pSql + sqlByteLen, 0/*NUL terminator*/); while(pSql && wasm.peek(pSql, 'i8') /* Maintenance reminder:^^^ _must_ be 'i8' or else we will very likely cause an endless loop. What that's doing is checking for a terminating NUL byte. If we use i32 or similar then we read 4 bytes, read stuff around the NUL terminator, and get stuck in and endless loop at the end of the SQL, endlessly re-preparing an empty statement. */ ){ wasm.pokePtr([ppStmt, pzTail], 0); DB.checkRc(this, capi.sqlite3_prepare_v3( this.pointer, pSql, sqlByteLen, 0, ppStmt, pzTail )); const pStmt = wasm.peekPtr(ppStmt); pSql = wasm.peekPtr(pzTail); sqlByteLen = pSqlEnd - pSql; if(!pStmt) continue; if(saveSql) saveSql.push(capi.sqlite3_sql(pStmt).trim()); stmt = new Stmt(this, pStmt, BindTypes); if(bind && stmt.parameterCount){ stmt.bind(bind); bind = null; } if(evalFirstResult && stmt.columnCount){ /* Only forward SELECT results for the FIRST query in the SQL which potentially has them. */ evalFirstResult = false; if(Array.isArray(opt.columnNames)){ stmt.getColumnNames(opt.columnNames); } if(arg.cbArg || resultRows){ for(; stmt.step(); stmt._isLocked = false){ stmt._isLocked = true; const row = arg.cbArg(stmt); if(resultRows) resultRows.push(row); if(callback && false === callback.call(opt, row, stmt)){ break; } } stmt._isLocked = false; } }else{ stmt.step(); } stmt.finalize(); stmt = null; } }/*catch(e){ console.warn("DB.exec() is propagating exception",opt,e); throw e; }*/finally{ if(stmt){ delete stmt._isLocked; stmt.finalize(); } wasm.scopedAllocPop(stack); } return arg.returnVal(); }/*exec()*/, /** Creates a new UDF (User-Defined Function) which is accessible via SQL code. This function may be called in any of the following forms: - (name, function) - (name, function, optionsObject) - (name, optionsObject) - (optionsObject) In the final two cases, the function must be defined as the `callback` property of the options object (optionally called `xFunc` to align with the C API documentation). In the final case, the function's name must be the 'name' property. The first two call forms can only be used for creating scalar functions. Creating an aggregate or window function requires the options-object form (see below for details). UDFs can be removed as documented for sqlite3_create_function_v2() and sqlite3_create_window_function(), but doing so will "leak" the JS-created WASM binding of those functions (meaning that their entries in the WASM indirect function table still exist). Eliminating that potential leak is a pending TODO. On success, returns this object. Throws on error. When called from SQL arguments to the UDF, and its result, will be converted between JS and SQL with as much fidelity as is feasible, triggering an exception if a type conversion cannot be determined. The docs for sqlite3_create_function_v2() |
︙ | ︙ | |||
1035 1036 1037 1038 1039 1040 1041 | If passed a 3rd argument, it is expected to be one of the SQLITE_{typename} constants. Passing the undefined value is the same as not passing a value. Throws on error (e.g. malformed SQL). */ selectValue: function(sql,bind,asType){ | > > | > > > > > > > > > > > > > | | | > | 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 | If passed a 3rd argument, it is expected to be one of the SQLITE_{typename} constants. Passing the undefined value is the same as not passing a value. Throws on error (e.g. malformed SQL). */ selectValue: function(sql,bind,asType){ return __selectFirstRow(this, sql, bind, 0, asType); }, /** Runs the given query and returns an array of the values from the first result column of each row of the result set. The 2nd argument is an optional value for use in a single-argument call to Stmt.bind(). The 3rd argument may be any value suitable for use as the 2nd argument to Stmt.get(). If a 3rd argument is desired but no bind data are needed, pass `undefined` for the 2nd argument. If there are no result rows, an empty array is returned. */ selectValues: function(sql,bind,asType){ const stmt = this.prepare(sql), rc = []; try { stmt.bind(bind); while(stmt.step()) rc.push(stmt.get(0,asType)); }finally{ stmt.finalize(); } return rc; }, /** Prepares the given SQL, step()s it one time, and returns an array containing the values of the first result row. If it has no results, `undefined` is returned. If passed a second argument other than `undefined`, it is treated like an argument to Stmt.bind(), so may be any type |
︙ | ︙ | |||
1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 | supported by that function. Throws on error (e.g. malformed SQL). */ selectObject: function(sql,bind){ return __selectFirstRow(this, sql, bind, {}); }, /** Returns the number of currently-opened Stmt handles for this db | > > > > > > > > > > > > > > > > > > > > | > > > | 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 | supported by that function. Throws on error (e.g. malformed SQL). */ selectObject: function(sql,bind){ return __selectFirstRow(this, sql, bind, {}); }, /** Runs the given SQL and returns an array of all results, with each row represented as an array, as per the 'array' `rowMode` option to `exec()`. An empty result set resolves to an empty array. The second argument, if any, is treated as the 'bind' option to a call to exec(). */ selectArrays: function(sql,bind){ return __selectAll(this, sql, bind, 'array'); }, /** Works identically to selectArrays() except that each value in the returned array is an object, as per the 'object' `rowMode` option to `exec()`. */ selectObjects: function(sql,bind){ return __selectAll(this, sql, bind, 'object'); }, /** Returns the number of currently-opened Stmt handles for this db handle, or 0 if this DB instance is closed. Note that only handles prepared via this.prepare() are counted, and not handles prepared using capi.sqlite3_prepare_v3() (or equivalent). */ openStatementCount: function(){ return this.pointer ? Object.keys(__stmtMap.get(this)).length : 0; }, /** Starts a transaction, calls the given callback, and then either |
︙ | ︙ | |||
1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 | const rc = callback(this); this.exec("RELEASE oo1"); return rc; }catch(e){ this.exec("ROLLBACK to SAVEPOINT oo1; RELEASE SAVEPOINT oo1"); throw e; } } }/*DB.prototype*/; /** Throws if the given Stmt has been finalized, else stmt is returned. */ const affirmStmtOpen = function(stmt){ | > > > > > > > > | 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 | const rc = callback(this); this.exec("RELEASE oo1"); return rc; }catch(e){ this.exec("ROLLBACK to SAVEPOINT oo1; RELEASE SAVEPOINT oo1"); throw e; } }, /** A convenience form of DB.checkRc(this,resultCode). If it does not throw, it returns this object. */ checkRc: function(resultCode){ return DB.checkRc(this, resultCode); } }/*DB.prototype*/; /** Throws if the given Stmt has been finalized, else stmt is returned. */ const affirmStmtOpen = function(stmt){ |
︙ | ︙ | |||
1154 1155 1156 1157 1158 1159 1160 | case BindTypes.string: return t; case BindTypes.bigint: if(wasm.bigIntEnabled) return t; /* else fall through */ default: //console.log("isSupportedBindType",t,v); | > | | 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 | case BindTypes.string: return t; case BindTypes.bigint: if(wasm.bigIntEnabled) return t; /* else fall through */ default: //console.log("isSupportedBindType",t,v); return (util.isBindableTypedArray(v) || (v instanceof ArrayBuffer)) ? BindTypes.blob : undefined; } }; /** If isSupportedBindType(v) returns a truthy value, this function returns that value, else it throws. */ |
︙ | ︙ | |||
1208 1209 1210 1211 1212 1213 1214 | /** Binds a single bound parameter value on the given stmt at the given index (numeric or named) using the given bindType (see the BindTypes enum) and value. Throws on error. Returns stmt on success. */ const bindOne = function f(stmt,ndx,bindType,val){ | | < < < < | < < | | < < < < < < < < < < < < < < | 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 | /** Binds a single bound parameter value on the given stmt at the given index (numeric or named) using the given bindType (see the BindTypes enum) and value. Throws on error. Returns stmt on success. */ const bindOne = function f(stmt,ndx,bindType,val){ affirmUnlocked(affirmStmtOpen(stmt), 'bind()'); if(!f._){ f._tooBigInt = (v)=>toss3( "BigInt value is too big to store without precision loss:", v ); /* Reminder: when not in BigInt mode, it's impossible for JS to represent a number out of the range we can bind, so we have no range checking. */ f._ = { string: function(stmt, ndx, val, asBlob){ const [pStr, n] = wasm.allocCString(val, true); const f = asBlob ? capi.sqlite3_bind_blob : capi.sqlite3_bind_text; return f(stmt.pointer, ndx, pStr, n, capi.SQLITE_WASM_DEALLOC); } }; }/* static init */ affirmSupportedBindType(val); ndx = affirmParamIndex(stmt,ndx); let rc = 0; switch((null===val || undefined===val) ? BindTypes.null : bindType){ |
︙ | ︙ | |||
1285 1286 1287 1288 1289 1290 1291 1292 1293 | } case BindTypes.boolean: rc = capi.sqlite3_bind_int(stmt.pointer, ndx, val ? 1 : 0); break; case BindTypes.blob: { if('string'===typeof val){ rc = f._.string(stmt, ndx, val, true); }else if(!util.isBindableTypedArray(val)){ toss3("Binding a value as a blob requires", | > > > | < < < < < < < < < < | < | < > | | < < < < > | 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 | } case BindTypes.boolean: rc = capi.sqlite3_bind_int(stmt.pointer, ndx, val ? 1 : 0); break; case BindTypes.blob: { if('string'===typeof val){ rc = f._.string(stmt, ndx, val, true); break; }else if(val instanceof ArrayBuffer){ val = new Uint8Array(val); }else if(!util.isBindableTypedArray(val)){ toss3("Binding a value as a blob requires", "that it be a string, Uint8Array, Int8Array, or ArrayBuffer."); } const pBlob = wasm.alloc(val.byteLength || 1); wasm.heap8().set(val.byteLength ? val : [0], pBlob) rc = capi.sqlite3_bind_blob(stmt.pointer, ndx, pBlob, val.byteLength, capi.SQLITE_WASM_DEALLOC); break; } default: console.warn("Unsupported bind() argument type:",val); toss3("Unsupported bind() argument type: "+(typeof val)); } if(rc) DB.checkRc(stmt.db.pointer, rc); stmt._mayGet = false; return stmt; }; Stmt.prototype = { /** "Finalizes" this statement. This is a no-op if the statement has already been finalizes. Returns |
︙ | ︙ | |||
1402 1403 1404 1405 1406 1407 1408 | BigInt as an int32 or a double if it can do so without loss of precision. If the BigInt is _too BigInt_ then it will throw. - Strings are bound as strings (use bindAsBlob() to force blob binding). | | | | 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 | BigInt as an int32 or a double if it can do so without loss of precision. If the BigInt is _too BigInt_ then it will throw. - Strings are bound as strings (use bindAsBlob() to force blob binding). - Uint8Array, Int8Array, and ArrayBuffer instances are bound as blobs. (TODO? binding the other TypedArray types.) If passed an array, each element of the array is bound at the parameter index equal to the array index plus 1 (because arrays are 0-based but binding is 1-based). If passed an object, each object key is treated as a bindable parameter name. The object keys _must_ match any |
︙ | ︙ | |||
1458 1459 1460 1461 1462 1463 1464 1465 | else if(Array.isArray(arg)){ /* bind each entry by index */ if(1!==arguments.length){ toss3("When binding an array, an index argument is not permitted."); } arg.forEach((v,i)=>bindOne(this, i+1, affirmSupportedBindType(v), v)); return this; } | > > | | 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 | else if(Array.isArray(arg)){ /* bind each entry by index */ if(1!==arguments.length){ toss3("When binding an array, an index argument is not permitted."); } arg.forEach((v,i)=>bindOne(this, i+1, affirmSupportedBindType(v), v)); return this; }else if(arg instanceof ArrayBuffer){ arg = new Uint8Array(arg); } if('object'===typeof arg/*null was checked above*/ && !util.isBindableTypedArray(arg)){ /* Treat each property of arg as a named bound parameter. */ if(1!==arguments.length){ toss3("When binding an object, an index argument is not permitted."); } Object.keys(arg) .forEach(k=>bindOne(this, k, |
︙ | ︙ | |||
1481 1482 1483 1484 1485 1486 1487 | }, /** Special case of bind() which binds the given value using the BLOB binding mechanism instead of the default selected one for the value. The ndx may be a numbered or named bind index. The value must be of type string, null/undefined (both get treated as null), or a TypedArray of a type supported by the bind() | | | < < | 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 | }, /** Special case of bind() which binds the given value using the BLOB binding mechanism instead of the default selected one for the value. The ndx may be a numbered or named bind index. The value must be of type string, null/undefined (both get treated as null), or a TypedArray of a type supported by the bind() API. This API cannot bind numbers as blobs. If passed a single argument, a bind index of 1 is assumed and the first argument is the value. */ bindAsBlob: function(ndx,arg){ affirmStmtOpen(this); if(1===arguments.length){ arg = ndx; ndx = 1; } const t = affirmSupportedBindType(arg); if(BindTypes.string !== t && BindTypes.blob !== t && BindTypes.null !== t){ toss3("Invalid value type for bindAsBlob()"); } return bindOne(this, ndx, BindTypes.blob, arg); }, /** Steps the statement one time. If the result indicates that a row of data is available, a truthy value is returned. If no row of data is available, a falsy value is returned. Throws on error. */ |
︙ | ︙ | |||
1565 1566 1567 1568 1569 1570 1571 | stepFinalize: function(){ const rc = this.step(); this.finalize(); return rc; }, /** Fetches the value from the given 0-based column index of | | | 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 | stepFinalize: function(){ const rc = this.step(); this.finalize(); return rc; }, /** Fetches the value from the given 0-based column index of the current data row, throwing if index is out of range. Requires that step() has just returned a truthy value, else an exception is thrown. By default it will determine the data type of the result automatically. If passed a second arugment, it must be one of the enumeration values for sqlite3 types, which are |
︙ | ︙ | |||
1745 1746 1747 1748 1749 1750 1751 | } Object.defineProperty(Stmt.prototype, 'pointer', prop); Object.defineProperty(DB.prototype, 'pointer', prop); } /** The OO API's public namespace. */ sqlite3.oo1 = { | < < < < | 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 | } Object.defineProperty(Stmt.prototype, 'pointer', prop); Object.defineProperty(DB.prototype, 'pointer', prop); } /** The OO API's public namespace. */ sqlite3.oo1 = { DB, Stmt }/*oo1 object*/; if(util.isUIThread()){ /** Functionally equivalent to DB(storageName,'c','kvvfs') except |
︙ | ︙ |
Deleted ext/wasm/api/sqlite3-api-opfs.js.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to ext/wasm/api/sqlite3-api-prologue.js.
1 2 3 4 5 6 7 8 9 10 11 12 13 | /* 2022-05-22 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 intended to be combined at build-time with other | | | | < < | < | < | < | < < < < | < < | < < | < < < < < < < < < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | /* 2022-05-22 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 intended to be combined at build-time with other related code, most notably a header and footer which wraps this whole file into an Emscripten Module.postRun() handler which has a parameter named "Module" (the Emscripten Module object). The sqlite3 JS API has no hard requirements on Emscripten, and does not expose any Emscripten APIs to clients. It is structured such that its build can be tweaked to include it in arbitrary WASM environments which supply the necessary underlying features (e.g. a POSIX file I/O layer). Main project home page: https://sqlite.org Documentation home page: https://sqlite.org/wasm */ /** sqlite3ApiBootstrap() is the only global symbol persistently exposed by this API. It is intended to be called one time at the end of the API amalgamation process, passed configuration details for the current environment, and then optionally be removed from the global object using `delete self.sqlite3ApiBootstrap`. This function is not intended for client-level use. It is intended for use in creating bundles configured for specific WASM environments. This function expects a configuration object, intended to abstract away details specific to any given WASM environment, primarily so that it can be used without any _direct_ dependency on Emscripten. (Note the default values for the config object!) The config object is only honored the first time this is called. Subsequent calls ignore the argument and return the same |
︙ | ︙ | |||
115 116 117 118 119 120 121 | - `bigIntEnabled`: true if BigInt support is enabled. Defaults to true if `self.BigInt64Array` is available, else false. Some APIs will throw exceptions if called without BigInt support, as BigInt is required for marshalling C-side int64 into and out of JS. - `allocExportName`: the name of the function, in `exports`, of the `malloc(3)`-compatible routine for the WASM environment. Defaults | | > > > > | | | < < < < > > > > | 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | - `bigIntEnabled`: true if BigInt support is enabled. Defaults to true if `self.BigInt64Array` is available, else false. Some APIs will throw exceptions if called without BigInt support, as BigInt is required for marshalling C-side int64 into and out of JS. - `allocExportName`: the name of the function, in `exports`, of the `malloc(3)`-compatible routine for the WASM environment. Defaults to `"sqlite3_malloc"`. Beware that using any allocator other than sqlite3_malloc() may require care in certain client-side code regarding which allocator is uses. Notably, sqlite3_deserialize() and sqlite3_serialize() can only safely use memory from different allocators under very specific conditions. - `deallocExportName`: the name of the function, in `exports`, of the `free(3)`-compatible routine for the WASM environment. Defaults to `"sqlite3_free"`. - `reallocExportName`: the name of the function, in `exports`, of the `realloc(3)`-compatible routine for the WASM environment. Defaults to `"sqlite3_realloc"`. - `wasmfsOpfsDir`[^1]: As of 2022-12-17, this feature does not currently work due to incompatible Emscripten-side changes made in the WASMFS+OPFS combination. This option is currently ignored. [^1] = This property may optionally be a function, in which case this function re-assigns it to the value returned from that function, enabling delayed evaluation. */ 'use strict'; |
︙ | ︙ | |||
154 155 156 157 158 159 160 | if('undefined'!==typeof Module){ /* Emscripten module will contain HEAPU64 when built with -sWASM_BIGINT=1, else it will not. */ return !!Module.HEAPU64; } return !!self.BigInt64Array; })(), | < < | > > > > > > > > > > > > > > > > > > | < < < < | 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | if('undefined'!==typeof Module){ /* Emscripten module will contain HEAPU64 when built with -sWASM_BIGINT=1, else it will not. */ return !!Module.HEAPU64; } return !!self.BigInt64Array; })(), wasmfsOpfsDir: '/opfs', /** useStdAlloc is just for testing an allocator discrepancy. The docs guarantee that this is false in the canonical builds. For 99% of purposes it doesn't matter which allocators we use, but it becomes significant with, e.g., sqlite3_deserialize() and certain wasm.xWrap.resultAdapter()s. */ useStdAlloc: false }, apiConfig || {}); Object.assign(config, { allocExportName: config.useStdAlloc ? 'malloc' : 'sqlite3_malloc', deallocExportName: config.useStdAlloc ? 'free' : 'sqlite3_free', reallocExportName: config.useStdAlloc ? 'realloc' : 'sqlite3_realloc' }, config); [ // If any of these config options are functions, replace them with // the result of calling that function... 'exports', 'memory', 'wasmfsOpfsDir' ].forEach((k)=>{ if('function' === typeof config[k]){ config[k] = config[k](); } }); config.wasmOpfsDir = /* 2022-12-17: WASMFS+OPFS can no longer be activated from the main thread (aborts via a failed assert() if it's attempted), which eliminates any(?) benefit to supporting it. */ false; /** The main sqlite3 binding API gets installed into this object, mimicking the C API as closely as we can. The numerous members names with prefixes 'sqlite3_' and 'SQLITE_' behave, insofar as possible, identically to the C-native counterparts, as documented at: https://www.sqlite.org/c3ref/intro.html A very few exceptions require an additional level of proxy function or may otherwise require special attention in the WASM environment, and all such cases are documented somewhere below in this file or in sqlite3-api-glue.js. capi members which are not documented are installed as 1-to-1 proxies for their C-side counterparts. */ const capi = Object.create(null); /** Holds state which are specific to the WASM-related infrastructure and glue code. Note that a number of members of this object are injected dynamically after the api object is fully constructed, so not all are documented in this file. */ const wasm = Object.create(null); |
︙ | ︙ | |||
219 220 221 222 223 224 225 | C-style APIs do and the object-oriented APIs use exceptions exclusively to report errors. */ class SQLite3Error extends Error { /** Constructs this object with a message depending on its arguments: | | | | < | > | < < > | > > > | > > > > > > > | | > > > > > > > > > > | < | | | | > > > | 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 | C-style APIs do and the object-oriented APIs use exceptions exclusively to report errors. */ class SQLite3Error extends Error { /** Constructs this object with a message depending on its arguments: If its first argument is an integer, it is assumed to be an SQLITE_... result code and it is passed to sqlite3.capi.sqlite3_js_rc_str() to stringify it. If called with exactly 2 arguments and the 2nd is an object, that object is treated as the 2nd argument to the parent constructor. The exception's message is created by concatenating its arguments with a space between each, except for the two-args-with-an-objec form and that the first argument will get coerced to a string, as described above, if it's an integer. If passed an integer first argument, the error object's `resultCode` member will be set to the given integer value, else it will be set to capi.SQLITE_ERROR. */ constructor(...args){ let rc; if(args.length){ if(__isInt(args[0])){ rc = args[0]; if(1===args.length){ super(__rcStr(args[0])); }else{ const rcStr = __rcStr(rc); if('object'===typeof args[1]){ super(rcStr,args[1]); }else{ args[0] = rcStr+':'; super(args.join(' ')); } } }else{ if(2===args.length && 'object'===typeof args[1]){ super(...args); }else{ super(args.join(' ')); } } } this.resultCode = rc || capi.SQLITE_ERROR; this.name = 'SQLite3Error'; } }; /** Functionally equivalent to the SQLite3Error constructor but may be used as part of an expression, e.g.: |
︙ | ︙ | |||
334 335 336 337 338 339 340 | const typedArrayPart = (aTypedArray, begin, end)=>{ return isSharedTypedArray(aTypedArray) ? aTypedArray.slice(begin, end) : aTypedArray.subarray(begin, end); }; /** | | | | > > > | > > | > > | 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 | const typedArrayPart = (aTypedArray, begin, end)=>{ return isSharedTypedArray(aTypedArray) ? aTypedArray.slice(begin, end) : aTypedArray.subarray(begin, end); }; /** Returns v if v appears to be one of our bind()-able TypedArray types: Uint8Array or Int8Array or ArrayBuffer. Support for TypedArrays with element sizes >1 is a potential TODO just waiting on a use case to justify them. Until then, their `buffer` property can be used to pass them as an ArrayBuffer. If it's not a bindable array type, a falsy value is returned. */ const isBindableTypedArray = (v)=>{ return v && (v instanceof Uint8Array || v instanceof Int8Array || v instanceof ArrayBuffer); }; /** Returns true if v appears to be one of the TypedArray types which is legal for holding SQL code (as opposed to binary blobs). Currently this is the same as isBindableTypedArray() but it seems likely that we'll eventually want to add Uint32Array and friends to the isBindableTypedArray() list but not to the isSQLableTypedArray() list. */ const isSQLableTypedArray = (v)=>{ return v && (v instanceof Uint8Array || v instanceof Int8Array || v instanceof ArrayBuffer); }; /** Returns true if isBindableTypedArray(v) does, else throws with a message that v is not a supported TypedArray value. */ const affirmBindableTypedArray = (v)=>{ return isBindableTypedArray(v) || toss3("Value is not of a supported TypedArray type."); |
︙ | ︙ | |||
379 380 381 382 383 384 385 | const typedArrayToString = function(typedArray, begin, end){ return utf8Decoder.decode(typedArrayPart(typedArray, begin,end)); }; /** If v is-a Array, its join("") result is returned. If isSQLableTypedArray(v) is true then typedArrayToString(v) is | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > | 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 | const typedArrayToString = function(typedArray, begin, end){ return utf8Decoder.decode(typedArrayPart(typedArray, begin,end)); }; /** If v is-a Array, its join("") result is returned. If isSQLableTypedArray(v) is true then typedArrayToString(v) is returned. If it looks like a WASM pointer, wasm.cstrToJs(v) is returned. Else v is returned as-is. */ const flexibleString = function(v){ if(isSQLableTypedArray(v)) return typedArrayToString(v); else if(Array.isArray(v)) return v.join(""); else if(wasm.isPtr(v)) v = wasm.cstrToJs(v); return v; }; /** An Error subclass specifically for reporting Wasm-level malloc() failure and enabling clients to unambiguously identify such exceptions. */ class WasmAllocError extends Error { /** If called with 2 arguments and the 2nd one is an object, it behaves like the Error constructor, else it concatenates all arguments together with a single space between each to construct an error message string. As a special case, if called with no arguments then it uses a default error message. */ constructor(...args){ if(2===args.length && 'object'===typeof args[1]){ super(...args); }else if(args.length){ super(args.join(' ')); }else{ super("Allocation failed."); } this.resultCode = capi.SQLITE_NOMEM; this.name = 'WasmAllocError'; } }; /** Functionally equivalent to the WasmAllocError constructor but may be used as part of an expression, e.g.: ``` return someAllocatingFunction(x) || WasmAllocError.toss(...); ``` */ WasmAllocError.toss = (...args)=>{ throw new WasmAllocError(...args); }; Object.assign(capi, { /** sqlite3_bind_blob() works exactly like its C counterpart unless its 3rd argument is one of: - JS string: the 3rd argument is converted to a C string, the 4th argument is ignored, and the C-string's length is used in its place. - Array: converted to a string as defined for "flexible strings" and then it's treated as a JS string. - Int8Array or Uint8Array: wasm.allocFromTypedArray() is used to conver the memory to the WASM heap. If the 4th argument is 0 or greater, it is used as-is, otherwise the array's byteLength value is used. This is an exception to the C API's undefined behavior for a negative 4th argument, but results are undefined if the given 4th argument value is greater than the byteLength of the input array. - If it's an ArrayBuffer, it gets wrapped in a Uint8Array and treated as that type. In all of those cases, the final argument (destructor) is ignored and capi.SQLITE_WASM_DEALLOC is assumed. A 3rd argument of `null` is treated as if it were a WASM pointer of 0. If the 3rd argument is neither a WASM pointer nor one of the above-described types, capi.SQLITE_MISUSE is returned. The first argument may be either an `sqlite3_stmt*` WASM pointer or an sqlite3.oo1.Stmt instance. For consistency with the C API, it requires the same number of arguments. It returns capi.SQLITE_MISUSE if passed any other argument count. */ sqlite3_bind_blob: undefined/*installed later*/, /** sqlite3_bind_text() works exactly like its C counterpart unless its 3rd argument is one of: - JS string: the 3rd argument is converted to a C string, the 4th argument is ignored, and the C-string's length is used in its place. - Array: converted to a string as defined for "flexible strings". The 4th argument is ignored and a value of -1 is assumed. - Int8Array or Uint8Array: is assumed to contain UTF-8 text, is converted to a string. The 4th argument is ignored, replaced by the array's byteLength value. - If it's an ArrayBuffer, it gets wrapped in a Uint8Array and treated as that type. In each of those cases, the final argument (text destructor) is ignored and capi.SQLITE_WASM_DEALLOC is assumed. A 3rd argument of `null` is treated as if it were a WASM pointer of 0. If the 3rd argument is neither a WASM pointer nor one of the above-described types, capi.SQLITE_MISUSE is returned. The first argument may be either an `sqlite3_stmt*` WASM pointer or an sqlite3.oo1.Stmt instance. For consistency with the C API, it requires the same number of arguments. It returns capi.SQLITE_MISUSE if passed any other argument count. If client code needs to bind partial strings, it needs to either parcel the string up before passing it in here or it must pass in a WASM pointer for the 3rd argument and a valid 4th-argument value, taking care not to pass a value which truncates a multi-byte UTF-8 character. When passing WASM-format strings, it is important that the final argument be valid or unexpected content can result can result, or even a crash if the application reads past the WASM heap bounds. */ sqlite3_bind_text: undefined/*installed later*/, /** sqlite3_create_function_v2() differs from its native counterpart only in the following ways: 1) The fourth argument (`eTextRep`) argument must not specify any encoding other than sqlite3.SQLITE_UTF8. The JS API does not currently support any other encoding and likely never will. This function does not replace that argument on its own because it may contain other flags. As a special case, if the bottom 4 bits of that argument are 0, SQLITE_UTF8 is assumed. 2) Any of the four final arguments may be either WASM pointers (assumed to be function pointers) or JS Functions. In the latter case, each gets bound to WASM using sqlite3.capi.wasm.installFunction() and that wrapper is passed on to the native implementation. For consistency with the C API, it requires the same number of arguments. It returns capi.SQLITE_MISUSE if passed any other argument count. The semantics of JS functions are: xFunc: is passed `(pCtx, ...values)`. Its return value becomes the new SQL function's result. xStep: is passed `(pCtx, ...values)`. Its return value is |
︙ | ︙ | |||
529 530 531 532 533 534 535 | C reference: https://www.sqlite.org/c3ref/create_function.html Maintenance reminder: the ability to add new WASM-accessible functions to the runtime requires that the WASM build is compiled with emcc's `-sALLOW_TABLE_GROWTH` flag. */ | | | | | | | | 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 | C reference: https://www.sqlite.org/c3ref/create_function.html Maintenance reminder: the ability to add new WASM-accessible functions to the runtime requires that the WASM build is compiled with emcc's `-sALLOW_TABLE_GROWTH` flag. */ sqlite3_create_function_v2: ( pDb, funcName, nArg, eTextRep, pApp, xFunc, xStep, xFinal, xDestroy )=>{/*installed later*/}, /** Equivalent to passing the same arguments to sqlite3_create_function_v2(), with 0 as the final argument. */ sqlite3_create_function: ( pDb, funcName, nArg, eTextRep, pApp, xFunc, xStep, xFinal )=>{/*installed later*/}, /** The sqlite3_create_window_function() JS wrapper differs from its native implementation in the exact same way that sqlite3_create_function_v2() does. The additional function, xInverse(), is treated identically to xStep() by the wrapping layer. */ sqlite3_create_window_function: ( pDb, funcName, nArg, eTextRep, pApp, xStep, xFinal, xValue, xInverse, xDestroy )=>{/*installed later*/}, /** The sqlite3_prepare_v3() binding handles two different uses with differing JS/WASM semantics: 1) sqlite3_prepare_v3(pDb, sqlString, -1, prepFlags, ppStmt , null) 2) sqlite3_prepare_v3(pDb, sqlPointer, sqlByteLen, prepFlags, ppStmt, sqlPointerToPointer) |
︙ | ︙ | |||
596 597 598 599 600 601 602 | documented for the C-side sqlite3_prepare_v3(). In case (2), the underlying C function is called with the equivalent of: (pDb, sqlAsPointer, sqlByteLen, prepFlags, ppStmt, pzTail) It returns its result and compiled statement as documented in the C API. Fetching the output pointers (5th and 6th | | | 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 | documented for the C-side sqlite3_prepare_v3(). In case (2), the underlying C function is called with the equivalent of: (pDb, sqlAsPointer, sqlByteLen, prepFlags, ppStmt, pzTail) It returns its result and compiled statement as documented in the C API. Fetching the output pointers (5th and 6th parameters) requires using `capi.wasm.peek()` (or equivalent) and the `pzTail` will point to an address relative to the `sqlAsPointer` value. If passed an invalid 2nd argument type, this function will return SQLITE_MISUSE and sqlite3_errmsg() will contain a string describing the problem. |
︙ | ︙ | |||
633 634 635 636 637 638 639 | parameter order and will call it like: `callback(pVoid, colCount, listOfValues, listOfColNames)` If the callback is not a JS function then this binding performs no translation of the callback, but the sql argument is still converted to a WASM string for the call using the | | | 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 | parameter order and will call it like: `callback(pVoid, colCount, listOfValues, listOfColNames)` If the callback is not a JS function then this binding performs no translation of the callback, but the sql argument is still converted to a WASM string for the call using the "string:flexible" argument converter. */ sqlite3_exec: (pDb, sql, callback, pVoid, pErrMsg)=>{}/*installed later*/, /** If passed a single argument which appears to be a byte-oriented TypedArray (Int8Array or Uint8Array), this function treats that TypedArray as an output target, fetches `theArray.byteLength` |
︙ | ︙ | |||
666 667 668 669 670 671 672 | */ const util = { affirmBindableTypedArray, flexibleString, bigIntFits32, bigIntFits64, bigIntFitsDouble, isBindableTypedArray, isInt32, isSQLableTypedArray, isTypedArray, typedArrayToString, | > | > > | | < < | 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 | */ const util = { affirmBindableTypedArray, flexibleString, bigIntFits32, bigIntFits64, bigIntFitsDouble, isBindableTypedArray, isInt32, isSQLableTypedArray, isTypedArray, typedArrayToString, isUIThread: ()=>(self.window===self && !!self.document), // is this true for ESM?: 'undefined'===typeof WorkerGlobalScope isSharedTypedArray, toss: function(...args){throw new Error(args.join(' '))}, toss3, typedArrayPart }; Object.assign(wasm, { /** Emscripten APIs have a deep-seated assumption that all pointers are 32 bits. We'll remain optimistic that that won't always be the case and will use this constant in places where we might otherwise use a hard-coded 4. */ ptrSizeof: config.wasmPtrSizeof || 4, /** The WASM IR (Intermediate Representation) value for pointer-type values. It MUST refer to a value type of the size described by this.ptrSizeof. */ ptrIR: config.wasmPtrIR || "i32", /** True if BigInt support was enabled via (e.g.) the Emscripten -sWASM_BIGINT flag, else false. When enabled, certain 64-bit sqlite3 APIs are enabled which are not otherwise enabled due to JS/WASM int64 |
︙ | ︙ | |||
713 714 715 716 717 718 719 | */ memory: config.memory || config.exports['memory'] || toss3("API config object requires a WebAssembly.Memory object", "in either config.exports.memory (exported)", "or config.memory (imported)."), /** | | | | | | | | > > > > > > > > > > > > > > > > > > > | | < > > > < < < < > > > > > | | > | | | | | < | < | | > > > > > | | > | | 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 | */ memory: config.memory || config.exports['memory'] || toss3("API config object requires a WebAssembly.Memory object", "in either config.exports.memory (exported)", "or config.memory (imported)."), /** The API's primary point of access to the WASM-side memory allocator. Works like sqlite3_malloc() but throws a WasmAllocError if allocation fails. It is important that any code which might pass through the sqlite3 C API NOT throw and must instead return SQLITE_NOMEM (or equivalent, depending on the context). Very few cases in the sqlite3 JS APIs can result in client-defined functions propagating exceptions via the C-style API. Most notably, this applies to WASM-bound JS functions which are created directly by clients and passed on _as WASM function pointers_ to functions such as sqlite3_create_function_v2(). Such bindings created transparently by this API will automatically use wrappers which catch exceptions and convert them to appropriate error codes. For cases where non-throwing allocation is required, use this.alloc.impl(), which is direct binding of the underlying C-level allocator. Design note: this function is not named "malloc" primarily because Emscripten uses that name and we wanted to avoid any confusion early on in this code's development, when it still had close ties to Emscripten's glue code. */ alloc: undefined/*installed later*/, /** Rarely necessary in JS code, this routine works like sqlite3_realloc(M,N), where M is either NULL or a pointer obtained from this function or this.alloc() and N is the number of bytes to reallocate the block to. Returns a pointer to the reallocated block or 0 if allocation fails. If M is NULL and N is positive, this behaves like this.alloc(N). If N is 0, it behaves like this.dealloc(). Results are undefined if N is negative (sqlite3_realloc() treats that as 0, but if this code is built with a different allocator it may misbehave with negative values). Like this.alloc.impl(), this.realloc.impl() is a direct binding to the underlying realloc() implementation which does not throw exceptions, instead returning 0 on allocation error. */ realloc: undefined/*installed later*/, /** The API's primary point of access to the WASM-side memory deallocator. Works like sqlite3_free(). Design note: this function is not named "free" for the same reason that this.alloc() is not called this.malloc(). */ dealloc: undefined/*installed later*/ /* Many more wasm-related APIs get installed later on. */ }/*wasm*/); /** wasm.alloc()'s srcTypedArray.byteLength bytes, populates them with the values from the source TypedArray, and returns the pointer to that memory. The returned pointer must eventually be passed to wasm.dealloc() to clean it up. The argument may be a Uint8Array, Int8Array, or ArrayBuffer, and it throws if passed any other type. As a special case, to avoid further special cases where this is used, if srcTypedArray.byteLength is 0, it allocates a single byte and sets it to the value 0. Even in such cases, calls must behave as if the allocated memory has exactly srcTypedArray.byteLength bytes. */ wasm.allocFromTypedArray = function(srcTypedArray){ if(srcTypedArray instanceof ArrayBuffer){ srcTypedArray = new Uint8Array(srcTypedArray); } affirmBindableTypedArray(srcTypedArray); const pRet = wasm.alloc(srcTypedArray.byteLength || 1); wasm.heapForSize(srcTypedArray.constructor).set( srcTypedArray.byteLength ? srcTypedArray : [0], pRet ); return pRet; }; { // Set up allocators... const keyAlloc = config.allocExportName, keyDealloc = config.deallocExportName, keyRealloc = config.reallocExportName; for(const key of [keyAlloc, keyDealloc, keyRealloc]){ const f = wasm.exports[key]; if(!(f instanceof Function)) toss3("Missing required exports[",key,"] function."); } wasm.alloc = function f(n){ return f.impl(n) || WasmAllocError.toss("Failed to allocate",n," bytes."); }; wasm.alloc.impl = wasm.exports[keyAlloc]; wasm.realloc = function f(m,n){ const m2 = f.impl(m,n); return n ? (m2 || WasmAllocError.toss("Failed to reallocate",n," bytes.")) : 0; }; wasm.realloc.impl = wasm.exports[keyRealloc]; wasm.dealloc = wasm.exports[keyDealloc]; } /** Reports info about compile-time options using sqlite3_compileoption_get() and sqlite3_compileoption_used(). It has several distinct uses: If optName is an array then it is expected to be a list of compilation options and this function returns an object which maps each such option to true or false, indicating whether or not the given option was included in this build. That object is returned. |
︙ | ︙ | |||
834 835 836 837 838 839 840 | else if(!f._opt){ f._rx = /^([^=]+)=(.+)/; f._rxInt = /^-?\d+$/; f._opt = function(opt, rv){ const m = f._rx.exec(opt); rv[0] = (m ? m[1] : opt); rv[1] = m ? (f._rxInt.test(m[2]) ? +m[2] : m[2]) : true; | | | 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 | else if(!f._opt){ f._rx = /^([^=]+)=(.+)/; f._rxInt = /^-?\d+$/; f._opt = function(opt, rv){ const m = f._rx.exec(opt); rv[0] = (m ? m[1] : opt); rv[1] = m ? (f._rxInt.test(m[2]) ? +m[2] : m[2]) : true; }; } const rc = {}, ov = [0,0]; let i = 0, k; while((k = capi.sqlite3_compileoption_get(i++))){ f._opt(k,ov); rc[ov[0]] = ov[1]; } |
︙ | ︙ | |||
859 860 861 862 863 864 865 | }); return optName; } return ( 'string'===typeof optName ) ? !!capi.sqlite3_compileoption_used(optName) : false; }/*compileOptionUsed()*/; | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 970 971 972 973 974 975 976 977 978 979 980 981 982 983 | }); return optName; } return ( 'string'===typeof optName ) ? !!capi.sqlite3_compileoption_used(optName) : false; }/*compileOptionUsed()*/; /** sqlite3.wasm.pstack (pseudo-stack) holds a special-case stack-style allocator intended only for use with _small_ data of not more than (in total) a few kb in size, managed as if it were stack-based. |
︙ | ︙ | |||
1048 1049 1050 1051 1052 1053 1054 | This allocator is much faster than a general-purpose one but is limited to usage patterns like the one shown above. It operates from a static range of memory which lives outside of space managed by Emscripten's stack-management, so does not collide with Emscripten-provided stack allocation APIs. The memory lives in the WASM heap and can be used with routines such | | > > > > > | > > > > > | > > > | | 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 | This allocator is much faster than a general-purpose one but is limited to usage patterns like the one shown above. It operates from a static range of memory which lives outside of space managed by Emscripten's stack-management, so does not collide with Emscripten-provided stack allocation APIs. The memory lives in the WASM heap and can be used with routines such as wasm.poke() and any wasm.heap8u().slice(). */ wasm.pstack = Object.assign(Object.create(null),{ /** Sets the current pstack position to the given pointer. Results are undefined if the passed-in value did not come from this.pointer. */ restore: wasm.exports.sqlite3_wasm_pstack_restore, /** Attempts to allocate the given number of bytes from the pstack. On success, it zeroes out a block of memory of the given size, adjusts the pstack pointer, and returns a pointer to the memory. On error, returns throws a WasmAllocError. The memory must eventually be released using restore(). If n is a string, it must be a WASM "IR" value in the set accepted by wasm.sizeofIR(), which is mapped to the size of that data type. If passed a string not in that set, it throws a WasmAllocError. This method always adjusts the given value to be a multiple of 8 bytes because failing to do so can lead to incorrect results when reading and writing 64-bit values from/to the WASM heap. Similarly, the returned address is always 8-byte aligned. */ alloc: function(n){ if('string'===typeof n && !(n = wasm.sizeofIR(n))){ WasmAllocError.toss("Invalid value for pstack.alloc(",arguments[0],")"); } return wasm.exports.sqlite3_wasm_pstack_alloc(n) || WasmAllocError.toss("Could not allocate",n, "bytes from the pstack."); }, /** alloc()'s n chunks, each sz bytes, as a single memory block and returns the addresses as an array of n element, each holding the address of one chunk. sz may optionally be an IR string accepted by wasm.sizeofIR(). Throws a WasmAllocError if allocation fails. Example: ``` const [p1, p2, p3] = wasm.pstack.allocChunks(3,4); ``` */ allocChunks: function(n,sz){ if('string'===typeof sz && !(sz = wasm.sizeofIR(sz))){ WasmAllocError.toss("Invalid size value for allocChunks(",arguments[1],")"); } const mem = wasm.pstack.alloc(n * sz); const rc = []; let i = 0, offset = 0; for(; i < n; offset = (sz * ++i)){ rc.push(mem + offset); } return rc; }, /** A convenience wrapper for allocChunks() which sizes each chunk as either 8 bytes (safePtrSize is truthy) or wasm.ptrSizeof (if safePtrSize is falsy). How it returns its result differs depending on its first argument: if it's 1, it returns a single pointer value. If it's more than 1, it returns the same as allocChunks(). When a returned pointers will refer to a 64-bit value, e.g. a double or int64, and that value must be written or fetched, e.g. using wasm.poke() or wasm.peek(), it is important that the pointer in question be aligned to an 8-byte boundary or else it will not be fetched or written properly and will corrupt or read neighboring memory. However, when all pointers involved point to "small" data, it is safe to pass a falsy value to save a tiny bit of memory. */ |
︙ | ︙ | |||
1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 | } wasm.exports.sqlite3_randomness(...args); }; /** State for sqlite3_wasmfs_opfs_dir(). */ let __wasmfsOpfsDir = undefined; /** If the wasm environment has a WASMFS/OPFS-backed persistent storage directory, its path is returned by this function. If it does not then it returns "" (noting that "" is a falsy value). The first time this is called, this function inspects the current environment to determine whether persistence support is available and, if it is, enables it (if needed). This function currently only recognizes the WASMFS/OPFS storage combination and its path refers to storage rooted in the Emscripten-managed virtual filesystem. */ capi.sqlite3_wasmfs_opfs_dir = function(){ if(undefined !== __wasmfsOpfsDir) return __wasmfsOpfsDir; // If we have no OPFS, there is no persistent dir const pdir = config.wasmfsOpfsDir; if(!pdir || !self.FileSystemHandle || !self.FileSystemDirectoryHandle || !self.FileSystemFileHandle){ return __wasmfsOpfsDir = ""; } try{ | > > > > > > > > > | 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 | } wasm.exports.sqlite3_randomness(...args); }; /** State for sqlite3_wasmfs_opfs_dir(). */ let __wasmfsOpfsDir = undefined; /** 2022-12-17: incompatible WASMFS changes have made WASMFS+OPFS unavailable from the main thread, which eliminates the most significant benefit of supporting WASMFS. This function is now a no-op which always returns a falsy value. Before that change, this function behaved as documented below (and how it will again if we can find a compelling reason to support it). If the wasm environment has a WASMFS/OPFS-backed persistent storage directory, its path is returned by this function. If it does not then it returns "" (noting that "" is a falsy value). The first time this is called, this function inspects the current environment to determine whether persistence support is available and, if it is, enables it (if needed). This function currently only recognizes the WASMFS/OPFS storage combination and its path refers to storage rooted in the Emscripten-managed virtual filesystem. */ capi.sqlite3_wasmfs_opfs_dir = function(){ if(undefined !== __wasmfsOpfsDir) return __wasmfsOpfsDir; // If we have no OPFS, there is no persistent dir const pdir = config.wasmfsOpfsDir; console.error("sqlite3_wasmfs_opfs_dir() can no longer work due "+ "to incompatible WASMFS changes. It will be removed."); if(!pdir || !self.FileSystemHandle || !self.FileSystemDirectoryHandle || !self.FileSystemFileHandle){ return __wasmfsOpfsDir = ""; } try{ |
︙ | ︙ | |||
1294 1295 1296 1297 1298 1299 1300 | VFSes. */ capi.sqlite3_js_vfs_list = function(){ const rc = []; let pVfs = capi.sqlite3_vfs_find(0); while(pVfs){ const oVfs = new capi.sqlite3_vfs(pVfs); | | > | > > | > > > > > | | > | | > > > | | | | | 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 | VFSes. */ capi.sqlite3_js_vfs_list = function(){ const rc = []; let pVfs = capi.sqlite3_vfs_find(0); while(pVfs){ const oVfs = new capi.sqlite3_vfs(pVfs); rc.push(wasm.cstrToJs(oVfs.$zName)); pVfs = oVfs.$pNext; oVfs.dispose(); } return rc; }; /** A convenience wrapper around sqlite3_serialize() which serializes the given `sqlite3*` pointer to a Uint8Array. The first argument may be either an `sqlite3*` or an sqlite3.oo1.DB instance. On success it returns a Uint8Array. If the schema is empty, an empty array is returned. `schema` is the schema to serialize. It may be a WASM C-string pointer or a JS string. If it is falsy, it defaults to `"main"`. On error it throws with a description of the problem. */ capi.sqlite3_js_db_export = function(pDb, schema=0){ pDb = wasm.xWrap.testConvertArg('sqlite3*', pDb); if(!pDb) toss3('Invalid sqlite3* argument.'); if(!wasm.bigIntEnabled) toss3('BigInt64 support is not enabled.'); const scope = wasm.scopedAllocPush(); let pOut; try{ const pSize = wasm.scopedAlloc(8/*i64*/ + wasm.ptrSizeof); const ppOut = pSize + 8; /** Maintenance reminder, since this cost a full hour of grief and confusion: if the order of pSize/ppOut are reversed in that memory block, fetching the value of pSize after the export reads a garbage size because it's not on an 8-byte memory boundary! */ const zSchema = schema ? (wasm.isPtr(schema) ? schema : wasm.scopedAllocCString(''+schema)) : 0; let rc = wasm.exports.sqlite3_wasm_db_serialize( pDb, zSchema, ppOut, pSize, 0 ); if(rc){ toss3("Database serialization failed with code", sqlite3.capi.sqlite3_js_rc_str(rc)); } pOut = wasm.peekPtr(ppOut); const nOut = wasm.peek(pSize, 'i64'); rc = nOut ? wasm.heap8u().slice(pOut, pOut + Number(nOut)) : new Uint8Array(); return rc; }finally{ if(pOut) wasm.exports.sqlite3_free(pOut); wasm.scopedAllocPop(scope); } }; /** Given a `sqlite3*` and a database name (JS string or WASM C-string pointer, which may be 0), returns a pointer to the sqlite3_vfs responsible for it. If the given db name is null/0, |
︙ | ︙ | |||
1362 1363 1364 1365 1366 1367 1368 | */ capi.sqlite3_js_aggregate_context = (pCtx, n)=>{ return capi.sqlite3_aggregate_context(pCtx, n) || (n ? WasmAllocError.toss("Cannot allocate",n, "bytes for sqlite3_aggregate_context()") : 0); }; | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 | */ capi.sqlite3_js_aggregate_context = (pCtx, n)=>{ return capi.sqlite3_aggregate_context(pCtx, n) || (n ? WasmAllocError.toss("Cannot allocate",n, "bytes for sqlite3_aggregate_context()") : 0); }; /** Creates a file using the storage appropriate for the given sqlite3_vfs. The first argument may be a VFS name (JS string only, NOT a WASM C-string), WASM-managed `sqlite3_vfs*`, or a capi.sqlite3_vfs instance. Pass 0 (a NULL pointer) to use the default VFS. If passed a string which does not resolve using sqlite3_vfs_find(), an exception is thrown. (Note that a WASM C-string is not accepted because it is impossible to distinguish from a C-level `sqlite3_vfs*`.) The second argument, the filename, must be a JS or WASM C-string. The 3rd may either be falsy, a valid WASM memory pointer, an ArrayBuffer, or a Uint8Array. The 4th must be the length, in bytes, of the data array to copy. If the 3rd argument is an ArrayBuffer or Uint8Array and the 4th is not a positive integer then the 4th defaults to the array's byteLength value. If data is falsy then a file is created with dataLen bytes filled with uninitialized data (whatever truncate() leaves there). If data is not falsy then a file is created or truncated and it is filled with the first dataLen bytes of the data source. Throws if any arguments are invalid or if creating or writing to the file fails. Note that most VFSes do _not_ automatically create directory parts of filenames, nor do all VFSes have a concept of directories. If the given filename is not valid for the given VFS, an exception will be thrown. This function exists primarily to assist in implementing file-upload capability, with the caveat that clients must have some idea of the VFS into which they want to upload and that VFS must support the operation. VFS-specific notes: - "memdb": results are undefined. - "kvvfs": will fail with an I/O error due to strict internal requirments of that VFS's xTruncate(). - "unix" and related: will use the WASM build's equivalent of the POSIX I/O APIs. This will work so long as neither a specific VFS nor the WASM environment imposes requirements which break it. - "opfs": uses OPFS storage and creates directory parts of the filename. */ capi.sqlite3_js_vfs_create_file = function(vfs, filename, data, dataLen){ let pData; if(data){ if(wasm.isPtr(data)){ pData = data; }else if(data instanceof ArrayBuffer){ data = new Uint8Array(data); } if(data instanceof Uint8Array){ pData = wasm.allocFromTypedArray(data); if(arguments.length<4 || !util.isInt32(dataLen) || dataLen<0){ dataLen = data.byteLength; } }else{ SQLite3Error.toss("Invalid 3rd argument type for sqlite3_js_vfs_create_file()."); } }else{ pData = 0; } if(!util.isInt32(dataLen) || dataLen<0){ wasm.dealloc(pData); SQLite3Error.toss("Invalid 4th argument for sqlite3_js_vfs_create_file()."); } try{ const rc = wasm.sqlite3_wasm_vfs_create_file(vfs, filename, pData, dataLen); if(rc) SQLite3Error.toss("Creation of file failed with sqlite3 result code", capi.sqlite3_js_rc_str(rc)); }finally{ wasm.dealloc(pData); } }; if( util.isUIThread() ){ /* Features specific to the main window thread... */ /** Internal helper for sqlite3_js_kvvfs_clear() and friends. Its argument should be one of ('local','session',""). */ |
︙ | ︙ | |||
1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 | } }); return sz * 2 /* because JS uses 2-byte char encoding */; }; }/* main-window-only bits */ /* The remainder of the API will be set up in later steps. */ const sqlite3 = { WasmAllocError: WasmAllocError, SQLite3Error: SQLite3Error, capi, util, wasm, config, /** Holds the version info of the sqlite3 source tree from which the generated sqlite3-api.js gets built. Note that its version may well differ from that reported by sqlite3_libversion(), but that should be considered a source file mismatch, as the JS and WASM files are intended to be built and distributed together. This object is initially a placeholder which gets replaced by a build-generated object. */ version: Object.create(null), /** Performs any optional asynchronous library-level initialization which might be required. This function returns a Promise which resolves to the sqlite3 namespace object. Any error in the async init will be fatal to the init as a whole, but init routines are themselves welcome to install dummy catch() handlers which are not fatal if their failure should be | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 | } }); return sz * 2 /* because JS uses 2-byte char encoding */; }; }/* main-window-only bits */ /** Wraps all known variants of the C-side variadic sqlite3_db_config(). Full docs: https://sqlite.org/c3ref/db_config.html Returns capi.SQLITE_MISUSE if op is not a valid operation ID. */ capi.sqlite3_db_config = function f(pDb, op, ...args){ if(!this.s){ this.s = wasm.xWrap('sqlite3_wasm_db_config_s','int', ['sqlite3*', 'int', 'string:static'] /* MAINDBNAME requires a static string */); this.pii = wasm.xWrap('sqlite3_wasm_db_config_pii', 'int', ['sqlite3*', 'int', '*','int', 'int']); this.ip = wasm.xWrap('sqlite3_wasm_db_config_ip','int', ['sqlite3*', 'int', 'int','*']); } const c = capi; switch(op){ case c.SQLITE_DBCONFIG_ENABLE_FKEY: case c.SQLITE_DBCONFIG_ENABLE_TRIGGER: case c.SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: case c.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: case c.SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: case c.SQLITE_DBCONFIG_ENABLE_QPSG: case c.SQLITE_DBCONFIG_TRIGGER_EQP: case c.SQLITE_DBCONFIG_RESET_DATABASE: case c.SQLITE_DBCONFIG_DEFENSIVE: case c.SQLITE_DBCONFIG_WRITABLE_SCHEMA: case c.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: case c.SQLITE_DBCONFIG_DQS_DML: case c.SQLITE_DBCONFIG_DQS_DDL: case c.SQLITE_DBCONFIG_ENABLE_VIEW: case c.SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: case c.SQLITE_DBCONFIG_TRUSTED_SCHEMA: return this.ip(pDb, op, args[0], args[1] || 0); case c.SQLITE_DBCONFIG_LOOKASIDE: return this.pii(pDb, op, args[0], args[1], args[2]); case c.SQLITE_DBCONFIG_MAINDBNAME: return this.s(pDb, op, args[0]); default: return c.SQLITE_MISUSE; } }.bind(Object.create(null)); /** Given a (sqlite3_value*), this function attempts to convert it to an equivalent JS value with as much fidelity as feasible and return it. By default it throws if it cannot determine any sensible conversion. If passed a falsy second argument, it instead returns `undefined` if no suitable conversion is found. Note that there is no conversion from SQL to JS which results in the `undefined` value, so `undefined` has an unambiguous meaning here. It will always throw a WasmAllocError if allocating memory for a conversion fails. Caveats: - It does not support sqlite3_value_to_pointer() conversions because those require a type name string which this function does not have and cannot sensibly be given at the level of the API where this is used (e.g. automatically converting UDF arguments). Clients using sqlite3_value_to_pointer(), and its related APIs, will need to manage those themselves. */ capi.sqlite3_value_to_js = function(pVal,throwIfCannotConvert=true){ let arg; const valType = capi.sqlite3_value_type(pVal); switch(valType){ case capi.SQLITE_INTEGER: if(wasm.bigIntEnabled){ arg = capi.sqlite3_value_int64(pVal); if(util.bigIntFitsDouble(arg)) arg = Number(arg); } else arg = capi.sqlite3_value_double(pVal)/*yes, double, for larger integers*/; break; case capi.SQLITE_FLOAT: arg = capi.sqlite3_value_double(pVal); break; case capi.SQLITE_TEXT: arg = capi.sqlite3_value_text(pVal); break; case capi.SQLITE_BLOB:{ const n = capi.sqlite3_value_bytes(pVal); const pBlob = capi.sqlite3_value_blob(pVal); if(n && !pBlob) sqlite3.WasmAllocError.toss( "Cannot allocate memory for blob argument of",n,"byte(s)" ); arg = n ? wasm.heap8u().slice(pBlob, pBlob + Number(n)) : null; break; } case capi.SQLITE_NULL: arg = null; break; default: if(throwIfCannotConvert){ toss3(capi.SQLITE_MISMATCH, "Unhandled sqlite3_value_type():",valType); } arg = undefined; } return arg; }; /** Requires a C-style array of `sqlite3_value*` objects and the number of entries in that array. Returns a JS array containing the results of passing each C array entry to sqlite3_value_to_js(). The 3rd argument to this function is passed on as the 2nd argument to that one. */ capi.sqlite3_values_to_js = function(argc,pArgv,throwIfCannotConvert=true){ let i; const tgt = []; for(i = 0; i < argc; ++i){ /** Curiously: despite ostensibly requiring 8-byte alignment, the pArgv array is parcelled into chunks of 4 bytes (1 pointer each). The values those point to have 8-byte alignment but the individual argv entries do not. */ tgt.push(capi.sqlite3_value_to_js( wasm.peekPtr(pArgv + (wasm.ptrSizeof * i)) )); } return tgt; }; /** Calls either sqlite3_result_error_nomem(), if e is-a WasmAllocError, or sqlite3_result_error(). In the latter case, the second arugment is coerced to a string to create the error message. The first argument is a (sqlite3_context*). Returns void. Does not throw. */ capi.sqlite3_result_error_js = function(pCtx,e){ if(e instanceof WasmAllocError){ capi.sqlite3_result_error_nomem(pCtx); }else{ /* Maintenance reminder: ''+e, rather than e.message, will prefix e.message with e.name, so it includes the exception's type name in the result. */; capi.sqlite3_result_error(pCtx, ''+e, -1); } }; /** This function passes its 2nd argument to one of the sqlite3_result_xyz() routines, depending on the type of that argument: - If (val instanceof Error), this function passes it to sqlite3_result_error_js(). - `null`: `sqlite3_result_null()` - `boolean`: `sqlite3_result_int()` with a value of 0 or 1. - `number`: `sqlite3_result_int()`, `sqlite3_result_int64()`, or `sqlite3_result_double()`, depending on the range of the number and whether or not int64 support is enabled. - `bigint`: similar to `number` but will trigger an error if the value is too big to store in an int64. - `string`: `sqlite3_result_text()` - Uint8Array or Int8Array or ArrayBuffer: `sqlite3_result_blob()` - `undefined`: is a no-op provided to simplify certain use cases. Anything else triggers `sqlite3_result_error()` with a description of the problem. The first argument to this function is a `(sqlite3_context*)`. Returns void. Does not throw. */ capi.sqlite3_result_js = function(pCtx,val){ if(val instanceof Error){ capi.sqlite3_result_error_js(pCtx, val); return; } try{ switch(typeof val) { case 'undefined': /* This is a no-op. This routine originated in the create_function() family of APIs and in that context, passing in undefined indicated that the caller was responsible for calling sqlite3_result_xxx() (if needed). */ break; case 'boolean': capi.sqlite3_result_int(pCtx, val ? 1 : 0); break; case 'bigint': if(util.bigIntFits32(val)){ capi.sqlite3_result_int(pCtx, Number(val)); }else if(util.bigIntFitsDouble(val)){ capi.sqlite3_result_double(pCtx, Number(val)); }else if(wasm.bigIntEnabled){ if(util.bigIntFits64(val)) capi.sqlite3_result_int64(pCtx, val); else toss3("BigInt value",val.toString(),"is too BigInt for int64."); }else{ toss3("BigInt value",val.toString(),"is too BigInt."); } break; case 'number': { let f; if(util.isInt32(val)){ f = capi.sqlite3_result_int; }else if(wasm.bigIntEnabled && Number.isInteger(val) && util.bigIntFits64(BigInt(val))){ f = capi.sqlite3_result_int64; }else{ f = capi.sqlite3_result_double; } f(pCtx, val); break; } case 'string': { const [p, n] = wasm.allocCString(val,true); capi.sqlite3_result_text(pCtx, p, n, capi.SQLITE_WASM_DEALLOC); break; } case 'object': if(null===val/*yes, typeof null === 'object'*/) { capi.sqlite3_result_null(pCtx); break; }else if(util.isBindableTypedArray(val)){ const pBlob = wasm.allocFromTypedArray(val); capi.sqlite3_result_blob( pCtx, pBlob, val.byteLength, capi.SQLITE_WASM_DEALLOC ); break; } // else fall through default: toss3("Don't not how to handle this UDF result value:",(typeof val), val); } }catch(e){ capi.sqlite3_result_error_js(pCtx, e); } }; /** Returns the result sqlite3_column_value(pStmt,iCol) passed to sqlite3_value_to_js(). The 3rd argument of this function is ignored by this function except to pass it on as the second argument of sqlite3_value_to_js(). If the sqlite3_column_value() returns NULL (e.g. because the column index is out of range), this function returns `undefined`, regardless of the 3rd argument. If the 3rd argument is falsy and conversion fails, `undefined` will be returned. Note that sqlite3_column_value() returns an "unprotected" value object, but in a single-threaded environment (like this one) there is no distinction between protected and unprotected values. */ capi.sqlite3_column_js = function(pStmt, iCol, throwIfCannotConvert=true){ const v = capi.sqlite3_column_value(pStmt, iCol); return (0===v) ? undefined : capi.sqlite3_value_to_js(v, throwIfCannotConvert); }; /* The remainder of the API will be set up in later steps. */ const sqlite3 = { WasmAllocError: WasmAllocError, SQLite3Error: SQLite3Error, capi, util, wasm, config, /** Holds the version info of the sqlite3 source tree from which the generated sqlite3-api.js gets built. Note that its version may well differ from that reported by sqlite3_libversion(), but that should be considered a source file mismatch, as the JS and WASM files are intended to be built and distributed together. This object is initially a placeholder which gets replaced by a build-generated object. */ version: Object.create(null), /** The library reserves the 'client' property for client-side use and promises to never define a property with this name nor to ever rely on specific contents of it. It makes no such guarantees for other properties. */ client: undefined, /** Performs any optional asynchronous library-level initialization which might be required. This function returns a Promise which resolves to the sqlite3 namespace object. Any error in the async init will be fatal to the init as a whole, but init routines are themselves welcome to install dummy catch() handlers which are not fatal if their failure should be |
︙ | ︙ | |||
1492 1493 1494 1495 1496 1497 1498 | In Emscripten-based builds, this function is called automatically and deleted from this object. */ asyncPostInit: async function(){ let lip = sqlite3ApiBootstrap.initializersAsync; delete sqlite3ApiBootstrap.initializersAsync; if(!lip || !lip.length) return Promise.resolve(sqlite3); | < < < > > > > > > > > > > > > > > > > > > > | | | > > | > | 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 | In Emscripten-based builds, this function is called automatically and deleted from this object. */ asyncPostInit: async function(){ let lip = sqlite3ApiBootstrap.initializersAsync; delete sqlite3ApiBootstrap.initializersAsync; if(!lip || !lip.length) return Promise.resolve(sqlite3); lip = lip.map((f)=>{ const p = (f instanceof Promise) ? f : f(sqlite3); return p.catch((e)=>{ console.error("an async sqlite3 initializer failed:",e); throw e; }); }); const postInit = ()=>{ if(!sqlite3.__isUnderTest){ /* Delete references to internal-only APIs which are used by some initializers. Retain them when running in test mode so that we can add tests for them. */ delete sqlite3.util; /* It's conceivable that we might want to expose StructBinder to client-side code, but it's only useful if clients build their own sqlite3.wasm which contains their one C struct types. */ delete sqlite3.StructBinder; } return sqlite3; }; if(1){ /* Run all initializers in sequence. The advantage is that it allows us to have post-init cleanup defined outside of this routine at the end of the list and have it run at a well-defined time. */ let p = lip.shift(); while(lip.length) p = p.then(lip.shift()); return p.then(postInit); }else{ /* Run them in an arbitrary order. */ return Promise.all(lip).then(postInit); } }, /** scriptInfo ideally gets injected into this object by the infrastructure which assembles the JS/WASM module. It contains state which must be collected before sqlite3ApiBootstrap() can be declared. It is not necessarily available to any sqlite3ApiBootstrap.initializers but "should" be in place (if |
︙ | ︙ |
Changes to ext/wasm/api/sqlite3-api-worker1.js.
︙ | ︙ | |||
154 155 156 157 158 159 160 | messageId: ...as above..., result: { version: sqlite3.version object bigIntEnabled: bool. True if BigInt support is enabled. | < < < < < < < < < | 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | messageId: ...as above..., result: { version: sqlite3.version object bigIntEnabled: bool. True if BigInt support is enabled. vfsList: result of sqlite3.capi.sqlite3_js_vfs_list() } } ``` ==================================================================== |
︙ | ︙ | |||
445 446 447 448 449 450 451 | const wMsgHandler = { open: function(ev){ const oargs = Object.create(null), args = (ev.args || Object.create(null)); if(args.simulateError){ // undocumented internal testing option toss("Throwing because of simulateError flag."); } const rc = Object.create(null); | < | 436 437 438 439 440 441 442 443 444 445 446 447 448 449 | const wMsgHandler = { open: function(ev){ const oargs = Object.create(null), args = (ev.args || Object.create(null)); if(args.simulateError){ // undocumented internal testing option toss("Throwing because of simulateError flag."); } const rc = Object.create(null); let byteArray, pVfs; oargs.vfs = args.vfs; if(isSpecialDbFilename(args.filename)){ oargs.filename = args.filename || ""; }else{ oargs.filename = args.filename; byteArray = args.byteArray; |
︙ | ︙ | |||
471 472 473 474 475 476 477 | ); if(rc) sqlite3.SQLite3Error.toss(rc); }catch(e){ throw new sqlite3.SQLite3Error( e.name+' creating '+args.filename+": "+e.message, { cause: e } | | < | | 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 | ); if(rc) sqlite3.SQLite3Error.toss(rc); }catch(e){ throw new sqlite3.SQLite3Error( e.name+' creating '+args.filename+": "+e.message, { cause: e } ); }finally{ if(pMem) sqlite3.wasm.dealloc(pMem); } } const db = wState.open(oargs); rc.filename = db.filename; rc.persistent = !!sqlite3.capi.sqlite3_js_db_uses_vfs(db.pointer, "opfs"); rc.dbId = getDbId(db); rc.vfs = db.dbVfsName(); return rc; }, close: function(ev){ const db = getMsgDb(ev,false); |
︙ | ︙ | |||
554 555 556 557 558 559 560 | } return rc; }/*exec()*/, 'config-get': function(){ const rc = Object.create(null), src = sqlite3.config; [ | | < | 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 | } return rc; }/*exec()*/, 'config-get': function(){ const rc = Object.create(null), src = sqlite3.config; [ 'bigIntEnabled' ].forEach(function(k){ if(Object.getOwnPropertyDescriptor(src, k)) rc[k] = src[k]; }); rc.version = sqlite3.version; rc.vfsList = sqlite3.capi.sqlite3_js_vfs_list(); rc.opfsEnabled = !!sqlite3.opfs; return rc; }, /** |
︙ | ︙ |
Changes to ext/wasm/api/sqlite3-opfs-async-proxy.js.
︙ | ︙ | |||
9 10 11 12 13 14 15 | * May you share freely, never taking more than you give. *********************************************************************** A Worker which manages asynchronous OPFS handles on behalf of a synchronous API which controls it via a combination of Worker messages, SharedArrayBuffer, and Atomics. It is the asynchronous | | > > > > > > > > > > > > > > > > > > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | | | | | | < < < < < < < < < < | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | < | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | < | | < | | | | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | > | | < | | | | | | | | | | | | | | < < < < < < | | | | | | | | | | | | > > > > > > > > > > > > > > > > > | | | | | | | | | | | | | | < | | | | | | | | | | | | | | | | > | | | | | | | | | | | | | | | | | | | | | | | | | | < | | | | | | | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | < | | | | | | | | | | | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | < < < < < < | | | | | < | < < < < < < | | | | | | | | | | | | | | | | | | | < | | | | | | | < | < < < < | < | | < < | | | | | | | | | | | | | | | | | | | | | | | | | | > > > > > > > > > > > > > > > > | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 | * May you share freely, never taking more than you give. *********************************************************************** A Worker which manages asynchronous OPFS handles on behalf of a synchronous API which controls it via a combination of Worker messages, SharedArrayBuffer, and Atomics. It is the asynchronous counterpart of the API defined in sqlite3-vfs-opfs.js. Highly indebted to: https://github.com/rhashimoto/wa-sqlite/blob/master/src/examples/OriginPrivateFileSystemVFS.js for demonstrating how to use the OPFS APIs. This file is to be loaded as a Worker. It does not have any direct access to the sqlite3 JS/WASM bits, so any bits which it needs (most notably SQLITE_xxx integer codes) have to be imported into it via an initialization process. This file represents an implementation detail of a larger piece of code, and not a public interface. Its details may change at any time and are not intended to be used by any client-level code. 2022-11-27: Chrome v108 changes some async methods to synchronous, as documented at: https://developer.chrome.com/blog/sync-methods-for-accesshandles/ We cannot change to the sync forms at this point without breaking clients who use Chrome v104-ish or higher. truncate(), getSize(), flush(), and close() are now (as of v108) synchronous. Calling them with an "await", as we have to for the async forms, is still legal with the sync forms but is superfluous. Calling the async forms with theFunc().then(...) is not compatible with the change to synchronous, but we do do not use those APIs that way. i.e. we don't _need_ to change anything for this, but at some point (after Chrome versions (approximately) 104-107 are extinct) should change our usage of those methods to remove the "await". */ "use strict"; const wPost = (type,...args)=>postMessage({type, payload:args}); const installAsyncProxy = function(self){ const toss = function(...args){throw new Error(args.join(' '))}; if(self.window === self){ toss("This code cannot run from the main thread.", "Load it as a Worker from a separate Worker."); }else if(!navigator.storage.getDirectory){ toss("This API requires navigator.storage.getDirectory."); } /** Will hold state copied to this object from the syncronous side of this API. */ const state = Object.create(null); /** verbose: 0 = no logging output 1 = only errors 2 = warnings and errors 3 = debug, warnings, and errors */ state.verbose = 1; const loggers = { 0:console.error.bind(console), 1:console.warn.bind(console), 2:console.log.bind(console) }; const logImpl = (level,...args)=>{ if(state.verbose>level) loggers[level]("OPFS asyncer:",...args); }; const log = (...args)=>logImpl(2, ...args); const warn = (...args)=>logImpl(1, ...args); const error = (...args)=>logImpl(0, ...args); const metrics = Object.create(null); metrics.reset = ()=>{ let k; const r = (m)=>(m.count = m.time = m.wait = 0); for(k in state.opIds){ r(metrics[k] = Object.create(null)); } let s = metrics.s11n = Object.create(null); s = s.serialize = Object.create(null); s.count = s.time = 0; s = metrics.s11n.deserialize = Object.create(null); s.count = s.time = 0; }; metrics.dump = ()=>{ let k, n = 0, t = 0, w = 0; for(k in state.opIds){ const m = metrics[k]; n += m.count; t += m.time; w += m.wait; m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0; } console.log(self.location.href, "metrics for",self.location.href,":\n", metrics, "\nTotal of",n,"op(s) for",t,"ms", "approx",w,"ms spent waiting on OPFS APIs."); console.log("Serialization metrics:",metrics.s11n); }; /** __openFiles is a map of sqlite3_file pointers (integers) to metadata related to a given OPFS file handles. The pointers are, in this side of the interface, opaque file handle IDs provided by the synchronous part of this constellation. Each value is an object with a structure demonstrated in the xOpen() impl. */ const __openFiles = Object.create(null); /** __implicitLocks is a Set of sqlite3_file pointers (integers) which were "auto-locked". i.e. those for which we obtained a sync access handle without an explicit xLock() call. Such locks will be released during db connection idle time, whereas a sync access handle obtained via xLock(), or subsequently xLock()'d after auto-acquisition, will not be released until xUnlock() is called. Maintenance reminder: if we relinquish auto-locks at the end of the operation which acquires them, we pay a massive performance penalty: speedtest1 benchmarks take up to 4x as long. By delaying the lock release until idle time, the hit is negligible. */ const __implicitLocks = new Set(); /** Expects an OPFS file path. It gets resolved, such that ".." components are properly expanded, and returned. If the 2nd arg is true, the result is returned as an array of path elements, else an absolute path string is returned. */ const getResolvedPath = function(filename,splitIt){ const p = new URL( filename, 'file://irrelevant' ).pathname; return splitIt ? p.split('/').filter((v)=>!!v) : p; }; /** Takes the absolute path to a filesystem element. Returns an array of [handleOfContainingDir, filename]. If the 2nd argument is truthy then each directory element leading to the file is created along the way. Throws if any creation or resolution fails. */ const getDirForFilename = async function f(absFilename, createDirs = false){ const path = getResolvedPath(absFilename, true); const filename = path.pop(); let dh = state.rootDir; for(const dirName of path){ if(dirName){ dh = await dh.getDirectoryHandle(dirName, {create: !!createDirs}); } } return [dh, filename]; }; /** If the given file-holding object has a sync handle attached to it, that handle is remove and asynchronously closed. Though it may sound sensible to continue work as soon as the close() returns (noting that it's asynchronous), doing so can cause operations performed soon afterwards, e.g. a call to getSyncHandle() to fail because they may happen out of order from the close(). OPFS does not guaranty that the actual order of operations is retained in such cases. i.e. always "await" on the result of this function. */ const closeSyncHandle = async (fh)=>{ if(fh.syncHandle){ log("Closing sync handle for",fh.filenameAbs); const h = fh.syncHandle; delete fh.syncHandle; delete fh.xLock; __implicitLocks.delete(fh.fid); return h.close(); } }; /** A proxy for closeSyncHandle() which is guaranteed to not throw. This function is part of a lock/unlock step in functions which require a sync access handle but may be called without xLock() having been called first. Such calls need to release that handle to avoid locking the file for all of time. This is an _attempt_ at reducing cross-tab contention but it may prove to be more of a problem than a solution and may need to be removed. */ const closeSyncHandleNoThrow = async (fh)=>{ try{await closeSyncHandle(fh)} catch(e){ warn("closeSyncHandleNoThrow() ignoring:",e,fh); } }; /* Release all auto-locks. */ const releaseImplicitLocks = async ()=>{ if(__implicitLocks.size){ /* Release all auto-locks. */ for(const fid of __implicitLocks){ const fh = __openFiles[fid]; await closeSyncHandleNoThrow(fh); log("Auto-unlocked",fid,fh.filenameAbs); } } }; /** An experiment in improving concurrency by freeing up implicit locks sooner. This is known to impact performance dramatically but it has also shown to improve concurrency considerably. If fh.releaseImplicitLocks is truthy and fh is in __implicitLocks, this routine returns closeSyncHandleNoThrow(), else it is a no-op. */ const releaseImplicitLock = async (fh)=>{ if(fh.releaseImplicitLocks && __implicitLocks.has(fh.fid)){ return closeSyncHandleNoThrow(fh); } }; /** An error class specifically for use with getSyncHandle(), the goal of which is to eventually be able to distinguish unambiguously between locking-related failures and other types, noting that we cannot currently do so because createSyncAccessHandle() does not define its exceptions in the required level of detail. 2022-11-29: according to: https://github.com/whatwg/fs/pull/21 NoModificationAllowedError will be the standard exception thrown when acquisition of a sync access handle fails due to a locking error. As of this writing, that error type is not visible in the dev console in Chrome v109, nor is it documented in MDN, but an error with that "name" property is being thrown from the OPFS layer. */ class GetSyncHandleError extends Error { constructor(errorObject, ...msg){ super([ ...msg, ': '+errorObject.name+':', errorObject.message ].join(' '), { cause: errorObject }); this.name = 'GetSyncHandleError'; } }; GetSyncHandleError.convertRc = (e,rc)=>{ if(1){ return ( e instanceof GetSyncHandleError && ((e.cause.name==='NoModificationAllowedError') /* Inconsistent exception.name from Chrome/ium with the same exception.message text: */ || (e.cause.name==='DOMException' && 0===e.cause.message.indexOf('Access Handles cannot'))) ) ? ( /*console.warn("SQLITE_BUSY",e),*/ state.sq3Codes.SQLITE_BUSY ) : rc; }else{ return rc; } } /** Returns the sync access handle associated with the given file handle object (which must be a valid handle object, as created by xOpen()), lazily opening it if needed. In order to help alleviate cross-tab contention for a dabase, if an exception is thrown while acquiring the handle, this routine will wait briefly and try again, up to some fixed number of times. If acquisition still fails at that point it will give up and propagate the exception. Client-level code will see that as an I/O error. */ const getSyncHandle = async (fh,opName)=>{ if(!fh.syncHandle){ const t = performance.now(); log("Acquiring sync handle for",fh.filenameAbs); const maxTries = 6, msBase = state.asyncIdleWaitTime * 2; let i = 1, ms = msBase; for(; true; ms = msBase * ++i){ try { //if(i<3) toss("Just testing getSyncHandle() wait-and-retry."); //TODO? A config option which tells it to throw here //randomly every now and then, for testing purposes. fh.syncHandle = await fh.fileHandle.createSyncAccessHandle(); break; }catch(e){ if(i === maxTries){ throw new GetSyncHandleError( e, "Error getting sync handle for",opName+"().",maxTries, "attempts failed.",fh.filenameAbs ); } warn("Error getting sync handle for",opName+"(). Waiting",ms, "ms and trying again.",fh.filenameAbs,e); Atomics.wait(state.sabOPView, state.opIds.retry, 0, ms); } } log("Got",opName+"() sync handle for",fh.filenameAbs, 'in',performance.now() - t,'ms'); if(!fh.xLock){ __implicitLocks.add(fh.fid); log("Acquired implicit lock for",opName+"()",fh.fid,fh.filenameAbs); } } return fh.syncHandle; }; /** Stores the given value at state.sabOPView[state.opIds.rc] and then Atomics.notify()'s it. */ const storeAndNotify = (opName, value)=>{ log(opName+"() => notify(",value,")"); Atomics.store(state.sabOPView, state.opIds.rc, value); Atomics.notify(state.sabOPView, state.opIds.rc); }; /** Throws if fh is a file-holding object which is flagged as read-only. */ const affirmNotRO = function(opName,fh){ if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs); }; /** We track 2 different timers: the "metrics" timer records how much time we spend performing work. The "wait" timer records how much time we spend waiting on the underlying OPFS timer. See the calls to mTimeStart(), mTimeEnd(), wTimeStart(), and wTimeEnd() throughout this file to see how they're used. */ const __mTimer = Object.create(null); __mTimer.op = undefined; __mTimer.start = undefined; const mTimeStart = (op)=>{ __mTimer.start = performance.now(); __mTimer.op = op; //metrics[op] || toss("Maintenance required: missing metrics for",op); ++metrics[op].count; }; const mTimeEnd = ()=>( metrics[__mTimer.op].time += performance.now() - __mTimer.start ); const __wTimer = Object.create(null); __wTimer.op = undefined; __wTimer.start = undefined; const wTimeStart = (op)=>{ __wTimer.start = performance.now(); __wTimer.op = op; //metrics[op] || toss("Maintenance required: missing metrics for",op); }; const wTimeEnd = ()=>( metrics[__wTimer.op].wait += performance.now() - __wTimer.start ); /** Gets set to true by the 'opfs-async-shutdown' command to quit the wait loop. This is only intended for debugging purposes: we cannot inspect this file's state while the tight waitLoop() is running and need a way to stop that loop for introspection purposes. */ let flagAsyncShutdown = false; /** Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods methods, as well as helpers like mkdir(). Maintenance reminder: members are in alphabetical order to simplify finding them. */ const vfsAsyncImpls = { 'opfs-async-metrics': async ()=>{ mTimeStart('opfs-async-metrics'); metrics.dump(); storeAndNotify('opfs-async-metrics', 0); mTimeEnd(); }, 'opfs-async-shutdown': async ()=>{ flagAsyncShutdown = true; storeAndNotify('opfs-async-shutdown', 0); }, mkdir: async (dirname)=>{ mTimeStart('mkdir'); let rc = 0; wTimeStart('mkdir'); try { await getDirForFilename(dirname+"/filepart", true); }catch(e){ state.s11n.storeException(2,e); rc = state.sq3Codes.SQLITE_IOERR; }finally{ wTimeEnd(); } storeAndNotify('mkdir', rc); mTimeEnd(); }, xAccess: async (filename)=>{ mTimeStart('xAccess'); /* OPFS cannot support the full range of xAccess() queries sqlite3 calls for. We can essentially just tell if the file is accessible, but if it is then it's automatically writable (unless it's locked, which we cannot(?) know without trying to open it). OPFS does not have the notion of read-only. The return semantics of this function differ from sqlite3's xAccess semantics because we are limited in what we can communicate back to our synchronous communication partner: 0 = accessible, non-0 means not accessible. */ let rc = 0; wTimeStart('xAccess'); try{ const [dh, fn] = await getDirForFilename(filename); await dh.getFileHandle(fn); }catch(e){ state.s11n.storeException(2,e); rc = state.sq3Codes.SQLITE_IOERR; }finally{ wTimeEnd(); } storeAndNotify('xAccess', rc); mTimeEnd(); }, xClose: async function(fid/*sqlite3_file pointer*/){ const opName = 'xClose'; mTimeStart(opName); __implicitLocks.delete(fid); const fh = __openFiles[fid]; let rc = 0; wTimeStart(opName); if(fh){ delete __openFiles[fid]; await closeSyncHandle(fh); if(fh.deleteOnClose){ try{ await fh.dirHandle.removeEntry(fh.filenamePart) } catch(e){ warn("Ignoring dirHandle.removeEntry() failure of",fh,e) } } }else{ state.s11n.serialize(); rc = state.sq3Codes.SQLITE_NOTFOUND; } wTimeEnd(); storeAndNotify(opName, rc); mTimeEnd(); }, xDelete: async function(...args){ mTimeStart('xDelete'); const rc = await vfsAsyncImpls.xDeleteNoWait(...args); storeAndNotify('xDelete', rc); mTimeEnd(); }, xDeleteNoWait: async function(filename, syncDir = 0, recursive = false){ /* The syncDir flag is, for purposes of the VFS API's semantics, ignored here. However, if it has the value 0x1234 then: after deleting the given file, recursively try to delete any empty directories left behind in its wake (ignoring any errors and stopping at the first failure). That said: we don't know for sure that removeEntry() fails if the dir is not empty because the API is not documented. It has, however, a "recursive" flag which defaults to false, so presumably it will fail if the dir is not empty and that flag is false. */ let rc = 0; wTimeStart('xDelete'); try { while(filename){ const [hDir, filenamePart] = await getDirForFilename(filename, false); if(!filenamePart) break; await hDir.removeEntry(filenamePart, {recursive}); if(0x1234 !== syncDir) break; recursive = false; filename = getResolvedPath(filename, true); filename.pop(); filename = filename.join('/'); } }catch(e){ state.s11n.storeException(2,e); rc = state.sq3Codes.SQLITE_IOERR_DELETE; } wTimeEnd(); return rc; }, xFileSize: async function(fid/*sqlite3_file pointer*/){ mTimeStart('xFileSize'); const fh = __openFiles[fid]; let rc = 0; wTimeStart('xFileSize'); try{ const sz = await (await getSyncHandle(fh,'xFileSize')).getSize(); state.s11n.serialize(Number(sz)); }catch(e){ state.s11n.storeException(1,e); rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR); } await releaseImplicitLock(fh); wTimeEnd(); storeAndNotify('xFileSize', rc); mTimeEnd(); }, xLock: async function(fid/*sqlite3_file pointer*/, lockType/*SQLITE_LOCK_...*/){ mTimeStart('xLock'); const fh = __openFiles[fid]; let rc = 0; const oldLockType = fh.xLock; fh.xLock = lockType; if( !fh.syncHandle ){ wTimeStart('xLock'); try { await getSyncHandle(fh,'xLock'); __implicitLocks.delete(fid); }catch(e){ state.s11n.storeException(1,e); rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_LOCK); fh.xLock = oldLockType; } wTimeEnd(); } storeAndNotify('xLock',rc); mTimeEnd(); }, xOpen: async function(fid/*sqlite3_file pointer*/, filename, flags/*SQLITE_OPEN_...*/, opfsFlags/*OPFS_...*/){ const opName = 'xOpen'; mTimeStart(opName); const create = (state.sq3Codes.SQLITE_OPEN_CREATE & flags); wTimeStart('xOpen'); try{ let hDir, filenamePart; try { [hDir, filenamePart] = await getDirForFilename(filename, !!create); }catch(e){ state.s11n.storeException(1,e); storeAndNotify(opName, state.sq3Codes.SQLITE_NOTFOUND); mTimeEnd(); wTimeEnd(); return; } const hFile = await hDir.getFileHandle(filenamePart, {create}); wTimeEnd(); const fh = Object.assign(Object.create(null),{ fid: fid, filenameAbs: filename, filenamePart: filenamePart, dirHandle: hDir, fileHandle: hFile, sabView: state.sabFileBufView, readOnly: create ? false : (state.sq3Codes.SQLITE_OPEN_READONLY & flags), deleteOnClose: !!(state.sq3Codes.SQLITE_OPEN_DELETEONCLOSE & flags) }); fh.releaseImplicitLocks = (opfsFlags & state.opfsFlags.OPFS_UNLOCK_ASAP) || state.opfsFlags.defaultUnlockAsap; if(0 /* this block is modelled after something wa-sqlite does but it leads to immediate contention on journal files. Update: this approach reportedly only works for DELETE journal mode. */ && (0===(flags & state.sq3Codes.SQLITE_OPEN_MAIN_DB))){ /* sqlite does not lock these files, so go ahead and grab an OPFS lock. */ fh.xLock = "xOpen"/* Truthy value to keep entry from getting flagged as auto-locked. String value so that we can easily distinguish is later if needed. */; await getSyncHandle(fh,'xOpen'); } __openFiles[fid] = fh; storeAndNotify(opName, 0); }catch(e){ wTimeEnd(); error(opName,e); state.s11n.storeException(1,e); storeAndNotify(opName, state.sq3Codes.SQLITE_IOERR); } mTimeEnd(); }, xRead: async function(fid/*sqlite3_file pointer*/,n,offset64){ mTimeStart('xRead'); let rc = 0, nRead; const fh = __openFiles[fid]; try{ wTimeStart('xRead'); nRead = (await getSyncHandle(fh,'xRead')).read( fh.sabView.subarray(0, n), {at: Number(offset64)} ); wTimeEnd(); if(nRead < n){/* Zero-fill remaining bytes */ fh.sabView.fill(0, nRead, n); rc = state.sq3Codes.SQLITE_IOERR_SHORT_READ; } }catch(e){ if(undefined===nRead) wTimeEnd(); error("xRead() failed",e,fh); state.s11n.storeException(1,e); rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_READ); } await releaseImplicitLock(fh); storeAndNotify('xRead',rc); mTimeEnd(); }, xSync: async function(fid/*sqlite3_file pointer*/,flags/*ignored*/){ mTimeStart('xSync'); const fh = __openFiles[fid]; let rc = 0; if(!fh.readOnly && fh.syncHandle){ try { wTimeStart('xSync'); await fh.syncHandle.flush(); }catch(e){ state.s11n.storeException(2,e); rc = state.sq3Codes.SQLITE_IOERR_FSYNC; } wTimeEnd(); } storeAndNotify('xSync',rc); mTimeEnd(); }, xTruncate: async function(fid/*sqlite3_file pointer*/,size){ mTimeStart('xTruncate'); let rc = 0; const fh = __openFiles[fid]; wTimeStart('xTruncate'); try{ affirmNotRO('xTruncate', fh); await (await getSyncHandle(fh,'xTruncate')).truncate(size); }catch(e){ error("xTruncate():",e,fh); state.s11n.storeException(2,e); rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_TRUNCATE); } await releaseImplicitLock(fh); wTimeEnd(); storeAndNotify('xTruncate',rc); mTimeEnd(); }, xUnlock: async function(fid/*sqlite3_file pointer*/, lockType/*SQLITE_LOCK_...*/){ mTimeStart('xUnlock'); let rc = 0; const fh = __openFiles[fid]; if( state.sq3Codes.SQLITE_LOCK_NONE===lockType && fh.syncHandle ){ wTimeStart('xUnlock'); try { await closeSyncHandle(fh) } catch(e){ state.s11n.storeException(1,e); rc = state.sq3Codes.SQLITE_IOERR_UNLOCK; } wTimeEnd(); } storeAndNotify('xUnlock',rc); mTimeEnd(); }, xWrite: async function(fid/*sqlite3_file pointer*/,n,offset64){ mTimeStart('xWrite'); let rc; const fh = __openFiles[fid]; wTimeStart('xWrite'); try{ affirmNotRO('xWrite', fh); rc = ( n === (await getSyncHandle(fh,'xWrite')) .write(fh.sabView.subarray(0, n), {at: Number(offset64)}) ) ? 0 : state.sq3Codes.SQLITE_IOERR_WRITE; }catch(e){ error("xWrite():",e,fh); state.s11n.storeException(1,e); rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_WRITE); } await releaseImplicitLock(fh); wTimeEnd(); storeAndNotify('xWrite',rc); mTimeEnd(); } }/*vfsAsyncImpls*/; const initS11n = ()=>{ /** ACHTUNG: this code is 100% duplicated in the other half of this proxy! The documentation is maintained in the "synchronous half". */ if(state.s11n) return state.s11n; const textDecoder = new TextDecoder(), textEncoder = new TextEncoder('utf-8'), viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize), viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize); state.s11n = Object.create(null); const TypeIds = Object.create(null); TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' }; TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' }; TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' }; TypeIds.string = { id: 4 }; const getTypeId = (v)=>( TypeIds[typeof v] || toss("Maintenance required: this value type cannot be serialized.",v) ); const getTypeIdById = (tid)=>{ switch(tid){ case TypeIds.number.id: return TypeIds.number; case TypeIds.bigint.id: return TypeIds.bigint; case TypeIds.boolean.id: return TypeIds.boolean; case TypeIds.string.id: return TypeIds.string; default: toss("Invalid type ID:",tid); } }; state.s11n.deserialize = function(clear=false){ ++metrics.s11n.deserialize.count; const t = performance.now(); const argc = viewU8[0]; const rc = argc ? [] : null; if(argc){ const typeIds = []; let offset = 1, i, n, v; for(i = 0; i < argc; ++i, ++offset){ typeIds.push(getTypeIdById(viewU8[offset])); } for(i = 0; i < argc; ++i){ const t = typeIds[i]; if(t.getter){ v = viewDV[t.getter](offset, state.littleEndian); offset += t.size; }else{/*String*/ n = viewDV.getInt32(offset, state.littleEndian); offset += 4; v = textDecoder.decode(viewU8.slice(offset, offset+n)); offset += n; } rc.push(v); } } if(clear) viewU8[0] = 0; //log("deserialize:",argc, rc); metrics.s11n.deserialize.time += performance.now() - t; return rc; }; state.s11n.serialize = function(...args){ const t = performance.now(); ++metrics.s11n.serialize.count; if(args.length){ //log("serialize():",args); const typeIds = []; let i = 0, offset = 1; viewU8[0] = args.length & 0xff /* header = # of args */; for(; i < args.length; ++i, ++offset){ /* Write the TypeIds.id value into the next args.length bytes. */ typeIds.push(getTypeId(args[i])); viewU8[offset] = typeIds[i].id; } for(i = 0; i < args.length; ++i) { /* Deserialize the following bytes based on their corresponding TypeIds.id from the header. */ const t = typeIds[i]; if(t.setter){ viewDV[t.setter](offset, args[i], state.littleEndian); offset += t.size; }else{/*String*/ const s = textEncoder.encode(args[i]); viewDV.setInt32(offset, s.byteLength, state.littleEndian); offset += 4; viewU8.set(s, offset); offset += s.byteLength; } } //log("serialize() result:",viewU8.slice(0,offset)); }else{ viewU8[0] = 0; } metrics.s11n.serialize.time += performance.now() - t; }; state.s11n.storeException = state.asyncS11nExceptions ? ((priority,e)=>{ if(priority<=state.asyncS11nExceptions){ state.s11n.serialize([e.name,': ',e.message].join("")); } }) : ()=>{}; return state.s11n; }/*initS11n()*/; const waitLoop = async function f(){ const opHandlers = Object.create(null); for(let k of Object.keys(state.opIds)){ const vi = vfsAsyncImpls[k]; if(!vi) continue; const o = Object.create(null); opHandlers[state.opIds[k]] = o; o.key = k; o.f = vi; } while(!flagAsyncShutdown){ try { if('timed-out'===Atomics.wait( state.sabOPView, state.opIds.whichOp, 0, state.asyncIdleWaitTime )){ await releaseImplicitLocks(); continue; } const opId = Atomics.load(state.sabOPView, state.opIds.whichOp); Atomics.store(state.sabOPView, state.opIds.whichOp, 0); const hnd = opHandlers[opId] ?? toss("No waitLoop handler for whichOp #",opId); const args = state.s11n.deserialize( true /* clear s11n to keep the caller from confusing this with an exception string written by the upcoming operation */ ) || []; //warn("waitLoop() whichOp =",opId, hnd, args); if(hnd.f) await hnd.f(...args); else error("Missing callback for opId",opId); }catch(e){ error('in waitLoop():',e); } } }; navigator.storage.getDirectory().then(function(d){ state.rootDir = d; self.onmessage = function({data}){ switch(data.type){ case 'opfs-async-init':{ /* Receive shared state from synchronous partner */ const opt = data.args; for(const k in opt) state[k] = opt[k]; state.verbose = opt.verbose ?? 1; state.sabOPView = new Int32Array(state.sabOP); state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize); state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize); Object.keys(vfsAsyncImpls).forEach((k)=>{ if(!Number.isFinite(state.opIds[k])){ toss("Maintenance required: missing state.opIds[",k,"]"); } }); initS11n(); metrics.reset(); log("init state",state); wPost('opfs-async-inited'); waitLoop(); break; } case 'opfs-async-restart': if(flagAsyncShutdown){ warn("Restarting after opfs-async-shutdown. Might or might not work."); flagAsyncShutdown = false; waitLoop(); } break; case 'opfs-async-metrics': metrics.dump(); break; } }; wPost('opfs-async-loaded'); }).catch((e)=>error("error initializing OPFS asyncer:",e)); }/*installAsyncProxy()*/; if(!self.SharedArrayBuffer){ wPost('opfs-unavailable', "Missing SharedArrayBuffer API.", "The server must emit the COOP/COEP response headers to enable that."); }else if(!self.Atomics){ wPost('opfs-unavailable', "Missing Atomics API.", "The server must emit the COOP/COEP response headers to enable that."); }else if(!self.FileSystemHandle || !self.FileSystemDirectoryHandle || !self.FileSystemFileHandle || !self.FileSystemFileHandle.prototype.createSyncAccessHandle || !navigator.storage.getDirectory){ wPost('opfs-unavailable',"Missing required OPFS APIs."); }else{ installAsyncProxy(self); } |
Added ext/wasm/api/sqlite3-v-helper.js.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 | /* ** 2022-11-30 ** ** 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 installs sqlite3.vfs, and object which exists to assist in the creation of JavaScript implementations of sqlite3_vfs, along with its virtual table counterpart, sqlite3.vtab. */ 'use strict'; self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3; const vfs = Object.create(null), vtab = Object.create(null); sqlite3.vfs = vfs; sqlite3.vtab = vtab; const sii = capi.sqlite3_index_info; /** If n is >=0 and less than this.$nConstraint, this function returns either a WASM pointer to the 0-based nth entry of this.$aConstraint (if passed a truthy 2nd argument) or an sqlite3_index_info.sqlite3_index_constraint object wrapping that address (if passed a falsy value or no 2nd argument). Returns a falsy value if n is out of range. */ sii.prototype.nthConstraint = function(n, asPtr=false){ if(n<0 || n>=this.$nConstraint) return false; const ptr = this.$aConstraint + ( sii.sqlite3_index_constraint.structInfo.sizeof * n ); return asPtr ? ptr : new sii.sqlite3_index_constraint(ptr); }; /** Works identically to nthConstraint() but returns state from this.$aConstraintUsage, so returns an sqlite3_index_info.sqlite3_index_constraint_usage instance if passed no 2nd argument or a falsy 2nd argument. */ sii.prototype.nthConstraintUsage = function(n, asPtr=false){ if(n<0 || n>=this.$nConstraint) return false; const ptr = this.$aConstraintUsage + ( sii.sqlite3_index_constraint_usage.structInfo.sizeof * n ); return asPtr ? ptr : new sii.sqlite3_index_constraint_usage(ptr); }; /** If n is >=0 and less than this.$nOrderBy, this function returns either a WASM pointer to the 0-based nth entry of this.$aOrderBy (if passed a truthy 2nd argument) or an sqlite3_index_info.sqlite3_index_orderby object wrapping that address (if passed a falsy value or no 2nd argument). Returns a falsy value if n is out of range. */ sii.prototype.nthOrderBy = function(n, asPtr=false){ if(n<0 || n>=this.$nOrderBy) return false; const ptr = this.$aOrderBy + ( sii.sqlite3_index_orderby.structInfo.sizeof * n ); return asPtr ? ptr : new sii.sqlite3_index_orderby(ptr); }; /** Installs a StructBinder-bound function pointer member of the given name and function in the given StructType target object. It creates a WASM proxy for the given function and arranges for that proxy to be cleaned up when tgt.dispose() is called. Throws on the slightest hint of error, e.g. tgt is-not-a StructType, name does not map to a struct-bound member, etc. As a special case, if the given function is a pointer, then `wasm.functionEntry()` is used to validate that it is a known function. If so, it is used as-is with no extra level of proxying or cleanup, else an exception is thrown. It is legal to pass a value of 0, indicating a NULL pointer, with the caveat that 0 _is_ a legal function pointer in WASM but it will not be accepted as such _here_. (Justification: the function at address zero must be one which initially came from the WASM module, not a method we want to bind to a virtual table or VFS.) This function returns a proxy for itself which is bound to tgt and takes 2 args (name,func). That function returns the same thing as this one, permitting calls to be chained. If called with only 1 arg, it has no side effects but returns a func with the same signature as described above. ACHTUNG: because we cannot generically know how to transform JS exceptions into result codes, the installed functions do no automatic catching of exceptions. It is critical, to avoid undefined behavior in the C layer, that methods mapped via this function do not throw. The exception, as it were, to that rule is... If applyArgcCheck is true then each JS function (as opposed to function pointers) gets wrapped in a proxy which asserts that it is passed the expected number of arguments, throwing if the argument count does not match expectations. That is only intended for dev-time usage for sanity checking, and will leave the C environment in an undefined state. */ const installMethod = function callee( tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck ){ if(!(tgt instanceof sqlite3.StructBinder.StructType)){ toss("Usage error: target object is-not-a StructType."); }else if(!(func instanceof Function) && !wasm.isPtr(func)){ toss("Usage errror: expecting a Function or WASM pointer to one."); } if(1===arguments.length){ return (n,f)=>callee(tgt, n, f, applyArgcCheck); } if(!callee.argcProxy){ callee.argcProxy = function(tgt, funcName, func,sig){ return function(...args){ if(func.length!==arguments.length){ toss("Argument mismatch for", tgt.structInfo.name+"::"+funcName +": Native signature is:",sig); } return func.apply(this, args); } }; /* An ondispose() callback for use with sqlite3.StructBinder-created types. */ callee.removeFuncList = function(){ if(this.ondispose.__removeFuncList){ this.ondispose.__removeFuncList.forEach( (v,ndx)=>{ if('number'===typeof v){ try{wasm.uninstallFunction(v)} catch(e){/*ignore*/} } /* else it's a descriptive label for the next number in the list. */ } ); delete this.ondispose.__removeFuncList; } }; }/*static init*/ const sigN = tgt.memberSignature(name); if(sigN.length<2){ toss("Member",name,"does not have a function pointer signature:",sigN); } const memKey = tgt.memberKey(name); const fProxy = (applyArgcCheck && !wasm.isPtr(func)) /** This middle-man proxy is only for use during development, to confirm that we always pass the proper number of arguments. We know that the C-level code will always use the correct argument count. */ ? callee.argcProxy(tgt, memKey, func, sigN) : func; if(wasm.isPtr(fProxy)){ if(fProxy && !wasm.functionEntry(fProxy)){ toss("Pointer",fProxy,"is not a WASM function table entry."); } tgt[memKey] = fProxy; }else{ const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true)); tgt[memKey] = pFunc; if(!tgt.ondispose || !tgt.ondispose.__removeFuncList){ tgt.addOnDispose('ondispose.__removeFuncList handler', callee.removeFuncList); tgt.ondispose.__removeFuncList = []; } tgt.ondispose.__removeFuncList.push(memKey, pFunc); } return (n,f)=>callee(tgt, n, f, applyArgcCheck); }/*installMethod*/; installMethod.installMethodArgcCheck = false; /** Installs methods into the given StructType-type instance. Each entry in the given methods object must map to a known member of the given StructType, else an exception will be triggered. See installMethod() for more details, including the semantics of the 3rd argument. As an exception to the above, if any two or more methods in the 2nd argument are the exact same function, installMethod() is _not_ called for the 2nd and subsequent instances, and instead those instances get assigned the same method pointer which is created for the first instance. This optimization is primarily to accommodate special handling of sqlite3_module::xConnect and xCreate methods. On success, returns its first argument. Throws on error. */ const installMethods = function( structInstance, methods, applyArgcCheck = installMethod.installMethodArgcCheck ){ const seen = new Map /* map of <Function, memberName> */; for(const k of Object.keys(methods)){ const m = methods[k]; const prior = seen.get(m); if(prior){ const mkey = structInstance.memberKey(k); structInstance[mkey] = structInstance[structInstance.memberKey(prior)]; }else{ installMethod(structInstance, k, m, applyArgcCheck); seen.set(m, k); } } return structInstance; }; /** Equivalent to calling installMethod(this,...arguments) with a first argument of this object. If called with 1 or 2 arguments and the first is an object, it's instead equivalent to calling installMethods(this,...arguments). */ sqlite3.StructBinder.StructType.prototype.installMethod = function callee( name, func, applyArgcCheck = installMethod.installMethodArgcCheck ){ return (arguments.length < 3 && name && 'object'===typeof name) ? installMethods(this, ...arguments) : installMethod(this, ...arguments); }; /** Equivalent to calling installMethods() with a first argument of this object. */ sqlite3.StructBinder.StructType.prototype.installMethods = function( methods, applyArgcCheck = installMethod.installMethodArgcCheck ){ return installMethods(this, methods, applyArgcCheck); }; /** Uses sqlite3_vfs_register() to register this sqlite3.capi.sqlite3_vfs. This object must have already been filled out properly. If the first argument is truthy, the VFS is registered as the default VFS, else it is not. On success, returns this object. Throws on error. */ capi.sqlite3_vfs.prototype.registerVfs = function(asDefault=false){ if(!(this instanceof sqlite3.capi.sqlite3_vfs)){ toss("Expecting a sqlite3_vfs-type argument."); } const rc = capi.sqlite3_vfs_register(this, asDefault ? 1 : 0); if(rc){ toss("sqlite3_vfs_register(",this,") failed with rc",rc); } if(this.pointer !== capi.sqlite3_vfs_find(this.$zName)){ toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS", this); } return this; }; /** A wrapper for installMethods() or registerVfs() to reduce installation of a VFS and/or its I/O methods to a single call. Accepts an object which contains the properties "io" and/or "vfs", each of which is itself an object with following properties: - `struct`: an sqlite3.StructType-type struct. This must be a populated (except for the methods) object of type sqlite3_io_methods (for the "io" entry) or sqlite3_vfs (for the "vfs" entry). - `methods`: an object mapping sqlite3_io_methods method names (e.g. 'xClose') to JS implementations of those methods. The JS implementations must be call-compatible with their native counterparts. For each of those object, this function passes its (`struct`, `methods`, (optional) `applyArgcCheck`) properties to installMethods(). If the `vfs` entry is set then: - Its `struct` property's registerVfs() is called. The `vfs` entry may optionally have an `asDefault` property, which gets passed as the argument to registerVfs(). - If `struct.$zName` is falsy and the entry has a string-type `name` property, `struct.$zName` is set to the C-string form of that `name` value before registerVfs() is called. On success returns this object. Throws on error. */ vfs.installVfs = function(opt){ let count = 0; const propList = ['io','vfs']; for(const key of propList){ const o = opt[key]; if(o){ ++count; installMethods(o.struct, o.methods, !!o.applyArgcCheck); if('vfs'===key){ if(!o.struct.$zName && 'string'===typeof o.name){ o.struct.addOnDispose( o.struct.$zName = wasm.allocCString(o.name) ); } o.struct.registerVfs(!!o.asDefault); } } } if(!count) toss("Misuse: installVfs() options object requires at least", "one of:", propList); return this; }; /** Internal factory function for xVtab and xCursor impls. */ const __xWrapFactory = function(methodName,StructType){ return function(ptr,removeMapping=false){ if(0===arguments.length) ptr = new StructType; if(ptr instanceof StructType){ //T.assert(!this.has(ptr.pointer)); this.set(ptr.pointer, ptr); return ptr; }else if(!wasm.isPtr(ptr)){ sqlite3.SQLite3Error.toss("Invalid argument to",methodName+"()"); } let rc = this.get(ptr); if(removeMapping) this.delete(ptr); return rc; }.bind(new Map); }; /** A factory function which implements a simple lifetime manager for mappings between C struct pointers and their JS-level wrappers. The first argument must be the logical name of the manager (e.g. 'xVtab' or 'xCursor'), which is only used for error reporting. The second must be the capi.XYZ struct-type value, e.g. capi.sqlite3_vtab or capi.sqlite3_vtab_cursor. Returns an object with 4 methods: create(), get(), unget(), and dispose(), plus a StructType member with the value of the 2nd argument. The methods are documented in the body of this function. */ const StructPtrMapper = function(name, StructType){ const __xWrap = __xWrapFactory(name,StructType); /** This object houses a small API for managing mappings of (`T*`) to StructType<T> objects, specifically within the lifetime requirements of sqlite3_module methods. */ return Object.assign(Object.create(null),{ /** The StructType object for this object's API. */ StructType, /** Creates a new StructType object, writes its `pointer` value to the given output pointer, and returns that object. Its intended usage depends on StructType: sqlite3_vtab: to be called from sqlite3_module::xConnect() or xCreate() implementations. sqlite3_vtab_cursor: to be called from xOpen(). This will throw if allocation of the StructType instance fails or if ppOut is not a pointer-type value. */ create: (ppOut)=>{ const rc = __xWrap(); wasm.pokePtr(ppOut, rc.pointer); return rc; }, /** Returns the StructType object previously mapped to the given pointer using create(). Its intended usage depends on StructType: sqlite3_vtab: to be called from sqlite3_module methods which take a (sqlite3_vtab*) pointer _except_ for xDestroy()/xDisconnect(), in which case unget() or dispose(). sqlite3_vtab_cursor: to be called from any sqlite3_module methods which take a `sqlite3_vtab_cursor*` argument except xClose(), in which case use unget() or dispose(). Rule to remember: _never_ call dispose() on an instance returned by this function. */ get: (pCObj)=>__xWrap(pCObj), /** Identical to get() but also disconnects the mapping between the given pointer and the returned StructType object, such that future calls to this function or get() with the same pointer will return the undefined value. Its intended usage depends on StructType: sqlite3_vtab: to be called from sqlite3_module::xDisconnect() or xDestroy() implementations or in error handling of a failed xCreate() or xConnect(). sqlite3_vtab_cursor: to be called from xClose() or during cleanup in a failed xOpen(). Calling this method obligates the caller to call dispose() on the returned object when they're done with it. */ unget: (pCObj)=>__xWrap(pCObj,true), /** Works like unget() plus it calls dispose() on the StructType object. */ dispose: (pCObj)=>{ const o = __xWrap(pCObj,true); if(o) o.dispose(); } }); }; /** A lifetime-management object for mapping `sqlite3_vtab*` instances in sqlite3_module methods to capi.sqlite3_vtab objects. The API docs are in the API-internal StructPtrMapper(). */ vtab.xVtab = StructPtrMapper('xVtab', capi.sqlite3_vtab); /** A lifetime-management object for mapping `sqlite3_vtab_cursor*` instances in sqlite3_module methods to capi.sqlite3_vtab_cursor objects. The API docs are in the API-internal StructPtrMapper(). */ vtab.xCursor = StructPtrMapper('xCursor', capi.sqlite3_vtab_cursor); /** Convenience form of creating an sqlite3_index_info wrapper, intended for use in xBestIndex implementations. Note that the caller is expected to call dispose() on the returned object before returning. Though not _strictly_ required, as that object does not own the pIdxInfo memory, it is nonetheless good form. */ vtab.xIndexInfo = (pIdxInfo)=>new capi.sqlite3_index_info(pIdxInfo); /** Given an error object, this function returns sqlite3.capi.SQLITE_NOMEM if (e instanceof sqlite3.WasmAllocError), else it returns its second argument. Its intended usage is in the methods of a sqlite3_vfs or sqlite3_module: ``` try{ let rc = ... return rc; }catch(e){ return sqlite3.vtab.exceptionToRc(e, sqlite3.capi.SQLITE_XYZ); // where SQLITE_XYZ is some call-appropriate result code. } ``` */ /**vfs.exceptionToRc = vtab.exceptionToRc = (e, defaultRc=capi.SQLITE_ERROR)=>( (e instanceof sqlite3.WasmAllocError) ? capi.SQLITE_NOMEM : defaultRc );*/ /** Given an sqlite3_module method name and error object, this function returns sqlite3.capi.SQLITE_NOMEM if (e instanceof sqlite3.WasmAllocError), else it returns its second argument. Its intended usage is in the methods of a sqlite3_vfs or sqlite3_module: ``` try{ let rc = ... return rc; }catch(e){ return sqlite3.vtab.xError( 'xColumn', e, sqlite3.capi.SQLITE_XYZ); // where SQLITE_XYZ is some call-appropriate result code. } ``` If no 3rd argument is provided, its default depends on the error type: - An sqlite3.WasmAllocError always resolves to capi.SQLITE_NOMEM. - If err is an SQLite3Error then its `resultCode` property is used. - If all else fails, capi.SQLITE_ERROR is used. If xError.errorReporter is a function, it is called in order to report the error, else the error is not reported. If that function throws, that exception is ignored. */ vtab.xError = function f(methodName, err, defaultRc){ if(f.errorReporter instanceof Function){ try{f.errorReporter("sqlite3_module::"+methodName+"(): "+err.message);} catch(e){/*ignored*/} } let rc; if(err instanceof sqlite3.WasmAllocError) rc = capi.SQLITE_NOMEM; else if(arguments.length>2) rc = defaultRc; else if(err instanceof sqlite3.SQLite3Error) rc = err.resultCode; return rc || capi.SQLITE_ERROR; }; vtab.xError.errorReporter = 1 ? console.error.bind(console) : false; /** "The problem" with this is that it introduces an outer function with a different arity than the passed-in method callback. That means we cannot do argc validation on these. Additionally, some methods (namely xConnect) may have call-specific error handling. It would be a shame to hard-coded that per-method support in this function. */ /** vtab.methodCatcher = function(methodName, method, defaultErrRc=capi.SQLITE_ERROR){ return function(...args){ try { method(...args); } }catch(e){ return vtab.xError(methodName, e, defaultRc) } }; */ /** A helper for sqlite3_vtab::xRowid() and xUpdate() implementations. It must be passed the final argument to one of those methods (an output pointer to an int64 row ID) and the value to store at the output pointer's address. Returns the same as wasm.poke() and will throw if the 1st or 2nd arguments are invalid for that function. Example xRowid impl: ``` const xRowid = (pCursor, ppRowid64)=>{ const c = vtab.xCursor(pCursor); vtab.xRowid(ppRowid64, c.myRowId); return 0; }; ``` */ vtab.xRowid = (ppRowid64, value)=>wasm.poke(ppRowid64, value, 'i64'); /** A helper to initialize and set up an sqlite3_module object for later installation into individual databases using sqlite3_create_module(). Requires an object with the following properties: - `methods`: an object containing a mapping of properties with the C-side names of the sqlite3_module methods, e.g. xCreate, xBestIndex, etc., to JS implementations for those functions. Certain special-case handling is performed, as described below. - `catchExceptions` (default=false): if truthy, the given methods are not mapped as-is, but are instead wrapped inside wrappers which translate exceptions into result codes of SQLITE_ERROR or SQLITE_NOMEM, depending on whether the exception is an sqlite3.WasmAllocError. In the case of the xConnect and xCreate methods, the exception handler also sets the output error string to the exception's error string. - OPTIONAL `struct`: a sqlite3.capi.sqlite3_module() instance. If not set, one will be created automatically. If the current "this" is-a sqlite3_module then it is unconditionally used in place of `struct`. - OPTIONAL `iVersion`: if set, it must be an integer value and it gets assigned to the `$iVersion` member of the struct object. If it's _not_ set, and the passed-in `struct` object's `$iVersion` is 0 (the default) then this function attempts to define a value for that property based on the list of methods it has. If `catchExceptions` is false, it is up to the client to ensure that no exceptions escape the methods, as doing so would move them through the C API, leading to undefined behavior. (vtab.xError() is intended to assist in reporting such exceptions.) Certain methods may refer to the same implementation. To simplify the definition of such methods: - If `methods.xConnect` is `true` then the value of `methods.xCreate` is used in its place, and vice versa. sqlite treats xConnect/xCreate functions specially if they are exactly the same function (same pointer value). - If `methods.xDisconnect` is true then the value of `methods.xDestroy` is used in its place, and vice versa. This is to facilitate creation of those methods inline in the passed-in object without requiring the client to explicitly get a reference to one of them in order to assign it to the other one. The `catchExceptions`-installed handlers will account for identical references to the above functions and will install the same wrapper function for both. The given methods are expected to return integer values, as expected by the C API. If `catchExceptions` is truthy, the return value of the wrapped function will be used as-is and will be translated to 0 if the function returns a falsy value (e.g. if it does not have an explicit return). If `catchExceptions` is _not_ active, the method implementations must explicitly return integer values. Throws on error. On success, returns the sqlite3_module object (`this` or `opt.struct` or a new sqlite3_module instance, depending on how it's called). */ vtab.setupModule = function(opt){ let createdMod = false; const mod = (this instanceof capi.sqlite3_module) ? this : (opt.struct || (createdMod = new capi.sqlite3_module())); try{ const methods = opt.methods || toss("Missing 'methods' object."); for(const e of Object.entries({ // -----^ ==> [k,v] triggers a broken code transformation in // some versions of the emsdk toolchain. xConnect: 'xCreate', xDisconnect: 'xDestroy' })){ // Remap X=true to X=Y for certain X/Y combinations const k = e[0], v = e[1]; if(true === methods[k]) methods[k] = methods[v]; else if(true === methods[v]) methods[v] = methods[k]; } if(opt.catchExceptions){ const fwrap = function(methodName, func){ if(['xConnect','xCreate'].indexOf(methodName) >= 0){ return function(pDb, pAux, argc, argv, ppVtab, pzErr){ try{return func(...arguments) || 0} catch(e){ if(!(e instanceof sqlite3.WasmAllocError)){ wasm.dealloc(wasm.peekPtr(pzErr)); wasm.pokePtr(pzErr, wasm.allocCString(e.message)); } return vtab.xError(methodName, e); } }; }else{ return function(...args){ try{return func(...args) || 0} catch(e){ return vtab.xError(methodName, e); } }; } }; const mnames = [ 'xCreate', 'xConnect', 'xBestIndex', 'xDisconnect', 'xDestroy', 'xOpen', 'xClose', 'xFilter', 'xNext', 'xEof', 'xColumn', 'xRowid', 'xUpdate', 'xBegin', 'xSync', 'xCommit', 'xRollback', 'xFindFunction', 'xRename', 'xSavepoint', 'xRelease', 'xRollbackTo', 'xShadowName' ]; const remethods = Object.create(null); for(const k of mnames){ const m = methods[k]; if(!(m instanceof Function)) continue; else if('xConnect'===k && methods.xCreate===m){ remethods[k] = methods.xCreate; }else if('xCreate'===k && methods.xConnect===m){ remethods[k] = methods.xConnect; }else{ remethods[k] = fwrap(k, m); } } installMethods(mod, remethods, false); }else{ // No automatic exception handling. Trust the client // to not throw. installMethods( mod, methods, !!opt.applyArgcCheck/*undocumented option*/ ); } if(0===mod.$iVersion){ let v; if('number'===typeof opt.iVersion) v = opt.iVersion; else if(mod.$xShadowName) v = 3; else if(mod.$xSavePoint || mod.$xRelease || mod.$xRollbackTo) v = 2; else v = 1; mod.$iVersion = v; } }catch(e){ if(createdMod) createdMod.dispose(); throw e; } return mod; }/*setupModule()*/; /** Equivalent to calling vtab.setupModule() with this sqlite3_module object as the call's `this`. */ capi.sqlite3_module.prototype.setupModule = function(opt){ return vtab.setupModule.call(this, opt); }; }/*sqlite3ApiBootstrap.initializers.push()*/); |
Added ext/wasm/api/sqlite3-vfs-opfs.c-pp.js.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 | /* 2022-09-18 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 holds the synchronous half of an sqlite3_vfs implementation which proxies, in a synchronous fashion, the asynchronous Origin-Private FileSystem (OPFS) APIs using a second Worker, implemented in sqlite3-opfs-async-proxy.js. This file is intended to be appended to the main sqlite3 JS deliverable somewhere after sqlite3-api-oo1.js and before sqlite3-api-cleanup.js. */ 'use strict'; self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ /** installOpfsVfs() returns a Promise which, on success, installs an sqlite3_vfs named "opfs", suitable for use with all sqlite3 APIs which accept a VFS. It is intended to be called via sqlite3ApiBootstrap.initializersAsync or an equivalent mechanism. The installed VFS uses the Origin-Private FileSystem API for all file storage. On error it is rejected with an exception explaining the problem. Reasons for rejection include, but are not limited to: - The counterpart Worker (see below) could not be loaded. - The environment does not support OPFS. That includes when this function is called from the main window thread. Significant notes and limitations: - As of this writing, OPFS is still very much in flux and only available in bleeding-edge versions of Chrome (v102+, noting that that number will increase as the OPFS API matures). - The OPFS features used here are only available in dedicated Worker threads. This file tries to detect that case, resulting in a rejected Promise if those features do not seem to be available. - It requires the SharedArrayBuffer and Atomics classes, and the former is only available if the HTTP server emits the so-called COOP and COEP response headers. These features are required for proxying OPFS's synchronous API via the synchronous interface required by the sqlite3_vfs API. - This function may only be called a single time. When called, this function removes itself from the sqlite3 object. All arguments to this function are for internal/development purposes only. They do not constitute a public API and may change at any time. The argument may optionally be a plain object with the following configuration options: - proxyUri: as described above - verbose (=2): an integer 0-3. 0 disables all logging, 1 enables logging of errors. 2 enables logging of warnings and errors. 3 additionally enables debugging info. - sanityChecks (=false): if true, some basic sanity tests are run on the OPFS VFS API after it's initialized, before the returned Promise resolves. On success, the Promise resolves to the top-most sqlite3 namespace object and that object gets a new object installed in its `opfs` property, containing several OPFS-specific utilities. */ const installOpfsVfs = function callee(options){ if(!self.SharedArrayBuffer || !self.Atomics){ return Promise.reject( new Error("Cannot install OPFS: Missing SharedArrayBuffer and/or Atomics. "+ "The server must emit the COOP/COEP response headers to enable those. "+ "See https://sqlite.org/wasm/doc/trunk/persistence.md#coop-coep") ); }else if(self.window===self && self.document){ return Promise.reject( new Error("The OPFS sqlite3_vfs cannot run in the main thread "+ "because it requires Atomics.wait().") ); }else if(!self.FileSystemHandle || !self.FileSystemDirectoryHandle || !self.FileSystemFileHandle || !self.FileSystemFileHandle.prototype.createSyncAccessHandle || !navigator.storage.getDirectory){ return Promise.reject( new Error("Missing required OPFS APIs.") ); } if(!options || 'object'!==typeof options){ options = Object.create(null); } const urlParams = new URL(self.location.href).searchParams; if(undefined===options.verbose){ options.verbose = urlParams.has('opfs-verbose') ? (+urlParams.get('opfs-verbose') || 2) : 1; } if(undefined===options.sanityChecks){ options.sanityChecks = urlParams.has('opfs-sanity-check'); } if(undefined===options.proxyUri){ options.proxyUri = callee.defaultProxyUri; } //console.warn("OPFS options =",options,self.location); if('function' === typeof options.proxyUri){ options.proxyUri = options.proxyUri(); } const thePromise = new Promise(function(promiseResolve, promiseReject_){ const loggers = { 0:console.error.bind(console), 1:console.warn.bind(console), 2:console.log.bind(console) }; const logImpl = (level,...args)=>{ if(options.verbose>level) loggers[level]("OPFS syncer:",...args); }; const log = (...args)=>logImpl(2, ...args); const warn = (...args)=>logImpl(1, ...args); const error = (...args)=>logImpl(0, ...args); const toss = sqlite3.util.toss; const capi = sqlite3.capi; const wasm = sqlite3.wasm; const sqlite3_vfs = capi.sqlite3_vfs; const sqlite3_file = capi.sqlite3_file; const sqlite3_io_methods = capi.sqlite3_io_methods; /** Generic utilities for working with OPFS. This will get filled out by the Promise setup and, on success, installed as sqlite3.opfs. ACHTUNG: do not rely on these APIs in client code. They are experimental and subject to change or removal as the OPFS-specific sqlite3_vfs evolves. */ const opfsUtil = Object.create(null); /** Returns true if _this_ thread has access to the OPFS APIs. */ const thisThreadHasOPFS = ()=>{ return self.FileSystemHandle && self.FileSystemDirectoryHandle && self.FileSystemFileHandle && self.FileSystemFileHandle.prototype.createSyncAccessHandle && navigator.storage.getDirectory; }; /** Not part of the public API. Solely for internal/development use. */ opfsUtil.metrics = { dump: function(){ let k, n = 0, t = 0, w = 0; for(k in state.opIds){ const m = metrics[k]; n += m.count; t += m.time; w += m.wait; m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0; m.avgWait = (m.count && m.wait) ? (m.wait / m.count) : 0; } console.log(self.location.href, "metrics for",self.location.href,":",metrics, "\nTotal of",n,"op(s) for",t, "ms (incl. "+w+" ms of waiting on the async side)"); console.log("Serialization metrics:",metrics.s11n); W.postMessage({type:'opfs-async-metrics'}); }, reset: function(){ let k; const r = (m)=>(m.count = m.time = m.wait = 0); for(k in state.opIds){ r(metrics[k] = Object.create(null)); } let s = metrics.s11n = Object.create(null); s = s.serialize = Object.create(null); s.count = s.time = 0; s = metrics.s11n.deserialize = Object.create(null); s.count = s.time = 0; } }/*metrics*/; const opfsVfs = new sqlite3_vfs(); const opfsIoMethods = new sqlite3_io_methods(); const promiseReject = function(err){ opfsVfs.dispose(); return promiseReject_(err); }; const W = //#if target=es6-module new Worker(new URL(options.proxyUri, import.meta.url)); //#else new Worker(options.proxyUri); //#endif W._originalOnError = W.onerror /* will be restored later */; W.onerror = function(err){ // The error object doesn't contain any useful info when the // failure is, e.g., that the remote script is 404. error("Error initializing OPFS asyncer:",err); promiseReject(new Error("Loading OPFS async Worker failed for unknown reasons.")); }; const pDVfs = capi.sqlite3_vfs_find(null)/*pointer to default VFS*/; const dVfs = pDVfs ? new sqlite3_vfs(pDVfs) : null /* dVfs will be null when sqlite3 is built with SQLITE_OS_OTHER. */; opfsVfs.$iVersion = 2/*yes, two*/; opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof; opfsVfs.$mxPathname = 1024/*sure, why not?*/; opfsVfs.$zName = wasm.allocCString("opfs"); // All C-side memory of opfsVfs is zeroed out, but just to be explicit: opfsVfs.$xDlOpen = opfsVfs.$xDlError = opfsVfs.$xDlSym = opfsVfs.$xDlClose = null; opfsVfs.ondispose = [ '$zName', opfsVfs.$zName, 'cleanup default VFS wrapper', ()=>(dVfs ? dVfs.dispose() : null), 'cleanup opfsIoMethods', ()=>opfsIoMethods.dispose() ]; /** Pedantic sidebar about opfsVfs.ondispose: the entries in that array are items to clean up when opfsVfs.dispose() is called, but in this environment it will never be called. The VFS instance simply hangs around until the WASM module instance is cleaned up. We "could" _hypothetically_ clean it up by "importing" an sqlite3_os_end() impl into the wasm build, but the shutdown order of the wasm engine and the JS one are undefined so there is no guaranty that the opfsVfs instance would be available in one environment or the other when sqlite3_os_end() is called (_if_ it gets called at all in a wasm build, which is undefined). */ /** State which we send to the async-api Worker or share with it. This object must initially contain only cloneable or sharable objects. After the worker's "inited" message arrives, other types of data may be added to it. For purposes of Atomics.wait() and Atomics.notify(), we use a SharedArrayBuffer with one slot reserved for each of the API proxy's methods. The sync side of the API uses Atomics.wait() on the corresponding slot and the async side uses Atomics.notify() on that slot. The approach of using a single SAB to serialize comms for all instances might(?) lead to deadlock situations in multi-db cases. We should probably have one SAB here with a single slot for locking a per-file initialization step and then allocate a separate SAB like the above one for each file. That will require a bit of acrobatics but should be feasible. The most problematic part is that xOpen() would have to use postMessage() to communicate its SharedArrayBuffer, and mixing that approach with Atomics.wait/notify() gets a bit messy. */ const state = Object.create(null); state.verbose = options.verbose; state.littleEndian = (()=>{ const buffer = new ArrayBuffer(2); new DataView(buffer).setInt16(0, 256, true /* ==>littleEndian */); // Int16Array uses the platform's endianness. return new Int16Array(buffer)[0] === 256; })(); /** asyncIdleWaitTime is how long (ms) to wait, in the async proxy, for each Atomics.wait() when waiting on inbound VFS API calls. We need to wake up periodically to give the thread a chance to do other things. If this is too high (e.g. 500ms) then even two workers/tabs can easily run into locking errors. Some multiple of this value is also used for determining how long to wait on lock contention to free up. */ state.asyncIdleWaitTime = 150; /** Whether the async counterpart should log exceptions to the serialization channel. That produces a great deal of noise for seemingly innocuous things like xAccess() checks for missing files, so this option may have one of 3 values: 0 = no exception logging. 1 = only log exceptions for "significant" ops like xOpen(), xRead(), and xWrite(). 2 = log all exceptions. */ state.asyncS11nExceptions = 1; /* Size of file I/O buffer block. 64k = max sqlite3 page size, and xRead/xWrite() will never deal in blocks larger than that. */ state.fileBufferSize = 1024 * 64; state.sabS11nOffset = state.fileBufferSize; /** The size of the block in our SAB for serializing arguments and result values. Needs to be large enough to hold serialized values of any of the proxied APIs. Filenames are the largest part but are limited to opfsVfs.$mxPathname bytes. We also store exceptions there, so it needs to be long enough to hold a reasonably long exception string. */ state.sabS11nSize = opfsVfs.$mxPathname * 2; /** The SAB used for all data I/O between the synchronous and async halves (file i/o and arg/result s11n). */ state.sabIO = new SharedArrayBuffer( state.fileBufferSize/* file i/o block */ + state.sabS11nSize/* argument/result serialization block */ ); state.opIds = Object.create(null); const metrics = Object.create(null); { /* Indexes for use in our SharedArrayBuffer... */ let i = 0; /* SAB slot used to communicate which operation is desired between both workers. This worker writes to it and the other listens for changes. */ state.opIds.whichOp = i++; /* Slot for storing return values. This worker listens to that slot and the other worker writes to it. */ state.opIds.rc = i++; /* Each function gets an ID which this worker writes to the whichOp slot. The async-api worker uses Atomic.wait() on the whichOp slot to figure out which operation to run next. */ state.opIds.xAccess = i++; state.opIds.xClose = i++; state.opIds.xDelete = i++; state.opIds.xDeleteNoWait = i++; state.opIds.xFileControl = i++; state.opIds.xFileSize = i++; state.opIds.xLock = i++; state.opIds.xOpen = i++; state.opIds.xRead = i++; state.opIds.xSleep = i++; state.opIds.xSync = i++; state.opIds.xTruncate = i++; state.opIds.xUnlock = i++; state.opIds.xWrite = i++; state.opIds.mkdir = i++; state.opIds['opfs-async-metrics'] = i++; state.opIds['opfs-async-shutdown'] = i++; /* The retry slot is used by the async part for wait-and-retry semantics. Though we could hypothetically use the xSleep slot for that, doing so might lead to undesired side effects. */ state.opIds.retry = i++; state.sabOP = new SharedArrayBuffer( i * 4/* ==sizeof int32, noting that Atomics.wait() and friends can only function on Int32Array views of an SAB. */); opfsUtil.metrics.reset(); } /** SQLITE_xxx constants to export to the async worker counterpart... */ state.sq3Codes = Object.create(null); [ 'SQLITE_ACCESS_EXISTS', 'SQLITE_ACCESS_READWRITE', 'SQLITE_BUSY', 'SQLITE_ERROR', 'SQLITE_IOERR', 'SQLITE_IOERR_ACCESS', 'SQLITE_IOERR_CLOSE', 'SQLITE_IOERR_DELETE', 'SQLITE_IOERR_FSYNC', 'SQLITE_IOERR_LOCK', 'SQLITE_IOERR_READ', 'SQLITE_IOERR_SHORT_READ', 'SQLITE_IOERR_TRUNCATE', 'SQLITE_IOERR_UNLOCK', 'SQLITE_IOERR_WRITE', 'SQLITE_LOCK_EXCLUSIVE', 'SQLITE_LOCK_NONE', 'SQLITE_LOCK_PENDING', 'SQLITE_LOCK_RESERVED', 'SQLITE_LOCK_SHARED', 'SQLITE_LOCKED', 'SQLITE_MISUSE', 'SQLITE_NOTFOUND', 'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE', 'SQLITE_OPEN_MAIN_DB', 'SQLITE_OPEN_READONLY' ].forEach((k)=>{ if(undefined === (state.sq3Codes[k] = capi[k])){ toss("Maintenance required: not found:",k); } }); state.opfsFlags = Object.assign(Object.create(null),{ /** Flag for use with xOpen(). "opfs-unlock-asap=1" enables this. See defaultUnlockAsap, below. */ OPFS_UNLOCK_ASAP: 0x01, /** If true, any async routine which implicitly acquires a sync access handle (i.e. an OPFS lock) will release that locks at the end of the call which acquires it. If false, such "autolocks" are not released until the VFS is idle for some brief amount of time. The benefit of enabling this is much higher concurrency. The down-side is much-reduced performance (as much as a 4x decrease in speedtest1). */ defaultUnlockAsap: false }); /** Runs the given operation (by name) in the async worker counterpart, waits for its response, and returns the result which the async worker writes to SAB[state.opIds.rc]. The 2nd and subsequent arguments must be the aruguments for the async op. */ const opRun = (op,...args)=>{ const opNdx = state.opIds[op] || toss("Invalid op ID:",op); state.s11n.serialize(...args); Atomics.store(state.sabOPView, state.opIds.rc, -1); Atomics.store(state.sabOPView, state.opIds.whichOp, opNdx); Atomics.notify(state.sabOPView, state.opIds.whichOp) /* async thread will take over here */; const t = performance.now(); Atomics.wait(state.sabOPView, state.opIds.rc, -1) /* When this wait() call returns, the async half will have completed the operation and reported its results. */; const rc = Atomics.load(state.sabOPView, state.opIds.rc); metrics[op].wait += performance.now() - t; if(rc && state.asyncS11nExceptions){ const err = state.s11n.deserialize(); if(err) error(op+"() async error:",...err); } return rc; }; /** Not part of the public API. Only for test/development use. */ opfsUtil.debug = { asyncShutdown: ()=>{ warn("Shutting down OPFS async listener. The OPFS VFS will no longer work."); opRun('opfs-async-shutdown'); }, asyncRestart: ()=>{ warn("Attempting to restart OPFS VFS async listener. Might work, might not."); W.postMessage({type: 'opfs-async-restart'}); } }; const initS11n = ()=>{ /** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ACHTUNG: this code is 100% duplicated in the other half of this proxy! The documentation is maintained in the "synchronous half". !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! This proxy de/serializes cross-thread function arguments and output-pointer values via the state.sabIO SharedArrayBuffer, using the region defined by (state.sabS11nOffset, state.sabS11nOffset]. Only one dataset is recorded at a time. This is not a general-purpose format. It only supports the range of operations, and data sizes, needed by the sqlite3_vfs and sqlite3_io_methods operations. Serialized data are transient and this serialization algorithm may change at any time. The data format can be succinctly summarized as: Nt...Td...D Where: - N = number of entries (1 byte) - t = type ID of first argument (1 byte) - ...T = type IDs of the 2nd and subsequent arguments (1 byte each). - d = raw bytes of first argument (per-type size). - ...D = raw bytes of the 2nd and subsequent arguments (per-type size). All types except strings have fixed sizes. Strings are stored using their TextEncoder/TextDecoder representations. It would arguably make more sense to store them as Int16Arrays of their JS character values, but how best/fastest to get that in and out of string form is an open point. Initial experimentation with that approach did not gain us any speed. Historical note: this impl was initially about 1% this size by using using JSON.stringify/parse(), but using fit-to-purpose serialization saves considerable runtime. */ if(state.s11n) return state.s11n; const textDecoder = new TextDecoder(), textEncoder = new TextEncoder('utf-8'), viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize), viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize); state.s11n = Object.create(null); /* Only arguments and return values of these types may be serialized. This covers the whole range of types needed by the sqlite3_vfs API. */ const TypeIds = Object.create(null); TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' }; TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' }; TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' }; TypeIds.string = { id: 4 }; const getTypeId = (v)=>( TypeIds[typeof v] || toss("Maintenance required: this value type cannot be serialized.",v) ); const getTypeIdById = (tid)=>{ switch(tid){ case TypeIds.number.id: return TypeIds.number; case TypeIds.bigint.id: return TypeIds.bigint; case TypeIds.boolean.id: return TypeIds.boolean; case TypeIds.string.id: return TypeIds.string; default: toss("Invalid type ID:",tid); } }; /** Returns an array of the deserialized state stored by the most recent serialize() operation (from from this thread or the counterpart thread), or null if the serialization buffer is empty. If passed a truthy argument, the serialization buffer is cleared after deserialization. */ state.s11n.deserialize = function(clear=false){ ++metrics.s11n.deserialize.count; const t = performance.now(); const argc = viewU8[0]; const rc = argc ? [] : null; if(argc){ const typeIds = []; let offset = 1, i, n, v; for(i = 0; i < argc; ++i, ++offset){ typeIds.push(getTypeIdById(viewU8[offset])); } for(i = 0; i < argc; ++i){ const t = typeIds[i]; if(t.getter){ v = viewDV[t.getter](offset, state.littleEndian); offset += t.size; }else{/*String*/ n = viewDV.getInt32(offset, state.littleEndian); offset += 4; v = textDecoder.decode(viewU8.slice(offset, offset+n)); offset += n; } rc.push(v); } } if(clear) viewU8[0] = 0; //log("deserialize:",argc, rc); metrics.s11n.deserialize.time += performance.now() - t; return rc; }; /** Serializes all arguments to the shared buffer for consumption by the counterpart thread. This routine is only intended for serializing OPFS VFS arguments and (in at least one special case) result values, and the buffer is sized to be able to comfortably handle those. If passed no arguments then it zeroes out the serialization state. */ state.s11n.serialize = function(...args){ const t = performance.now(); ++metrics.s11n.serialize.count; if(args.length){ //log("serialize():",args); const typeIds = []; let i = 0, offset = 1; viewU8[0] = args.length & 0xff /* header = # of args */; for(; i < args.length; ++i, ++offset){ /* Write the TypeIds.id value into the next args.length bytes. */ typeIds.push(getTypeId(args[i])); viewU8[offset] = typeIds[i].id; } for(i = 0; i < args.length; ++i) { /* Deserialize the following bytes based on their corresponding TypeIds.id from the header. */ const t = typeIds[i]; if(t.setter){ viewDV[t.setter](offset, args[i], state.littleEndian); offset += t.size; }else{/*String*/ const s = textEncoder.encode(args[i]); viewDV.setInt32(offset, s.byteLength, state.littleEndian); offset += 4; viewU8.set(s, offset); offset += s.byteLength; } } //log("serialize() result:",viewU8.slice(0,offset)); }else{ viewU8[0] = 0; } metrics.s11n.serialize.time += performance.now() - t; }; return state.s11n; }/*initS11n()*/; /** Generates a random ASCII string len characters long, intended for use as a temporary file name. */ const randomFilename = function f(len=16){ if(!f._chars){ f._chars = "abcdefghijklmnopqrstuvwxyz"+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+ "012346789"; f._n = f._chars.length; } const a = []; let i = 0; for( ; i < len; ++i){ const ndx = Math.random() * (f._n * 64) % f._n | 0; a[i] = f._chars[ndx]; } return a.join(""); /* An alternative impl. with an unpredictable length but much simpler: Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(36) */ }; /** Map of sqlite3_file pointers to objects constructed by xOpen(). */ const __openFiles = Object.create(null); const opTimer = Object.create(null); opTimer.op = undefined; opTimer.start = undefined; const mTimeStart = (op)=>{ opTimer.start = performance.now(); opTimer.op = op; ++metrics[op].count; }; const mTimeEnd = ()=>( metrics[opTimer.op].time += performance.now() - opTimer.start ); /** Impls for the sqlite3_io_methods methods. Maintenance reminder: members are in alphabetical order to simplify finding them. */ const ioSyncWrappers = { xCheckReservedLock: function(pFile,pOut){ /** As of late 2022, only a single lock can be held on an OPFS file. We have no way of checking whether any _other_ db connection has a lock except by trying to obtain and (on success) release a sync-handle for it, but doing so would involve an inherent race condition. For the time being, pending a better solution, we simply report whether the given pFile is open. */ const f = __openFiles[pFile]; wasm.poke(pOut, f.lockType ? 1 : 0, 'i32'); return 0; }, xClose: function(pFile){ mTimeStart('xClose'); let rc = 0; const f = __openFiles[pFile]; if(f){ delete __openFiles[pFile]; rc = opRun('xClose', pFile); if(f.sq3File) f.sq3File.dispose(); } mTimeEnd(); return rc; }, xDeviceCharacteristics: function(pFile){ //debug("xDeviceCharacteristics(",pFile,")"); return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN; }, xFileControl: function(pFile, opId, pArg){ mTimeStart('xFileControl'); const rc = (capi.SQLITE_FCNTL_SYNC===opId) ? opRun('xSync', pFile, 0) : capi.SQLITE_NOTFOUND; mTimeEnd(); return rc; }, xFileSize: function(pFile,pSz64){ mTimeStart('xFileSize'); let rc = opRun('xFileSize', pFile); if(0==rc){ try { const sz = state.s11n.deserialize()[0]; wasm.poke(pSz64, sz, 'i64'); }catch(e){ error("Unexpected error reading xFileSize() result:",e); rc = state.sq3Codes.SQLITE_IOERR; } } mTimeEnd(); return rc; }, xLock: function(pFile,lockType){ mTimeStart('xLock'); const f = __openFiles[pFile]; let rc = 0; /* All OPFS locks are exclusive locks. If xLock() has previously succeeded, do nothing except record the lock type. If no lock is active, have the async counterpart lock the file. */ if( !f.lockType ) { rc = opRun('xLock', pFile, lockType); if( 0===rc ) f.lockType = lockType; }else{ f.lockType = lockType; } mTimeEnd(); return rc; }, xRead: function(pFile,pDest,n,offset64){ mTimeStart('xRead'); const f = __openFiles[pFile]; let rc; try { rc = opRun('xRead',pFile, n, Number(offset64)); if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){ /** Results get written to the SharedArrayBuffer f.sabView. Because the heap is _not_ a SharedArrayBuffer, we have to copy the results. TypedArray.set() seems to be the fastest way to copy this. */ wasm.heap8u().set(f.sabView.subarray(0, n), pDest); } }catch(e){ error("xRead(",arguments,") failed:",e,f); rc = capi.SQLITE_IOERR_READ; } mTimeEnd(); return rc; }, xSync: function(pFile,flags){ ++metrics.xSync.count; return 0; // impl'd in xFileControl() }, xTruncate: function(pFile,sz64){ mTimeStart('xTruncate'); const rc = opRun('xTruncate', pFile, Number(sz64)); mTimeEnd(); return rc; }, xUnlock: function(pFile,lockType){ mTimeStart('xUnlock'); const f = __openFiles[pFile]; let rc = 0; if( capi.SQLITE_LOCK_NONE === lockType && f.lockType ){ rc = opRun('xUnlock', pFile, lockType); } if( 0===rc ) f.lockType = lockType; mTimeEnd(); return rc; }, xWrite: function(pFile,pSrc,n,offset64){ mTimeStart('xWrite'); const f = __openFiles[pFile]; let rc; try { f.sabView.set(wasm.heap8u().subarray(pSrc, pSrc+n)); rc = opRun('xWrite', pFile, n, Number(offset64)); }catch(e){ error("xWrite(",arguments,") failed:",e,f); rc = capi.SQLITE_IOERR_WRITE; } mTimeEnd(); return rc; } }/*ioSyncWrappers*/; /** Impls for the sqlite3_vfs methods. Maintenance reminder: members are in alphabetical order to simplify finding them. */ const vfsSyncWrappers = { xAccess: function(pVfs,zName,flags,pOut){ mTimeStart('xAccess'); const rc = opRun('xAccess', wasm.cstrToJs(zName)); wasm.poke( pOut, (rc ? 0 : 1), 'i32' ); mTimeEnd(); return 0; }, xCurrentTime: function(pVfs,pOut){ /* If it turns out that we need to adjust for timezone, see: https://stackoverflow.com/a/11760121/1458521 */ wasm.poke(pOut, 2440587.5 + (new Date().getTime()/86400000), 'double'); return 0; }, xCurrentTimeInt64: function(pVfs,pOut){ // TODO: confirm that this calculation is correct wasm.poke(pOut, (2440587.5 * 86400000) + new Date().getTime(), 'i64'); return 0; }, xDelete: function(pVfs, zName, doSyncDir){ mTimeStart('xDelete'); opRun('xDelete', wasm.cstrToJs(zName), doSyncDir, false); /* We're ignoring errors because we cannot yet differentiate between harmless and non-harmless failures. */ mTimeEnd(); return 0; }, xFullPathname: function(pVfs,zName,nOut,pOut){ /* Until/unless we have some notion of "current dir" in OPFS, simply copy zName to pOut... */ const i = wasm.cstrncpy(pOut, zName, nOut); return i<nOut ? 0 : capi.SQLITE_CANTOPEN /*CANTOPEN is required by the docs but SQLITE_RANGE would be a closer match*/; }, xGetLastError: function(pVfs,nOut,pOut){ /* TODO: store exception.message values from the async partner in a dedicated SharedArrayBuffer, noting that we'd have to encode them... TextEncoder can do that for us. */ warn("OPFS xGetLastError() has nothing sensible to return."); return 0; }, //xSleep is optionally defined below xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){ mTimeStart('xOpen'); let opfsFlags = 0; if(0===zName){ zName = randomFilename(); }else if('number'===typeof zName){ if(capi.sqlite3_uri_boolean(zName, "opfs-unlock-asap", 0)){ /* -----------------------^^^^^ MUST pass the untranslated C-string here. */ opfsFlags |= state.opfsFlags.OPFS_UNLOCK_ASAP; } zName = wasm.cstrToJs(zName); } const fh = Object.create(null); fh.fid = pFile; fh.filename = zName; fh.sab = new SharedArrayBuffer(state.fileBufferSize); fh.flags = flags; const rc = opRun('xOpen', pFile, zName, flags, opfsFlags); if(!rc){ /* Recall that sqlite3_vfs::xClose() will be called, even on error, unless pFile->pMethods is NULL. */ if(fh.readOnly){ wasm.poke(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32'); } __openFiles[pFile] = fh; fh.sabView = state.sabFileBufView; fh.sq3File = new sqlite3_file(pFile); fh.sq3File.$pMethods = opfsIoMethods.pointer; fh.lockType = capi.SQLITE_LOCK_NONE; } mTimeEnd(); return rc; }/*xOpen()*/ }/*vfsSyncWrappers*/; if(dVfs){ opfsVfs.$xRandomness = dVfs.$xRandomness; opfsVfs.$xSleep = dVfs.$xSleep; } if(!opfsVfs.$xRandomness){ /* If the default VFS has no xRandomness(), add a basic JS impl... */ vfsSyncWrappers.xRandomness = function(pVfs, nOut, pOut){ const heap = wasm.heap8u(); let i = 0; for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF; return i; }; } if(!opfsVfs.$xSleep){ /* If we can inherit an xSleep() impl from the default VFS then assume it's sane and use it, otherwise install a JS-based one. */ vfsSyncWrappers.xSleep = function(pVfs,ms){ Atomics.wait(state.sabOPView, state.opIds.xSleep, 0, ms); return 0; }; } /** Expects an OPFS file path. It gets resolved, such that ".." components are properly expanded, and returned. If the 2nd arg is true, the result is returned as an array of path elements, else an absolute path string is returned. */ opfsUtil.getResolvedPath = function(filename,splitIt){ const p = new URL(filename, "file://irrelevant").pathname; return splitIt ? p.split('/').filter((v)=>!!v) : p; }; /** Takes the absolute path to a filesystem element. Returns an array of [handleOfContainingDir, filename]. If the 2nd argument is truthy then each directory element leading to the file is created along the way. Throws if any creation or resolution fails. */ opfsUtil.getDirForFilename = async function f(absFilename, createDirs = false){ const path = opfsUtil.getResolvedPath(absFilename, true); const filename = path.pop(); let dh = opfsUtil.rootDirectory; for(const dirName of path){ if(dirName){ dh = await dh.getDirectoryHandle(dirName, {create: !!createDirs}); } } return [dh, filename]; }; /** Creates the given directory name, recursively, in the OPFS filesystem. Returns true if it succeeds or the directory already exists, else false. */ opfsUtil.mkdir = async function(absDirName){ try { await opfsUtil.getDirForFilename(absDirName+"/filepart", true); return true; }catch(e){ //console.warn("mkdir(",absDirName,") failed:",e); return false; } }; /** Checks whether the given OPFS filesystem entry exists, returning true if it does, false if it doesn't. */ opfsUtil.entryExists = async function(fsEntryName){ try { const [dh, fn] = await opfsUtil.getDirForFilename(fsEntryName); await dh.getFileHandle(fn); return true; }catch(e){ return false; } }; /** Generates a random ASCII string, intended for use as a temporary file name. Its argument is the length of the string, defaulting to 16. */ opfsUtil.randomFilename = randomFilename; /** Re-registers the OPFS VFS. This is intended only for odd use cases which have to call sqlite3_shutdown() as part of their initialization process, which will unregister the VFS registered by installOpfsVfs(). If passed a truthy value, the OPFS VFS is registered as the default VFS, else it is not made the default. Returns the result of the the sqlite3_vfs_register() call. Design note: the problem of having to re-register things after a shutdown/initialize pair is more general. How to best plug that in to the library is unclear. In particular, we cannot hook in to any C-side calls to sqlite3_initialize(), so we cannot add an after-initialize callback mechanism. */ opfsUtil.registerVfs = (asDefault=false)=>{ return wasm.exports.sqlite3_vfs_register( opfsVfs.pointer, asDefault ? 1 : 0 ); }; /** Returns a promise which resolves to an object which represents all files and directories in the OPFS tree. The top-most object has two properties: `dirs` is an array of directory entries (described below) and `files` is a list of file names for all files in that directory. Traversal starts at sqlite3.opfs.rootDirectory. Each `dirs` entry is an object in this form: ``` { name: directoryName, dirs: [...subdirs], files: [...file names] } ``` The `files` and `subdirs` entries are always set but may be empty arrays. The returned object has the same structure but its `name` is an empty string. All returned objects are created with Object.create(null), so have no prototype. Design note: the entries do not contain more information, e.g. file sizes, because getting such info is not only expensive but is subject to locking-related errors. */ opfsUtil.treeList = async function(){ const doDir = async function callee(dirHandle,tgt){ tgt.name = dirHandle.name; tgt.dirs = []; tgt.files = []; for await (const handle of dirHandle.values()){ if('directory' === handle.kind){ const subDir = Object.create(null); tgt.dirs.push(subDir); await callee(handle, subDir); }else{ tgt.files.push(handle.name); } } }; const root = Object.create(null); await doDir(opfsUtil.rootDirectory, root); return root; }; /** Irrevocably deletes _all_ files in the current origin's OPFS. Obviously, this must be used with great caution. It may throw an exception if removal of anything fails (e.g. a file is locked), but the precise conditions under which the underlying APIs will throw are not documented (so we cannot tell you what they are). */ opfsUtil.rmfr = async function(){ const dir = opfsUtil.rootDirectory, opt = {recurse: true}; for await (const handle of dir.values()){ dir.removeEntry(handle.name, opt); } }; /** Deletes the given OPFS filesystem entry. As this environment has no notion of "current directory", the given name must be an absolute path. If the 2nd argument is truthy, deletion is recursive (use with caution!). The returned Promise resolves to true if the deletion was successful, else false (but...). The OPFS API reports the reason for the failure only in human-readable form, not exceptions which can be type-checked to determine the failure. Because of that... If the final argument is truthy then this function will propagate any exception on error, rather than returning false. */ opfsUtil.unlink = async function(fsEntryName, recursive = false, throwOnError = false){ try { const [hDir, filenamePart] = await opfsUtil.getDirForFilename(fsEntryName, false); await hDir.removeEntry(filenamePart, {recursive}); return true; }catch(e){ if(throwOnError){ throw new Error("unlink(",arguments[0],") failed: "+e.message,{ cause: e }); } return false; } }; /** Traverses the OPFS filesystem, calling a callback for each one. The argument may be either a callback function or an options object with any of the following properties: - `callback`: function which gets called for each filesystem entry. It gets passed 3 arguments: 1) the FileSystemFileHandle or FileSystemDirectoryHandle of each entry (noting that both are instanceof FileSystemHandle). 2) the FileSystemDirectoryHandle of the parent directory. 3) the current depth level, with 0 being at the top of the tree relative to the starting directory. If the callback returns a literal false, as opposed to any other falsy value, traversal stops without an error. Any exceptions it throws are propagated. Results are undefined if the callback manipulate the filesystem (e.g. removing or adding entries) because the how OPFS iterators behave in the face of such changes is undocumented. - `recursive` [bool=true]: specifies whether to recurse into subdirectories or not. Whether recursion is depth-first or breadth-first is unspecified! - `directory` [FileSystemDirectoryEntry=sqlite3.opfs.rootDirectory] specifies the starting directory. If this function is passed a function, it is assumed to be the callback. Returns a promise because it has to (by virtue of being async) but that promise has no specific meaning: the traversal it performs is synchronous. The promise must be used to catch any exceptions propagated by the callback, however. TODO: add an option which specifies whether to traverse depth-first or breadth-first. We currently do depth-first but an incremental file browsing widget would benefit more from breadth-first. */ opfsUtil.traverse = async function(opt){ const defaultOpt = { recursive: true, directory: opfsUtil.rootDirectory }; if('function'===typeof opt){ opt = {callback:opt}; } opt = Object.assign(defaultOpt, opt||{}); const doDir = async function callee(dirHandle, depth){ for await (const handle of dirHandle.values()){ if(false === opt.callback(handle, dirHandle, depth)) return false; else if(opt.recursive && 'directory' === handle.kind){ if(false === await callee(handle, depth + 1)) break; } } }; doDir(opt.directory, 0); }; //TODO to support fiddle and worker1 db upload: //opfsUtil.createFile = function(absName, content=undefined){...} //We have sqlite3.wasm.sqlite3_wasm_vfs_create_file() for this //purpose but its interface and name are still under //consideration. if(sqlite3.oo1){ const OpfsDb = function(...args){ const opt = sqlite3.oo1.DB.dbCtorHelper.normalizeArgs(...args); opt.vfs = opfsVfs.$zName; sqlite3.oo1.DB.dbCtorHelper.call(this, opt); }; OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype); sqlite3.oo1.OpfsDb = OpfsDb; sqlite3.oo1.DB.dbCtorHelper.setVfsPostOpenSql( opfsVfs.pointer, function(oo1Db, sqlite3){ /* Set a relatively high default busy-timeout handler to help OPFS dbs deal with multi-tab/multi-worker contention. */ sqlite3.capi.sqlite3_busy_timeout(oo1Db, 10000); sqlite3.capi.sqlite3_exec(oo1Db, [ /* Truncate journal mode is faster than delete for this vfs, per speedtest1. That gap seems to have closed with Chrome version 108 or 109, but "persist" is very roughly 5-6% faster than truncate in initial tests. */ "pragma journal_mode=persist;", /* This vfs benefits hugely from cache on moderate/large speedtest1 --size 50 and --size 100 workloads. We currently rely on setting a non-default cache size when building sqlite3.wasm. If that policy changes, the cache can be set here. */ "pragma cache_size=-16384;" ], 0, 0, 0); } ); }/*extend sqlite3.oo1*/ const sanityCheck = function(){ const scope = wasm.scopedAllocPush(); const sq3File = new sqlite3_file(); try{ const fid = sq3File.pointer; const openFlags = capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE //| capi.SQLITE_OPEN_DELETEONCLOSE | capi.SQLITE_OPEN_MAIN_DB; const pOut = wasm.scopedAlloc(8); const dbFile = "/sanity/check/file"+randomFilename(8); const zDbFile = wasm.scopedAllocCString(dbFile); let rc; state.s11n.serialize("This is ä string."); rc = state.s11n.deserialize(); log("deserialize() says:",rc); if("This is ä string."!==rc[0]) toss("String d13n error."); vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut); rc = wasm.peek(pOut,'i32'); log("xAccess(",dbFile,") exists ?=",rc); rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, zDbFile, fid, openFlags, pOut); log("open rc =",rc,"state.sabOPView[xOpen] =", state.sabOPView[state.opIds.xOpen]); if(0!==rc){ error("open failed with code",rc); return; } vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut); rc = wasm.peek(pOut,'i32'); if(!rc) toss("xAccess() failed to detect file."); rc = ioSyncWrappers.xSync(sq3File.pointer, 0); if(rc) toss('sync failed w/ rc',rc); rc = ioSyncWrappers.xTruncate(sq3File.pointer, 1024); if(rc) toss('truncate failed w/ rc',rc); wasm.poke(pOut,0,'i64'); rc = ioSyncWrappers.xFileSize(sq3File.pointer, pOut); if(rc) toss('xFileSize failed w/ rc',rc); log("xFileSize says:",wasm.peek(pOut, 'i64')); rc = ioSyncWrappers.xWrite(sq3File.pointer, zDbFile, 10, 1); if(rc) toss("xWrite() failed!"); const readBuf = wasm.scopedAlloc(16); rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2); wasm.poke(readBuf+6,0); let jRead = wasm.cstrToJs(readBuf); log("xRead() got:",jRead); if("sanity"!==jRead) toss("Unexpected xRead() value."); if(vfsSyncWrappers.xSleep){ log("xSleep()ing before close()ing..."); vfsSyncWrappers.xSleep(opfsVfs.pointer,2000); log("waking up from xSleep()"); } rc = ioSyncWrappers.xClose(fid); log("xClose rc =",rc,"sabOPView =",state.sabOPView); log("Deleting file:",dbFile); vfsSyncWrappers.xDelete(opfsVfs.pointer, zDbFile, 0x1234); vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut); rc = wasm.peek(pOut,'i32'); if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete()."); warn("End of OPFS sanity checks."); }finally{ sq3File.dispose(); wasm.scopedAllocPop(scope); } }/*sanityCheck()*/; W.onmessage = function({data}){ //log("Worker.onmessage:",data); switch(data.type){ case 'opfs-unavailable': /* Async proxy has determined that OPFS is unavailable. There's nothing more for us to do here. */ promiseReject(new Error(data.payload.join(' '))); break; case 'opfs-async-loaded': /*Arrives as soon as the asyc proxy finishes loading. Pass our config and shared state on to the async worker.*/ W.postMessage({type: 'opfs-async-init',args: state}); break; case 'opfs-async-inited':{ /*Indicates that the async partner has received the 'init' and has finished initializing, so the real work can begin...*/ try { sqlite3.vfs.installVfs({ io: {struct: opfsIoMethods, methods: ioSyncWrappers}, vfs: {struct: opfsVfs, methods: vfsSyncWrappers} }); state.sabOPView = new Int32Array(state.sabOP); state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize); state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize); initS11n(); if(options.sanityChecks){ warn("Running sanity checks because of opfs-sanity-check URL arg..."); sanityCheck(); } if(thisThreadHasOPFS()){ navigator.storage.getDirectory().then((d)=>{ W.onerror = W._originalOnError; delete W._originalOnError; sqlite3.opfs = opfsUtil; opfsUtil.rootDirectory = d; log("End of OPFS sqlite3_vfs setup.", opfsVfs); promiseResolve(sqlite3); }).catch(promiseReject); }else{ promiseResolve(sqlite3); } }catch(e){ error(e); promiseReject(e); } break; } default: promiseReject(e); error("Unexpected message from the async worker:",data); break; }/*switch(data.type)*/ }/*W.onmessage()*/; })/*thePromise*/; return thePromise; }/*installOpfsVfs()*/; installOpfsVfs.defaultProxyUri = "sqlite3-opfs-async-proxy.js"; self.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{ try{ let proxyJs = installOpfsVfs.defaultProxyUri; if(sqlite3.scriptInfo.sqlite3Dir){ installOpfsVfs.defaultProxyUri = sqlite3.scriptInfo.sqlite3Dir + proxyJs; //console.warn("installOpfsVfs.defaultProxyUri =",installOpfsVfs.defaultProxyUri); } return installOpfsVfs().catch((e)=>{ console.warn("Ignoring inability to install OPFS sqlite3_vfs:",e.message); }); }catch(e){ console.error("installOpfsVfs() exception:",e); throw e; } }); }/*sqlite3ApiBootstrap.initializers.push()*/); |
Changes to ext/wasm/api/sqlite3-wasm.c.
︙ | ︙ | |||
28 29 30 31 32 33 34 | /* ** Threading and file locking: JS is single-threaded. Each Worker ** thread is a separate instance of the JS engine so can never access ** the same db handle as another thread, thus multi-threading support ** is unnecessary in the library. Because the filesystems are virtual ** and local to a given wasm runtime instance, two Workers can never | | < < < < < | | | | | 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | /* ** Threading and file locking: JS is single-threaded. Each Worker ** thread is a separate instance of the JS engine so can never access ** the same db handle as another thread, thus multi-threading support ** is unnecessary in the library. Because the filesystems are virtual ** and local to a given wasm runtime instance, two Workers can never ** access the same db file at once, with the exception of OPFS. ** ** Summary: except for the case of OPFS, which supports locking using ** its own API, threading and file locking support are unnecessary in ** the wasm build. */ /* ** Undefine any SQLITE_... config flags which we specifically do not ** want defined. Please keep these alphabetized. */ #undef SQLITE_OMIT_DESERIALIZE #undef SQLITE_OMIT_MEMORYDB /* ** Define any SQLITE_... config defaults we want if they aren't ** overridden by the builder. Please keep these alphabetized. |
︙ | ︙ | |||
65 66 67 68 69 70 71 | ** higher. On smaller workloads, e.g. speedtest1 --size 25, they ** clearly benefit from having 4mb of cache, but not as much as a ** larger cache benefits the larger workloads. Speed differences ** between 2x and nearly 3x have been measured with ample page cache. */ # define SQLITE_DEFAULT_CACHE_SIZE -16384 #endif | | | > > > > > > > > | | 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | ** higher. On smaller workloads, e.g. speedtest1 --size 25, they ** clearly benefit from having 4mb of cache, but not as much as a ** larger cache benefits the larger workloads. Speed differences ** between 2x and nearly 3x have been measured with ample page cache. */ # define SQLITE_DEFAULT_CACHE_SIZE -16384 #endif #if !defined(SQLITE_DEFAULT_PAGE_SIZE) /* ** OPFS performance is improved by approx. 12% with a page size of 8kb ** instead of 4kb. Performance with 16kb is equivalent to 8kb. ** ** Performance difference of kvvfs with a page size of 8kb compared to ** 4kb, as measured by speedtest1 --size 4, is indeterminate: ** measurements are all over the place either way and not ** significantly different. */ # define SQLITE_DEFAULT_PAGE_SIZE 8192 #endif #ifndef SQLITE_DEFAULT_UNIX_VFS # define SQLITE_DEFAULT_UNIX_VFS "unix-none" #endif #undef SQLITE_DQS #define SQLITE_DQS 0 |
︙ | ︙ | |||
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | #endif #ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS # define SQLITE_ENABLE_EXPLAIN_COMMENTS 1 #endif #ifndef SQLITE_ENABLE_FTS4 # define SQLITE_ENABLE_FTS4 1 #endif #ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC # define SQLITE_ENABLE_OFFSET_SQL_FUNC 1 #endif #ifndef SQLITE_ENABLE_RTREE # define SQLITE_ENABLE_RTREE 1 #endif #ifndef SQLITE_ENABLE_STMTVTAB # define SQLITE_ENABLE_STMTVTAB 1 #endif #ifndef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION # define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION #endif /**********************************************************************/ /* SQLITE_O... */ #ifndef SQLITE_OMIT_DEPRECATED # define SQLITE_OMIT_DEPRECATED 1 #endif #ifndef SQLITE_OMIT_LOAD_EXTENSION | > > > > > > > > > > > > > > > | 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | #endif #ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS # define SQLITE_ENABLE_EXPLAIN_COMMENTS 1 #endif #ifndef SQLITE_ENABLE_FTS4 # define SQLITE_ENABLE_FTS4 1 #endif #ifndef SQLITE_ENABLE_MATH_FUNCTIONS # define SQLITE_ENABLE_MATH_FUNCTIONS 1 #endif #ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC # define SQLITE_ENABLE_OFFSET_SQL_FUNC 1 #endif #ifndef SQLITE_ENABLE_PREUPDATE_HOOK # define SQLITE_ENABLE_PREUPDATE_HOOK 1 /*required by session extension*/ #endif #ifndef SQLITE_ENABLE_RTREE # define SQLITE_ENABLE_RTREE 1 #endif #ifndef SQLITE_ENABLE_SESSION # define SQLITE_ENABLE_SESSION 1 #endif #ifndef SQLITE_ENABLE_STMTVTAB # define SQLITE_ENABLE_STMTVTAB 1 #endif #ifndef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION # define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION #endif /**********************************************************************/ /* SQLITE_M... */ #ifndef SQLITE_MAX_ALLOCATION_SIZE # define SQLITE_MAX_ALLOCATION_SIZE 0x1fffffff #endif /**********************************************************************/ /* SQLITE_O... */ #ifndef SQLITE_OMIT_DEPRECATED # define SQLITE_OMIT_DEPRECATED 1 #endif #ifndef SQLITE_OMIT_LOAD_EXTENSION |
︙ | ︙ | |||
307 308 309 310 311 312 313 | ** Wasm bindings such as sqlite3_prepare_v2/v3(), and definitely not ** from client code. ** ** Returns err_code. */ SQLITE_WASM_KEEP int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){ | > | | | | | > | 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 | ** Wasm bindings such as sqlite3_prepare_v2/v3(), and definitely not ** from client code. ** ** Returns err_code. */ SQLITE_WASM_KEEP int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){ if( db!=0 ){ if( 0!=zMsg ){ const int nMsg = sqlite3Strlen30(zMsg); sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg); }else{ sqlite3ErrorWithMsg(db, err_code, NULL); } } return err_code; } #if SQLITE_WASM_TESTS struct WasmTestStruct { int v4; |
︙ | ︙ | |||
355 356 357 358 359 360 361 | ** ** If this function returns NULL then it means that the internal ** buffer is not large enough for the generated JSON and needs to be ** increased. In debug builds that will trigger an assert(). */ SQLITE_WASM_KEEP const char * sqlite3_wasm_enum_json(void){ | | | | 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 | ** ** If this function returns NULL then it means that the internal ** buffer is not large enough for the generated JSON and needs to be ** increased. In debug builds that will trigger an assert(). */ SQLITE_WASM_KEEP const char * sqlite3_wasm_enum_json(void){ static char aBuffer[1024 * 20] = {0} /* where the JSON goes */; int n = 0, nChildren = 0, nStruct = 0 /* output counters for figuring out where commas go */; char * zPos = &aBuffer[1] /* skip first byte for now to help protect ** against a small race condition */; char const * const zEnd = &aBuffer[0] + sizeof(aBuffer) /* one-past-the-end */; if(aBuffer[0]) return aBuffer; /* Leave aBuffer[0] at 0 until the end to help guard against a tiny ** race condition. If this is called twice concurrently, they might ** end up both writing to aBuffer, but they'll both write the same ** thing, so that's okay. If we set byte 0 up front then the 2nd ** instance might return and use the string before the 1st instance |
︙ | ︙ | |||
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 | /* The following groups are sorted alphabetic by group name. */ DefGroup(access){ DefInt(SQLITE_ACCESS_EXISTS); DefInt(SQLITE_ACCESS_READWRITE); DefInt(SQLITE_ACCESS_READ)/*docs say this is unused*/; } _DefGroup; DefGroup(blobFinalizers) { /* SQLITE_STATIC/TRANSIENT need to be handled explicitly as ** integers to avoid casting-related warnings. */ out("\"SQLITE_STATIC\":0, \"SQLITE_TRANSIENT\":-1"); } _DefGroup; DefGroup(dataTypes) { DefInt(SQLITE_INTEGER); DefInt(SQLITE_FLOAT); DefInt(SQLITE_TEXT); DefInt(SQLITE_BLOB); DefInt(SQLITE_NULL); } _DefGroup; DefGroup(encodings) { /* Noting that the wasm binding only aims to support UTF-8. */ DefInt(SQLITE_UTF8); DefInt(SQLITE_UTF16LE); DefInt(SQLITE_UTF16BE); DefInt(SQLITE_UTF16); | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 | /* The following groups are sorted alphabetic by group name. */ DefGroup(access){ DefInt(SQLITE_ACCESS_EXISTS); DefInt(SQLITE_ACCESS_READWRITE); DefInt(SQLITE_ACCESS_READ)/*docs say this is unused*/; } _DefGroup; DefGroup(authorizer){ DefInt(SQLITE_DENY); DefInt(SQLITE_IGNORE); DefInt(SQLITE_CREATE_INDEX); DefInt(SQLITE_CREATE_TABLE); DefInt(SQLITE_CREATE_TEMP_INDEX); DefInt(SQLITE_CREATE_TEMP_TABLE); DefInt(SQLITE_CREATE_TEMP_TRIGGER); DefInt(SQLITE_CREATE_TEMP_VIEW); DefInt(SQLITE_CREATE_TRIGGER); DefInt(SQLITE_CREATE_VIEW); DefInt(SQLITE_DELETE); DefInt(SQLITE_DROP_INDEX); DefInt(SQLITE_DROP_TABLE); DefInt(SQLITE_DROP_TEMP_INDEX); DefInt(SQLITE_DROP_TEMP_TABLE); DefInt(SQLITE_DROP_TEMP_TRIGGER); DefInt(SQLITE_DROP_TEMP_VIEW); DefInt(SQLITE_DROP_TRIGGER); DefInt(SQLITE_DROP_VIEW); DefInt(SQLITE_INSERT); DefInt(SQLITE_PRAGMA); DefInt(SQLITE_READ); DefInt(SQLITE_SELECT); DefInt(SQLITE_TRANSACTION); DefInt(SQLITE_UPDATE); DefInt(SQLITE_ATTACH); DefInt(SQLITE_DETACH); DefInt(SQLITE_ALTER_TABLE); DefInt(SQLITE_REINDEX); DefInt(SQLITE_ANALYZE); DefInt(SQLITE_CREATE_VTABLE); DefInt(SQLITE_DROP_VTABLE); DefInt(SQLITE_FUNCTION); DefInt(SQLITE_SAVEPOINT); //DefInt(SQLITE_COPY) /* No longer used */; DefInt(SQLITE_RECURSIVE); } _DefGroup; DefGroup(blobFinalizers) { /* SQLITE_STATIC/TRANSIENT need to be handled explicitly as ** integers to avoid casting-related warnings. */ out("\"SQLITE_STATIC\":0, \"SQLITE_TRANSIENT\":-1"); } _DefGroup; DefGroup(changeset){ DefInt(SQLITE_CHANGESETSTART_INVERT); DefInt(SQLITE_CHANGESETAPPLY_NOSAVEPOINT); DefInt(SQLITE_CHANGESETAPPLY_INVERT); DefInt(SQLITE_CHANGESET_DATA); DefInt(SQLITE_CHANGESET_NOTFOUND); DefInt(SQLITE_CHANGESET_CONFLICT); DefInt(SQLITE_CHANGESET_CONSTRAINT); DefInt(SQLITE_CHANGESET_FOREIGN_KEY); DefInt(SQLITE_CHANGESET_OMIT); DefInt(SQLITE_CHANGESET_REPLACE); DefInt(SQLITE_CHANGESET_ABORT); } _DefGroup; DefGroup(config){ DefInt(SQLITE_CONFIG_SINGLETHREAD); DefInt(SQLITE_CONFIG_MULTITHREAD); DefInt(SQLITE_CONFIG_SERIALIZED); DefInt(SQLITE_CONFIG_MALLOC); DefInt(SQLITE_CONFIG_GETMALLOC); DefInt(SQLITE_CONFIG_SCRATCH); DefInt(SQLITE_CONFIG_PAGECACHE); DefInt(SQLITE_CONFIG_HEAP); DefInt(SQLITE_CONFIG_MEMSTATUS); DefInt(SQLITE_CONFIG_MUTEX); DefInt(SQLITE_CONFIG_GETMUTEX); /* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ DefInt(SQLITE_CONFIG_LOOKASIDE); DefInt(SQLITE_CONFIG_PCACHE); DefInt(SQLITE_CONFIG_GETPCACHE); DefInt(SQLITE_CONFIG_LOG); DefInt(SQLITE_CONFIG_URI); DefInt(SQLITE_CONFIG_PCACHE2); DefInt(SQLITE_CONFIG_GETPCACHE2); DefInt(SQLITE_CONFIG_COVERING_INDEX_SCAN); DefInt(SQLITE_CONFIG_SQLLOG); DefInt(SQLITE_CONFIG_MMAP_SIZE); DefInt(SQLITE_CONFIG_WIN32_HEAPSIZE); DefInt(SQLITE_CONFIG_PCACHE_HDRSZ); DefInt(SQLITE_CONFIG_PMASZ); DefInt(SQLITE_CONFIG_STMTJRNL_SPILL); DefInt(SQLITE_CONFIG_SMALL_MALLOC); DefInt(SQLITE_CONFIG_SORTERREF_SIZE); DefInt(SQLITE_CONFIG_MEMDB_MAXSIZE); } _DefGroup; DefGroup(dataTypes) { DefInt(SQLITE_INTEGER); DefInt(SQLITE_FLOAT); DefInt(SQLITE_TEXT); DefInt(SQLITE_BLOB); DefInt(SQLITE_NULL); } _DefGroup; DefGroup(dbConfig){ DefInt(SQLITE_DBCONFIG_MAINDBNAME); DefInt(SQLITE_DBCONFIG_LOOKASIDE); DefInt(SQLITE_DBCONFIG_ENABLE_FKEY); DefInt(SQLITE_DBCONFIG_ENABLE_TRIGGER); DefInt(SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER); DefInt(SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION); DefInt(SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE); DefInt(SQLITE_DBCONFIG_ENABLE_QPSG); DefInt(SQLITE_DBCONFIG_TRIGGER_EQP); DefInt(SQLITE_DBCONFIG_RESET_DATABASE); DefInt(SQLITE_DBCONFIG_DEFENSIVE); DefInt(SQLITE_DBCONFIG_WRITABLE_SCHEMA); DefInt(SQLITE_DBCONFIG_LEGACY_ALTER_TABLE); DefInt(SQLITE_DBCONFIG_DQS_DML); DefInt(SQLITE_DBCONFIG_DQS_DDL); DefInt(SQLITE_DBCONFIG_ENABLE_VIEW); DefInt(SQLITE_DBCONFIG_LEGACY_FILE_FORMAT); DefInt(SQLITE_DBCONFIG_TRUSTED_SCHEMA); DefInt(SQLITE_DBCONFIG_MAX); } _DefGroup; DefGroup(dbStatus){ DefInt(SQLITE_DBSTATUS_LOOKASIDE_USED); DefInt(SQLITE_DBSTATUS_CACHE_USED); DefInt(SQLITE_DBSTATUS_SCHEMA_USED); DefInt(SQLITE_DBSTATUS_STMT_USED); DefInt(SQLITE_DBSTATUS_LOOKASIDE_HIT); DefInt(SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE); DefInt(SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL); DefInt(SQLITE_DBSTATUS_CACHE_HIT); DefInt(SQLITE_DBSTATUS_CACHE_MISS); DefInt(SQLITE_DBSTATUS_CACHE_WRITE); DefInt(SQLITE_DBSTATUS_DEFERRED_FKS); DefInt(SQLITE_DBSTATUS_CACHE_USED_SHARED); DefInt(SQLITE_DBSTATUS_CACHE_SPILL); DefInt(SQLITE_DBSTATUS_MAX); } _DefGroup; DefGroup(encodings) { /* Noting that the wasm binding only aims to support UTF-8. */ DefInt(SQLITE_UTF8); DefInt(SQLITE_UTF16LE); DefInt(SQLITE_UTF16BE); DefInt(SQLITE_UTF16); |
︙ | ︙ | |||
489 490 491 492 493 494 495 496 497 498 499 500 501 502 | DefInt(SQLITE_IOCAP_SAFE_APPEND); DefInt(SQLITE_IOCAP_SEQUENTIAL); DefInt(SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN); DefInt(SQLITE_IOCAP_POWERSAFE_OVERWRITE); DefInt(SQLITE_IOCAP_IMMUTABLE); DefInt(SQLITE_IOCAP_BATCH_ATOMIC); } _DefGroup; DefGroup(openFlags) { /* Noting that not all of these will have any effect in ** WASM-space. */ DefInt(SQLITE_OPEN_READONLY); DefInt(SQLITE_OPEN_READWRITE); DefInt(SQLITE_OPEN_CREATE); | > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 | DefInt(SQLITE_IOCAP_SAFE_APPEND); DefInt(SQLITE_IOCAP_SEQUENTIAL); DefInt(SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN); DefInt(SQLITE_IOCAP_POWERSAFE_OVERWRITE); DefInt(SQLITE_IOCAP_IMMUTABLE); DefInt(SQLITE_IOCAP_BATCH_ATOMIC); } _DefGroup; DefGroup(limits) { DefInt(SQLITE_MAX_ALLOCATION_SIZE); DefInt(SQLITE_LIMIT_LENGTH); DefInt(SQLITE_MAX_LENGTH); DefInt(SQLITE_LIMIT_SQL_LENGTH); DefInt(SQLITE_MAX_SQL_LENGTH); DefInt(SQLITE_LIMIT_COLUMN); DefInt(SQLITE_MAX_COLUMN); DefInt(SQLITE_LIMIT_EXPR_DEPTH); DefInt(SQLITE_MAX_EXPR_DEPTH); DefInt(SQLITE_LIMIT_COMPOUND_SELECT); DefInt(SQLITE_MAX_COMPOUND_SELECT); DefInt(SQLITE_LIMIT_VDBE_OP); DefInt(SQLITE_MAX_VDBE_OP); DefInt(SQLITE_LIMIT_FUNCTION_ARG); DefInt(SQLITE_MAX_FUNCTION_ARG); DefInt(SQLITE_LIMIT_ATTACHED); DefInt(SQLITE_MAX_ATTACHED); DefInt(SQLITE_LIMIT_LIKE_PATTERN_LENGTH); DefInt(SQLITE_MAX_LIKE_PATTERN_LENGTH); DefInt(SQLITE_LIMIT_VARIABLE_NUMBER); DefInt(SQLITE_MAX_VARIABLE_NUMBER); DefInt(SQLITE_LIMIT_TRIGGER_DEPTH); DefInt(SQLITE_MAX_TRIGGER_DEPTH); DefInt(SQLITE_LIMIT_WORKER_THREADS); DefInt(SQLITE_MAX_WORKER_THREADS); } _DefGroup; DefGroup(openFlags) { /* Noting that not all of these will have any effect in ** WASM-space. */ DefInt(SQLITE_OPEN_READONLY); DefInt(SQLITE_OPEN_READWRITE); DefInt(SQLITE_OPEN_CREATE); |
︙ | ︙ | |||
640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 | DefGroup(serialize){ DefInt(SQLITE_SERIALIZE_NOCOPY); DefInt(SQLITE_DESERIALIZE_FREEONCLOSE); DefInt(SQLITE_DESERIALIZE_READONLY); DefInt(SQLITE_DESERIALIZE_RESIZEABLE); } _DefGroup; DefGroup(syncFlags) { DefInt(SQLITE_SYNC_NORMAL); DefInt(SQLITE_SYNC_FULL); DefInt(SQLITE_SYNC_DATAONLY); } _DefGroup; DefGroup(trace) { DefInt(SQLITE_TRACE_STMT); DefInt(SQLITE_TRACE_PROFILE); DefInt(SQLITE_TRACE_ROW); DefInt(SQLITE_TRACE_CLOSE); } _DefGroup; DefGroup(udfFlags) { DefInt(SQLITE_DETERMINISTIC); DefInt(SQLITE_DIRECTONLY); DefInt(SQLITE_INNOCUOUS); } _DefGroup; DefGroup(version) { DefInt(SQLITE_VERSION_NUMBER); DefStr(SQLITE_VERSION); DefStr(SQLITE_SOURCE_ID); } _DefGroup; | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 | DefGroup(serialize){ DefInt(SQLITE_SERIALIZE_NOCOPY); DefInt(SQLITE_DESERIALIZE_FREEONCLOSE); DefInt(SQLITE_DESERIALIZE_READONLY); DefInt(SQLITE_DESERIALIZE_RESIZEABLE); } _DefGroup; DefGroup(session){ DefInt(SQLITE_SESSION_CONFIG_STRMSIZE); DefInt(SQLITE_SESSION_OBJCONFIG_SIZE); } _DefGroup; DefGroup(sqlite3Status){ DefInt(SQLITE_STATUS_MEMORY_USED); DefInt(SQLITE_STATUS_PAGECACHE_USED); DefInt(SQLITE_STATUS_PAGECACHE_OVERFLOW); //DefInt(SQLITE_STATUS_SCRATCH_USED) /* NOT USED */; //DefInt(SQLITE_STATUS_SCRATCH_OVERFLOW) /* NOT USED */; DefInt(SQLITE_STATUS_MALLOC_SIZE); DefInt(SQLITE_STATUS_PARSER_STACK); DefInt(SQLITE_STATUS_PAGECACHE_SIZE); //DefInt(SQLITE_STATUS_SCRATCH_SIZE) /* NOT USED */; DefInt(SQLITE_STATUS_MALLOC_COUNT); } _DefGroup; DefGroup(stmtStatus){ DefInt(SQLITE_STMTSTATUS_FULLSCAN_STEP); DefInt(SQLITE_STMTSTATUS_SORT); DefInt(SQLITE_STMTSTATUS_AUTOINDEX); DefInt(SQLITE_STMTSTATUS_VM_STEP); DefInt(SQLITE_STMTSTATUS_REPREPARE); DefInt(SQLITE_STMTSTATUS_RUN); DefInt(SQLITE_STMTSTATUS_FILTER_MISS); DefInt(SQLITE_STMTSTATUS_FILTER_HIT); DefInt(SQLITE_STMTSTATUS_MEMUSED); } _DefGroup; DefGroup(syncFlags) { DefInt(SQLITE_SYNC_NORMAL); DefInt(SQLITE_SYNC_FULL); DefInt(SQLITE_SYNC_DATAONLY); } _DefGroup; DefGroup(trace) { DefInt(SQLITE_TRACE_STMT); DefInt(SQLITE_TRACE_PROFILE); DefInt(SQLITE_TRACE_ROW); DefInt(SQLITE_TRACE_CLOSE); } _DefGroup; DefGroup(txnState){ DefInt(SQLITE_TXN_NONE); DefInt(SQLITE_TXN_READ); DefInt(SQLITE_TXN_WRITE); } _DefGroup; DefGroup(udfFlags) { DefInt(SQLITE_DETERMINISTIC); DefInt(SQLITE_DIRECTONLY); DefInt(SQLITE_INNOCUOUS); } _DefGroup; DefGroup(version) { DefInt(SQLITE_VERSION_NUMBER); DefStr(SQLITE_VERSION); DefStr(SQLITE_SOURCE_ID); } _DefGroup; DefGroup(vtab) { DefInt(SQLITE_INDEX_SCAN_UNIQUE); DefInt(SQLITE_INDEX_CONSTRAINT_EQ); DefInt(SQLITE_INDEX_CONSTRAINT_GT); DefInt(SQLITE_INDEX_CONSTRAINT_LE); DefInt(SQLITE_INDEX_CONSTRAINT_LT); DefInt(SQLITE_INDEX_CONSTRAINT_GE); DefInt(SQLITE_INDEX_CONSTRAINT_MATCH); DefInt(SQLITE_INDEX_CONSTRAINT_LIKE); DefInt(SQLITE_INDEX_CONSTRAINT_GLOB); DefInt(SQLITE_INDEX_CONSTRAINT_REGEXP); DefInt(SQLITE_INDEX_CONSTRAINT_NE); DefInt(SQLITE_INDEX_CONSTRAINT_ISNOT); DefInt(SQLITE_INDEX_CONSTRAINT_ISNOTNULL); DefInt(SQLITE_INDEX_CONSTRAINT_ISNULL); DefInt(SQLITE_INDEX_CONSTRAINT_IS); DefInt(SQLITE_INDEX_CONSTRAINT_LIMIT); DefInt(SQLITE_INDEX_CONSTRAINT_OFFSET); DefInt(SQLITE_INDEX_CONSTRAINT_FUNCTION); DefInt(SQLITE_VTAB_CONSTRAINT_SUPPORT); DefInt(SQLITE_VTAB_INNOCUOUS); DefInt(SQLITE_VTAB_DIRECTONLY); DefInt(SQLITE_ROLLBACK); //DefInt(SQLITE_IGNORE); // Also used by sqlite3_authorizer() callback DefInt(SQLITE_FAIL); //DefInt(SQLITE_ABORT); // Also an error code DefInt(SQLITE_REPLACE); } _DefGroup; #undef DefGroup #undef DefStr #undef DefInt #undef _DefGroup /* ** Emit an array of "StructBinder" struct descripions, which look |
︙ | ︙ | |||
691 692 693 694 695 696 697 | ** Detailed documentation for those bits are in the docs for the ** Jaccwabyt JS-side component. */ /** Macros for emitting StructBinder description. */ #define StructBinder__(TYPE) \ n = 0; \ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 | ** Detailed documentation for those bits are in the docs for the ** Jaccwabyt JS-side component. */ /** Macros for emitting StructBinder description. */ #define StructBinder__(TYPE) \ n = 0; \ outf("%s{", (nStruct++ ? ", " : "")); \ out("\"name\": \"" # TYPE "\","); \ outf("\"sizeof\": %d", (int)sizeof(TYPE)); \ out(",\"members\": {"); #define StructBinder_(T) StructBinder__(T) /** ^^^ indirection needed to expand CurrentStruct */ #define StructBinder StructBinder_(CurrentStruct) #define _StructBinder CloseBrace(2) #define M(MEMBER,SIG) \ outf("%s\"%s\": " \ "{\"offset\":%d,\"sizeof\": %d,\"signature\":\"%s\"}", \ (n++ ? ", " : ""), #MEMBER, \ (int)offsetof(CurrentStruct,MEMBER), \ (int)sizeof(((CurrentStruct*)0)->MEMBER), \ SIG) nStruct = 0; out(", \"structs\": ["); { #define CurrentStruct sqlite3_vfs StructBinder { M(iVersion, "i"); M(szOsFile, "i"); M(mxPathname, "i"); M(pNext, "p"); M(zName, "s"); M(pAppData, "p"); M(xOpen, "i(pppip)"); M(xDelete, "i(ppi)"); M(xAccess, "i(ppip)"); M(xFullPathname, "i(ppip)"); M(xDlOpen, "p(pp)"); M(xDlError, "p(pip)"); M(xDlSym, "p()"); M(xDlClose, "v(pp)"); M(xRandomness, "i(pip)"); M(xSleep, "i(pi)"); M(xCurrentTime, "i(pp)"); M(xGetLastError, "i(pip)"); M(xCurrentTimeInt64, "i(pp)"); M(xSetSystemCall, "i(ppp)"); M(xGetSystemCall, "p(pp)"); M(xNextSystemCall, "p(pp)"); } _StructBinder; #undef CurrentStruct #define CurrentStruct sqlite3_io_methods StructBinder { M(iVersion, "i"); M(xClose, "i(p)"); M(xRead, "i(ppij)"); M(xWrite, "i(ppij)"); M(xTruncate, "i(pj)"); M(xSync, "i(pi)"); M(xFileSize, "i(pp)"); M(xLock, "i(pi)"); M(xUnlock, "i(pi)"); M(xCheckReservedLock, "i(pp)"); M(xFileControl, "i(pip)"); M(xSectorSize, "i(p)"); M(xDeviceCharacteristics, "i(p)"); M(xShmMap, "i(piiip)"); M(xShmLock, "i(piii)"); M(xShmBarrier, "v(p)"); M(xShmUnmap, "i(pi)"); M(xFetch, "i(pjip)"); M(xUnfetch, "i(pjp)"); } _StructBinder; #undef CurrentStruct #define CurrentStruct sqlite3_file StructBinder { M(pMethods, "p"); } _StructBinder; #undef CurrentStruct #define CurrentStruct sqlite3_kvvfs_methods StructBinder { M(xRead, "i(sspi)"); M(xWrite, "i(sss)"); M(xDelete, "i(ss)"); M(nKeySize, "i"); } _StructBinder; #undef CurrentStruct #define CurrentStruct sqlite3_vtab StructBinder { M(pModule, "p"); M(nRef, "i"); M(zErrMsg, "p"); } _StructBinder; #undef CurrentStruct #define CurrentStruct sqlite3_vtab_cursor StructBinder { M(pVtab, "p"); } _StructBinder; #undef CurrentStruct #define CurrentStruct sqlite3_module StructBinder { M(iVersion, "i"); M(xCreate, "i(ppippp)"); M(xConnect, "i(ppippp)"); M(xBestIndex, "i(pp)"); M(xDisconnect, "i(p)"); M(xDestroy, "i(p)"); M(xOpen, "i(pp)"); M(xClose, "i(p)"); M(xFilter, "i(pisip)"); M(xNext, "i(p)"); M(xEof, "i(p)"); M(xColumn, "i(ppi)"); M(xRowid, "i(pp)"); M(xUpdate, "i(pipp)"); M(xBegin, "i(p)"); M(xSync, "i(p)"); M(xCommit, "i(p)"); M(xRollback, "i(p)"); M(xFindFunction, "i(pispp)"); M(xRename, "i(ps)"); // ^^^ v1. v2+ follows... M(xSavepoint, "i(pi)"); M(xRelease, "i(pi)"); M(xRollbackTo, "i(pi)"); // ^^^ v2. v3+ follows... M(xShadowName, "i(s)"); } _StructBinder; #undef CurrentStruct /** ** Workaround: in order to map the various inner structs from ** sqlite3_index_info, we have to uplift those into constructs we ** can access by type name. These structs _must_ match their ** in-sqlite3_index_info counterparts byte for byte. */ typedef struct { int iColumn; unsigned char op; unsigned char usable; int iTermOffset; } sqlite3_index_constraint; typedef struct { int iColumn; unsigned char desc; } sqlite3_index_orderby; typedef struct { int argvIndex; unsigned char omit; } sqlite3_index_constraint_usage; { /* Validate that the above struct sizeof()s match ** expectations. We could improve upon this by ** checking the offsetof() for each member. */ const sqlite3_index_info siiCheck; #define IndexSzCheck(T,M) \ (sizeof(T) == sizeof(*siiCheck.M)) if(!IndexSzCheck(sqlite3_index_constraint,aConstraint) || !IndexSzCheck(sqlite3_index_orderby,aOrderBy) || !IndexSzCheck(sqlite3_index_constraint_usage,aConstraintUsage)){ assert(!"sizeof mismatch in sqlite3_index_... struct(s)"); return 0; } #undef IndexSzCheck } #define CurrentStruct sqlite3_index_constraint StructBinder { M(iColumn, "i"); M(op, "C"); M(usable, "C"); M(iTermOffset, "i"); } _StructBinder; #undef CurrentStruct #define CurrentStruct sqlite3_index_orderby StructBinder { M(iColumn, "i"); M(desc, "C"); } _StructBinder; #undef CurrentStruct #define CurrentStruct sqlite3_index_constraint_usage StructBinder { M(argvIndex, "i"); M(omit, "C"); } _StructBinder; #undef CurrentStruct #define CurrentStruct sqlite3_index_info StructBinder { M(nConstraint, "i"); M(aConstraint, "p"); M(nOrderBy, "i"); M(aOrderBy, "p"); M(aConstraintUsage, "p"); M(idxNum, "i"); M(idxStr, "p"); M(needToFreeIdxStr, "i"); M(orderByConsumed, "i"); M(estimatedCost, "d"); M(estimatedRows, "j"); M(idxFlags, "i"); M(colUsed, "j"); } _StructBinder; #undef CurrentStruct #if SQLITE_WASM_TESTS #define CurrentStruct WasmTestStruct StructBinder { M(v4, "i"); M(cstr, "s"); M(ppV, "p"); M(v8, "j"); M(xFunc, "v(p)"); } _StructBinder; #undef CurrentStruct #endif } out( "]"/*structs*/); out("}"/*top-level object*/); |
︙ | ︙ | |||
816 817 818 819 820 821 822 | ** This function invokes the xDelete method of the given VFS (or the ** default VFS if pVfs is NULL), passing on the given filename. If ** zName is NULL, no default VFS is found, or it has no xDelete ** method, SQLITE_MISUSE is returned, else the result of the xDelete() ** call is returned. */ SQLITE_WASM_KEEP | | | 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 | ** This function invokes the xDelete method of the given VFS (or the ** default VFS if pVfs is NULL), passing on the given filename. If ** zName is NULL, no default VFS is found, or it has no xDelete ** method, SQLITE_MISUSE is returned, else the result of the xDelete() ** call is returned. */ SQLITE_WASM_KEEP int sqlite3_wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){ int rc = SQLITE_MISUSE /* ??? */; if( 0==pVfs && 0!=zName ) pVfs = sqlite3_vfs_find(0); if( zName && pVfs && pVfs->xDelete ){ rc = pVfs->xDelete(pVfs, zName, 1); } return rc; } |
︙ | ︙ | |||
847 848 849 850 851 852 853 | /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. ** ** This function resets the given db pointer's database as described at ** | | > > > > | > > | | > > > > | 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 | /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. ** ** This function resets the given db pointer's database as described at ** ** https://sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigresetdatabase ** ** But beware: virtual tables destroyed that way do not have their ** xDestroy() called, so will leak if they require that function for ** proper cleanup. ** ** Returns 0 on success, an SQLITE_xxx code on error. Returns ** SQLITE_MISUSE if pDb is NULL. */ SQLITE_WASM_KEEP int sqlite3_wasm_db_reset(sqlite3 *pDb){ int rc = SQLITE_MISUSE; if( pDb ){ sqlite3_table_column_metadata(pDb, "main", 0, 0, 0, 0, 0, 0, 0); rc = sqlite3_db_config(pDb, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); if( 0==rc ){ rc = sqlite3_exec(pDb, "VACUUM", 0, 0, 0); sqlite3_db_config(pDb, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); } } return rc; } /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. ** ** Uses the given database's VFS xRead to stream the db file's ** contents out to the given callback. The callback gets a single ** chunk of size n (its 2nd argument) on each call and must return 0 ** on success, non-0 on error. This function returns 0 on success, ** SQLITE_NOTFOUND if no db is open, or propagates any other non-0 ** code from the callback. Note that this is not thread-friendly: it ** expects that it will be the only thread reading the db file and |
︙ | ︙ | |||
903 904 905 906 907 908 909 | if(0 == nSize % 4096) nBuf = 4096; else if(0 == nSize % 2048) nBuf = 2048; else if(0 == nSize % 1024) nBuf = 1024; else nBuf = 512; } for( ; 0==rc && nPos<nSize; nPos += nBuf ){ rc = pFile->pMethods->xRead(pFile, buf, nBuf, nPos); | | > > > | > | | | | | | | > | | | | > > > | < < < | 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 | if(0 == nSize % 4096) nBuf = 4096; else if(0 == nSize % 2048) nBuf = 2048; else if(0 == nSize % 1024) nBuf = 1024; else nBuf = 512; } for( ; 0==rc && nPos<nSize; nPos += nBuf ){ rc = pFile->pMethods->xRead(pFile, buf, nBuf, nPos); if( SQLITE_IOERR_SHORT_READ == rc ){ rc = (nPos + nBuf) < nSize ? rc : 0/*assume EOF*/; } if( 0==rc ) rc = xCallback(buf, nBuf); } return rc; } /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. ** ** A proxy for sqlite3_serialize() which serializes the schema zSchema ** of pDb, placing the serialized output in pOut and nOut. nOut may be ** NULL. If zSchema is NULL then "main" is assumed. If pDb or pOut are ** NULL then SQLITE_MISUSE is returned. If allocation of the ** serialized copy fails, SQLITE_NOMEM is returned. On success, 0 is ** returned and `*pOut` will contain a pointer to the memory unless ** mFlags includes SQLITE_SERIALIZE_NOCOPY and the database has no ** contiguous memory representation, in which case `*pOut` will be ** NULL but 0 will be returned. ** ** If `*pOut` is not NULL, the caller is responsible for passing it to ** sqlite3_free() to free it. */ SQLITE_WASM_KEEP int sqlite3_wasm_db_serialize( sqlite3 *pDb, const char *zSchema, unsigned char **pOut, sqlite3_int64 *nOut, unsigned int mFlags ){ unsigned char * z; if( !pDb || !pOut ) return SQLITE_MISUSE; if( nOut ) *nOut = 0; z = sqlite3_serialize(pDb, zSchema ? zSchema : "main", nOut, mFlags); if( z || (SQLITE_SERIALIZE_NOCOPY & mFlags) ){ *pOut = z; return 0; }else{ return SQLITE_NOMEM; } } /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. ** ** Creates a new file using the I/O API of the given VFS, containing ** the given number of bytes of the given data. If the file exists, it ** is truncated to the given length and populated with the given ** data. ** ** This function exists so that we can implement the equivalent of ** Emscripten's FS.createDataFile() in a VFS-agnostic way. This ** functionality is intended for use in uploading database files. ** ** Not all VFSes support this functionality, e.g. the "kvvfs" does ** not. ** ** If pVfs is NULL, sqlite3_vfs_find(0) is used. ** ** If zFile is NULL, pVfs is NULL (and sqlite3_vfs_find(0) returns ** NULL), or nData is negative, SQLITE_MISUSE are returned. ** ** On success, it creates a new file with the given name, populated ** with the fist nData bytes of pData. If pData is NULL, the file is ** created and/or truncated to nData bytes. ** ** Whether or not directory components of zFilename are created ** automatically or not is unspecified: that detail is left to the ** VFS. The "opfs" VFS, for example, creates them. ** ** If an error happens while populating or truncating the file, the ** target file will be deleted (if needed) if this function created ** it. If this function did not create it, it is not deleted but may ** be left in an undefined state. ** ** Returns 0 on success. On error, it returns a code described above |
︙ | ︙ | |||
998 999 1000 1001 1002 1003 1004 | int doUnlock = 0; const unsigned char *pPos = pData; const int blockSize = 512 /* Because we are using pFile->pMethods->xWrite() for writing, and ** it may have a buffer limit related to sqlite3's pager size, we ** conservatively write in 512-byte blocks (smallest page ** size). */; | | > > > > > > > > > | > > > | > > | > > > > | 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 | int doUnlock = 0; const unsigned char *pPos = pData; const int blockSize = 512 /* Because we are using pFile->pMethods->xWrite() for writing, and ** it may have a buffer limit related to sqlite3's pager size, we ** conservatively write in 512-byte blocks (smallest page ** size). */; //fprintf(stderr, "pVfs=%p, zFilename=%s, nData=%d\n", pVfs, zFilename, nData); if( !pVfs ) pVfs = sqlite3_vfs_find(0); if( !pVfs || !zFilename || nData<0 ) return SQLITE_MISUSE; pVfs->xAccess(pVfs, zFilename, SQLITE_ACCESS_EXISTS, &fileExisted); rc = sqlite3OsOpenMalloc(pVfs, zFilename, &pFile, openFlags, &flagsOut); #if 0 # define RC fprintf(stderr,"create_file(%s,%s) @%d rc=%d\n", \ pVfs->zName, zFilename, __LINE__, rc); #else # define RC #endif RC; if(rc) return rc; pIo = pFile->pMethods; if( pIo->xLock ) { /* We need xLock() in order to accommodate the OPFS VFS, as it ** obtains a writeable handle via the lock operation and releases ** it in xUnlock(). If we don't do those here, we have to add code ** to the VFS to account check whether it was locked before ** xFileSize(), xTruncate(), and the like, and release the lock ** only if it was unlocked when the op was started. */ rc = pIo->xLock(pFile, SQLITE_LOCK_EXCLUSIVE); RC; doUnlock = 0==rc; } if( 0==rc ){ rc = pIo->xTruncate(pFile, nData); RC; } if( 0==rc && 0!=pData && nData>0 ){ while( 0==rc && nData>0 ){ const int n = nData>=blockSize ? blockSize : nData; rc = pIo->xWrite(pFile, pPos, n, (sqlite3_int64)(pPos - pData)); RC; nData -= n; pPos += n; } if( 0==rc && nData>0 ){ assert( nData<blockSize ); rc = pIo->xWrite(pFile, pPos, nData, (sqlite3_int64)(pPos - pData)); RC; } } if( pIo->xUnlock && doUnlock!=0 ){ pIo->xUnlock(pFile, SQLITE_LOCK_NONE); } pIo->xClose(pFile); if( rc!=0 && 0==fileExisted ){ pVfs->xDelete(pVfs, zFilename, 1); } RC; #undef RC return rc; } /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. ** |
︙ | ︙ | |||
1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 | ** Returns the pointer to the singleton object which holds the kvvfs ** I/O methods and associated state. */ SQLITE_WASM_KEEP sqlite3_kvvfs_methods * sqlite3_wasm_kvvfs_methods(void){ return &sqlite3KvvfsMethods; } #if defined(__EMSCRIPTEN__) && defined(SQLITE_ENABLE_WASMFS) #include <emscripten/wasmfs.h> /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings, specifically | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 | ** Returns the pointer to the singleton object which holds the kvvfs ** I/O methods and associated state. */ SQLITE_WASM_KEEP sqlite3_kvvfs_methods * sqlite3_wasm_kvvfs_methods(void){ return &sqlite3KvvfsMethods; } /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. ** ** This is a proxy for the variadic sqlite3_vtab_config() which passes ** its argument on, or not, to sqlite3_vtab_config(), depending on the ** value of its 2nd argument. Returns the result of ** sqlite3_vtab_config(), or SQLITE_MISUSE if the 2nd arg is not a ** valid value. */ SQLITE_WASM_KEEP int sqlite3_wasm_vtab_config(sqlite3 *pDb, int op, int arg){ switch(op){ case SQLITE_VTAB_DIRECTONLY: case SQLITE_VTAB_INNOCUOUS: return sqlite3_vtab_config(pDb, op); case SQLITE_VTAB_CONSTRAINT_SUPPORT: return sqlite3_vtab_config(pDb, op, arg); default: return SQLITE_MISUSE; } } /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. ** ** Wrapper for the variants of sqlite3_db_config() which take ** (int,int*) variadic args. */ SQLITE_WASM_KEEP int sqlite3_wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){ switch(op){ case SQLITE_DBCONFIG_ENABLE_FKEY: case SQLITE_DBCONFIG_ENABLE_TRIGGER: case SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: case SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: case SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: case SQLITE_DBCONFIG_ENABLE_QPSG: case SQLITE_DBCONFIG_TRIGGER_EQP: case SQLITE_DBCONFIG_RESET_DATABASE: case SQLITE_DBCONFIG_DEFENSIVE: case SQLITE_DBCONFIG_WRITABLE_SCHEMA: case SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: case SQLITE_DBCONFIG_DQS_DML: case SQLITE_DBCONFIG_DQS_DDL: case SQLITE_DBCONFIG_ENABLE_VIEW: case SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: case SQLITE_DBCONFIG_TRUSTED_SCHEMA: return sqlite3_db_config(pDb, op, arg1, pArg2); default: return SQLITE_MISUSE; } } /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. ** ** Wrapper for the variants of sqlite3_db_config() which take ** (void*,int,int) variadic args. */ SQLITE_WASM_KEEP int sqlite3_wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, int arg3){ switch(op){ case SQLITE_DBCONFIG_LOOKASIDE: return sqlite3_db_config(pDb, op, pArg1, arg2, arg3); default: return SQLITE_MISUSE; } } /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. ** ** Wrapper for the variants of sqlite3_db_config() which take ** (const char *) variadic args. */ SQLITE_WASM_KEEP int sqlite3_wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){ switch(op){ case SQLITE_DBCONFIG_MAINDBNAME: return sqlite3_db_config(pDb, op, zArg); default: return SQLITE_MISUSE; } } /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. ** ** Binding for combinations of sqlite3_config() arguments which take ** a single integer argument. */ SQLITE_WASM_KEEP int sqlite3_wasm_config_i(int op, int arg){ return sqlite3_config(op, arg); } /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. ** ** Binding for combinations of sqlite3_config() arguments which take ** two int arguments. */ SQLITE_WASM_KEEP int sqlite3_wasm_config_ii(int op, int arg1, int arg2){ return sqlite3_config(op, arg1, arg2); } /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. ** ** Binding for combinations of sqlite3_config() arguments which take ** a single i64 argument. */ SQLITE_WASM_KEEP int sqlite3_wasm_config_j(int op, sqlite3_int64 arg){ return sqlite3_config(op, arg); } #if defined(__EMSCRIPTEN__) && defined(SQLITE_ENABLE_WASMFS) #include <emscripten/wasmfs.h> /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings, specifically |
︙ | ︙ | |||
1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 | #if SQLITE_WASM_TESTS SQLITE_WASM_KEEP int sqlite3_wasm_test_intptr(int * p){ return *p = *p * 2; } SQLITE_WASM_KEEP int64_t sqlite3_wasm_test_int64_max(void){ return (int64_t)0x7fffffffffffffff; } SQLITE_WASM_KEEP | > > > > > | 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 | #if SQLITE_WASM_TESTS SQLITE_WASM_KEEP int sqlite3_wasm_test_intptr(int * p){ return *p = *p * 2; } SQLITE_WASM_KEEP void * sqlite3_wasm_test_voidptr(void * p){ return p; } SQLITE_WASM_KEEP int64_t sqlite3_wasm_test_int64_max(void){ return (int64_t)0x7fffffffffffffff; } SQLITE_WASM_KEEP |
︙ | ︙ | |||
1162 1163 1164 1165 1166 1167 1168 | } SQLITE_WASM_KEEP void sqlite3_wasm_test_stack_overflow(int recurse){ if(recurse) sqlite3_wasm_test_stack_overflow(recurse); } | | | | 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 | } SQLITE_WASM_KEEP void sqlite3_wasm_test_stack_overflow(int recurse){ if(recurse) sqlite3_wasm_test_stack_overflow(recurse); } /* For testing the 'string:dealloc' whwasmutil.xWrap() conversion. */ SQLITE_WASM_KEEP char * sqlite3_wasm_test_str_hello(int fail){ char * s = fail ? 0 : (char *)sqlite3_malloc(6); if(s){ memcpy(s, "hello", 5); s[5] = 0; } return s; } #endif /* SQLITE_WASM_TESTS */ #undef SQLITE_WASM_KEEP |
Changes to ext/wasm/api/sqlite3-worker1.js.
︙ | ︙ | |||
37 38 39 40 41 42 43 | let theJs = 'sqlite3.js'; if(urlParams.has('sqlite3.dir')){ theJs = urlParams.get('sqlite3.dir') + '/' + theJs; } //console.warn("worker1 theJs =",theJs); importScripts(theJs); sqlite3InitModule().then((sqlite3)=>{ | < < < | 37 38 39 40 41 42 43 44 45 46 | let theJs = 'sqlite3.js'; if(urlParams.has('sqlite3.dir')){ theJs = urlParams.get('sqlite3.dir') + '/' + theJs; } //console.warn("worker1 theJs =",theJs); importScripts(theJs); sqlite3InitModule().then((sqlite3)=>{ sqlite3.initWorker1API(); }); })(); |
Changes to ext/wasm/batch-runner.js.
︙ | ︙ | |||
223 224 225 226 227 228 229 | pSqlBegin = wasm.scopedAlloc( sqlByteLen + 1/*SQL + NUL*/) || toss("alloc(",sqlByteLen,") failed"); metrics.malloc = performance.now() - t; metrics.byteLength = sqlByteLen; let pSql = pSqlBegin; const pSqlEnd = pSqlBegin + sqlByteLen; t = performance.now(); wasm.heap8().set(sql, pSql); | | | | | | | | 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 | pSqlBegin = wasm.scopedAlloc( sqlByteLen + 1/*SQL + NUL*/) || toss("alloc(",sqlByteLen,") failed"); metrics.malloc = performance.now() - t; metrics.byteLength = sqlByteLen; let pSql = pSqlBegin; const pSqlEnd = pSqlBegin + sqlByteLen; t = performance.now(); wasm.heap8().set(sql, pSql); wasm.poke(pSql + sqlByteLen, 0); metrics.strcpy = performance.now() - t; let breaker = 0; while(pSql && wasm.peek(pSql,'i8')){ wasm.pokePtr(ppStmt, 0); wasm.pokePtr(pzTail, 0); t = performance.now(); let rc = capi.sqlite3_prepare_v3( db.handle, pSql, sqlByteLen, 0, ppStmt, pzTail ); metrics.prepTotal += performance.now() - t; checkSqliteRc(db.handle, rc); pStmt = wasm.peekPtr(ppStmt); pSql = wasm.peekPtr(pzTail); sqlByteLen = pSqlEnd - pSql; if(!pStmt) continue/*empty statement*/; ++metrics.stmtCount; t = performance.now(); rc = capi.sqlite3_step(pStmt); capi.sqlite3_finalize(pStmt); pStmt = 0; |
︙ | ︙ | |||
491 492 493 494 495 496 497 | const capi = this.sqlite3.capi, wasm = this.sqlite3.wasm; const stack = wasm.scopedAllocPush(); let pDb = 0; try{ const oFlags = capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE; const ppDb = wasm.scopedAllocPtr(); const rc = capi.sqlite3_open_v2(d.filename, ppDb, oFlags, null); | | | 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 | const capi = this.sqlite3.capi, wasm = this.sqlite3.wasm; const stack = wasm.scopedAllocPush(); let pDb = 0; try{ const oFlags = capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE; const ppDb = wasm.scopedAllocPtr(); const rc = capi.sqlite3_open_v2(d.filename, ppDb, oFlags, null); pDb = wasm.peekPtr(ppDb) if(rc) toss("sqlite3_open_v2() failed with code",rc); capi.sqlite3_exec(pDb, "PRAGMA cache_size="+cacheSize, 0, 0, 0); this.logHtml(dbId,"cache_size =",cacheSize); }catch(e){ if(pDb) capi.sqlite3_close_v2(pDb); }finally{ wasm.scopedAllocPop(stack); |
︙ | ︙ |
Added ext/wasm/c-pp.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 | /* ** 2022-11-12: ** ** 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. ** ************************************************************************ ** ** The C-minus Preprocessor: a truly minimal C-like preprocessor. ** Why? Because C preprocessors _can_ process non-C code but generally make ** quite a mess of it. The purpose of this application is an extremely ** minimal preprocessor with only the most basic functionality of a C ** preprocessor, namely: ** ** - Limited `#if`, where its one argument is a macro name which ** resolves to true if it's defined, false if it's not. Likewise, ** `#ifnot` is the inverse. Includes `#else` and `#elif` and ** `#elifnot`. Such chains are terminated with `#endif`. ** ** - `#define` accepts one or more arguments, the names of ** macros. Each one is implicitly true. ** ** - `#undef` undefine one or more macros. ** ** - `#error` treats the rest of the line as a fatal error message. ** ** - `#include` treats its argument as a filename token (NOT quoted, ** though support for quoting may be added later). Some effort is ** made to prevent recursive inclusion, but that support is both ** somewhat fragile and possibly completely unnecessary. ** ** - `#pragma` is in place for adding "meta-commands", but it does not ** yet have any concrete list of documented commands. ** * - `#stderr` outputs its file name, line number, and the remaininder ** of that line to stderr. ** ** - `#//` acts as a single-line comment, noting that there must be as ** space after the `//` part because `//` is (despite appearances) ** parsed like a keyword. ** ** Note that "#" above is symbolic. The keyword delimiter is ** configurable and defaults to "##". Define CMPP_DEFAULT_DELIM to a ** string when compiling to define the default at build-time. ** ** This preprocessor does no expansion of content except within the ** bounds of its `#keywords`. ** ** Design note: this code makes use of sqlite3. Though not _strictly_ ** needed in order to implement it, this tool was specifically created ** for potential use with the sqlite3 project's own JavaScript code, ** so there's no reason not to make use of it to do some of the heavy ** lifting. It does not require any cutting-edge sqlite3 features and ** should be usable with any version which supports `WITHOUT ROWID`. ** ** Author(s): ** ** - Stephan Beal <https://wanderinghorse.net/home/stephan/> */ #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <stdarg.h> #include <assert.h> #include <ctype.h> #include "sqlite3.h" #if defined(_WIN32) || defined(WIN32) # include <io.h> # include <fcntl.h> # ifndef access # define access(f,m) _access((f),(m)) # endif #else # include <unistd.h> #endif #ifndef CMPP_DEFAULT_DELIM #define CMPP_DEFAULT_DELIM "##" #endif #if 1 # define CMPP_NORETURN __attribute__((noreturn)) #else # define CMPP_NORETURN #endif /* Fatally exits the app with the given printf-style message. */ static CMPP_NORETURN void fatalv(char const *zFmt, va_list); static CMPP_NORETURN void fatal(char const *zFmt, ...); /** Proxy for free(), for symmetry with cmpp_realloc(). */ static void cmpp_free(void *p); /** A realloc() proxy which dies fatally on allocation error. */ static void * cmpp_realloc(void * p, unsigned n); #if 0 /** A malloc() proxy which dies fatally on allocation error. */ static void * cmpp_malloc(unsigned n); #endif /* ** If p is stdin or stderr then this is a no-op, else it is a ** proxy for fclose(). This is a no-op if p is NULL. */ static void FILE_close(FILE *p); /* ** Works like fopen() but accepts the special name "-" to mean either ** stdin (if zMode indicates a real-only mode) or stdout. Fails ** fatally on error. */ static FILE * FILE_open(char const *zName, const char * zMode); /* ** Reads the entire contents of the given file, allocating it in a ** buffer which gets assigned to `*pOut`. `*nOut` gets assigned the ** length of the output buffer. Fails fatally on error. */ static void FILE_slurp(FILE *pFile, unsigned char **pOut, unsigned * nOut); /* ** Intended to be passed an sqlite3 result code. If it's non-0 ** then it emits a fatal error message which contains both the ** given string and the sqlite3_errmsg() from the application's ** database instance. */ static void db_affirm_rc(int rc, const char * zMsg); /* ** Proxy for sqlite3_str_finish() which fails fatally if that ** routine returns NULL. */ static char * db_str_finish(sqlite3_str *s, int * n); /* ** Proxy for sqlite3_str_new() which fails fatally if that ** routine returns NULL. */ static sqlite3_str * db_str_new(void); /* Proxy for sqlite3_finalize(). */ static void db_finalize(sqlite3_stmt *pStmt); /* ** Proxy for sqlite3_step() which fails fatally if the result ** is anything other than SQLITE_ROW or SQLITE_DONE. */ static int db_step(sqlite3_stmt *pStmt); /* ** Proxy for sqlite3_bind_int() which fails fatally on error. */ static void db_bind_int(sqlite3_stmt *pStmt, int col, int val); #if 0 /* ** Proxy for sqlite3_bind_null() which fails fatally on error. */ static void db_bind_null(sqlite3_stmt *pStmt, int col); #endif /* ** Proxy for sqlite3_bind_text() which fails fatally on error. */ static void db_bind_text(sqlite3_stmt *pStmt, int col, const char * zStr); /* ** Proxy for sqlite3_bind_text() which fails fatally on error. */ static void db_bind_textn(sqlite3_stmt *pStmt, int col, const char * zStr, int len); #if 0 /* ** Proxy for sqlite3_bind_text() which fails fatally on error. It uses ** sqlite3_str_vappendf() so supports all of its formatting options. */ static void db_bind_textv(sqlite3_stmt *pStmt, int col, const char * zFmt, ...); #endif /* ** Proxy for sqlite3_free(), to be passed any memory which is allocated ** by sqlite3_malloc(). */ static void db_free(void *m); /* ** Adds the given `#define` macro name to the list of macros, ignoring ** any duplicates. Fails fatally on error. */ static void db_define_add(const char * zKey); /* ** Returns true if the given key is already in the `#define` list, ** else false. Fails fatally on db error. */ static int db_define_has(const char * zName); /* ** Removes the given `#define` macro name from the list of ** macros. Fails fatally on error. */ static void db_define_rm(const char * zKey); /* ** Adds the given filename to the list of being-`#include`d files, ** using the given source file name and line number of error reporting ** purposes. If recursion is later detected. */ static void db_including_add(const char * zKey, const char * zSrc, int srcLine); /* ** Adds the given dir to the list of includes. They are checked in the ** order they are added. */ static void db_include_dir_add(const char * zKey); /* ** Returns a resolved path of PREFIX+'/'+zKey, where PREFIX is one of ** the `#include` dirs (db_include_dir_add()). If no file match is ** found, NULL is returned. Memory must eventually be passed to ** db_free() to free it. */ static char * db_include_search(const char * zKey); /* ** Removes the given key from the `#include` list. */ static void db_include_rm(const char * zKey); /* ** A proxy for sqlite3_prepare() which fails fatally on error. */ static void db_prepare(sqlite3_stmt **pStmt, const char * zSql, ...); /* ** Opens the given file and processes its contents as c-pp, sending ** all output to the global c-pp output channel. Fails fatally on ** error. */ static void cmpp_process_file(const char * zName); /* ** Returns the number newline characters between the given starting ** point and inclusive ending point. Results are undefined if zFrom is ** greater than zTo. */ static unsigned count_lines(unsigned char const * zFrom, unsigned char const *zTo); /* ** Wrapper around a FILE handle. */ struct FileWrapper { /* File's name. */ char const *zName; /* FILE handle. */ FILE * pFile; /* Where FileWrapper_slurp() stores the file's contents. */ unsigned char * zContent; /* Size of this->zContent, as set by FileWrapper_slurp(). */ unsigned nContent; }; typedef struct FileWrapper FileWrapper; #define FileWrapper_empty_m {0,0,0,0} static const FileWrapper FileWrapper_empty = FileWrapper_empty_m; /* Proxy for FILE_close(). */ static void FileWrapper_close(FileWrapper * p); /* Proxy for FILE_open(). */ static void FileWrapper_open(FileWrapper * p, const char * zName, const char *zMode); /* Proxy for FILE_slurp(). */ static void FileWrapper_slurp(FileWrapper * p); /* ** Outputs a printf()-formatted message to stderr. */ static void g_stderr(char const *zFmt, ...); /* ** Outputs a printf()-formatted message to stderr. */ static void g_stderrv(char const *zFmt, va_list); #define g_debug(lvl,pfexpr) \ if(lvl<=g.doDebug) g_stderr("%s @ %s:%d: ",g.zArgv0,__FILE__,__LINE__); \ if(lvl<=g.doDebug) g_stderr pfexpr void fatalv(char const *zFmt, va_list va){ if(zFmt && *zFmt){ vfprintf(stderr, zFmt, va); } fputc('\n', stderr); exit(1); } void fatal(char const *zFmt, ...){ va_list va; va_start(va, zFmt); fatalv(zFmt, va); va_end(va); } void cmpp_free(void *p){ free(p); } void * cmpp_realloc(void * p, unsigned n){ void * const rc = realloc(p, n); if(!rc) fatal("realloc(P,%u) failed", n); return rc; } #if 0 void * cmpp_malloc(unsigned n){ void * const rc = malloc(n); if(!rc) fatal("malloc(%u) failed", n); return rc; } #endif FILE * FILE_open(char const *zName, const char * zMode){ FILE * p; if('-'==zName[0] && 0==zName[1]){ p = strstr(zMode,"w") ? stdout : stdin; }else{ p = fopen(zName, zMode); if(!p) fatal("Cannot open file [%s] with mode [%s]", zName, zMode); } return p; } void FILE_close(FILE *p){ if(p && p!=stdout && p!=stderr){ fclose(p); } } void FILE_slurp(FILE *pFile, unsigned char **pOut, unsigned * nOut){ unsigned char zBuf[1024 * 8]; unsigned char * pDest = 0; unsigned nAlloc = 0; unsigned nOff = 0; /* Note that this needs to be able to work on non-seekable streams, ** thus we read in chunks instead of doing a single alloc and ** filling it in one go. */ while( !feof(pFile) ){ size_t const n = fread(zBuf, 1, sizeof(zBuf), pFile); if(n>0){ if(nAlloc < nOff + n + 1){ nAlloc = nOff + n + 1; pDest = cmpp_realloc(pDest, nAlloc); } memcpy(pDest + nOff, zBuf, n); nOff += n; } } if(pDest) pDest[nOff] = 0; *pOut = pDest; *nOut = nOff; } void FileWrapper_close(FileWrapper * p){ if(p->pFile) FILE_close(p->pFile); if(p->zContent) cmpp_free(p->zContent); *p = FileWrapper_empty; } void FileWrapper_open(FileWrapper * p, const char * zName, const char * zMode){ FileWrapper_close(p); p->pFile = FILE_open(zName, zMode); p->zName = zName; } void FileWrapper_slurp(FileWrapper * p){ assert(!p->zContent); assert(p->pFile); FILE_slurp(p->pFile, &p->zContent, &p->nContent); } unsigned count_lines(unsigned char const * zFrom, unsigned char const *zTo){ unsigned ln = 0; unsigned char const *zPos = zFrom; assert(zFrom && zTo); assert(zFrom <= zTo); for(; zPos < zTo; ++zPos){ switch(*zPos){ case (unsigned)'\n': ++ln; break; default: break; } } return ln; } enum CmppParseState { TS_Start = 1, TS_If, TS_IfPassed, TS_Else, TS_Error }; typedef enum CmppParseState CmppParseState; enum CmppTokenType { TT_Invalid = 0, TT_Comment, TT_Define, TT_Elif, TT_ElifNot, TT_Else, TT_EndIf, TT_Error, TT_If, TT_IfNot, TT_Include, TT_Line, TT_Pragma, TT_Stderr, TT_Undef }; typedef enum CmppTokenType CmppTokenType; struct CmppToken { CmppTokenType ttype; /* Line number of this token in the source file. */ unsigned lineNo; /* Start of the token. */ unsigned char const * zBegin; /* One-past-the-end byte of the token. */ unsigned char const * zEnd; }; typedef struct CmppToken CmppToken; #define CmppToken_empty_m {TT_Invalid,0,0,0} static const CmppToken CmppToken_empty = CmppToken_empty_m; /* ** CmppLevel represents one "level" of tokenization, starting at the ** top of the main input, incrementing once for each level of `#if`, ** and decrementing for each `#endif`. */ typedef struct CmppLevel CmppLevel; struct CmppLevel { unsigned short flags; /* ** Used for controlling which parts of an if/elif/...endif chain ** should get output. */ unsigned short skipLevel; /* The token which started this level (an 'if' or 'ifnot'). */ CmppToken token; CmppParseState pstate; }; #define CmppLevel_empty_m {0U,0U,CmppToken_empty_m,TS_Start} static const CmppLevel CmppLevel_empty = CmppLevel_empty_m; enum CmppLevel_Flags { /* Max depth of nested `#if` constructs in a single tokenizer. */ CmppLevel_Max = 10, /* Max number of keyword arguments. */ CmppArgs_Max = 10, /* Flag indicating that output for a CmpLevel should be elided. */ CmppLevel_F_ELIDE = 0x01, /* ** Mask of CmppLevel::flags which are inherited when CmppLevel_push() ** is used. */ CmppLevel_F_INHERIT_MASK = 0x01 }; typedef struct CmppTokenizer CmppTokenizer; typedef struct CmppKeyword CmppKeyword; typedef void (*cmpp_keyword_f)(CmppKeyword const * pKw, CmppTokenizer * t); struct CmppKeyword { const char *zName; unsigned nName; int bTokenize; CmppTokenType ttype; cmpp_keyword_f xCall; }; static CmppKeyword const * CmppKeyword_search(const char *zName); static void cmpp_process_keyword(CmppTokenizer * const t); /* ** Tokenizer for c-pp input files. */ struct CmppTokenizer { const char * zName; /* Input (file) name for error reporting */ unsigned const char * zBegin; /* start of input */ unsigned const char * zEnd; /* one-after-the-end of input */ unsigned const char * zAnchor; /* start of input or end point of previous token */ unsigned const char * zPos; /* current position */ unsigned int lineNo; /* line # of current pos */ CmppParseState pstate; CmppToken token; /* current token result */ struct { unsigned ndx; CmppLevel stack[CmppLevel_Max]; } level; /* Args for use in cmpp_keyword_f() impls. */ struct { CmppKeyword const * pKw; int argc; const unsigned char * argv[CmppArgs_Max]; unsigned char lineBuf[1024]; } args; }; #define CT_level(t) (t)->level.stack[(t)->level.ndx] #define CT_pstate(t) CT_level(t).pstate #define CT_skipLevel(t) CT_level(t).skipLevel #define CLvl_skip(lvl) ((lvl)->skipLevel || ((lvl)->flags & CmppLevel_F_ELIDE)) #define CT_skip(t) CLvl_skip(&CT_level(t)) #define CmppTokenizer_empty_m { \ 0,0,0,0,0,1U/*lineNo*/, \ TS_Start, \ CmppToken_empty_m, \ {/*level*/0U,{CmppLevel_empty_m}}, \ {/*args*/0,0,{0},{0}} \ } static const CmppTokenizer CmppTokenizer_empty = CmppTokenizer_empty_m; static void cmpp_t_out(CmppTokenizer * t, void const *z, unsigned int n); /*static void cmpp_t_outf(CmppTokenizer * t, char const *zFmt, ...);*/ /* ** Pushes a new level into the given tokenizer. Fails fatally if ** it's too deep. */ static void CmppLevel_push(CmppTokenizer * const t); /* ** Pops a level from the tokenizer. Fails fatally if the top ** level is popped. */ static void CmppLevel_pop(CmppTokenizer * const t); /* ** Returns the current level object. */ static CmppLevel * CmppLevel_get(CmppTokenizer * const t); /* ** Global app state singleton. */ static struct Global { /* main()'s argv[0]. */ const char * zArgv0; /* ** Bytes of the keyword delimiter/prefix. Owned ** elsewhere. */ const char * zDelim; /* Byte length of this->zDelim. */ unsigned short nDelim; /* If true, enables certain debugging output. */ int doDebug; /* App's db instance. */ sqlite3 * db; /* Output channel. */ FileWrapper out; struct { sqlite3_stmt * defIns; sqlite3_stmt * defDel; sqlite3_stmt * defHas; sqlite3_stmt * inclIns; sqlite3_stmt * inclDel; sqlite3_stmt * inclHas; sqlite3_stmt * inclPathAdd; sqlite3_stmt * inclSearch; } stmt; } g = { "?", CMPP_DEFAULT_DELIM/*zDelim*/, (unsigned short) sizeof(CMPP_DEFAULT_DELIM)-1/*nDelim*/, 0/*doDebug*/, 0/*db*/, FileWrapper_empty_m/*out*/, {/*stmt*/ 0/*defIns*/, 0/*defDel*/, 0/*defHas*/, 0/*inclIns*/, 0/*inclDel*/, 0/*inclHas*/, 0/*inclPathAdd*/ } }; #if 0 /* ** Outputs a printf()-formatted message to c-pp's global output ** channel. */ static void g_outf(char const *zFmt, ...); void g_outf(char const *zFmt, ...){ va_list va; va_start(va, zFmt); vfprintf(g.out.pFile, zFmt, va); va_end(va); } #endif #if 0 /* Outputs n bytes from z to c-pp's global output channel. */ static void g_out(void const *z, unsigned int n); void g_out(void const *z, unsigned int n){ if(1!=fwrite(z, n, 1, g.out.pFile)){ int const err = errno; fatal("fwrite() output failed with errno #%d", err); } } #endif void g_stderrv(char const *zFmt, va_list va){ vfprintf(stderr, zFmt, va); } void g_stderr(char const *zFmt, ...){ va_list va; va_start(va, zFmt); g_stderrv(zFmt, va); va_end(va); } #if 0 void cmpp_t_outf(CmppTokenizer * t, char const *zFmt, ...){ if(!CT_skip(t)){ va_list va; va_start(va, zFmt); vfprintf(g.out.pFile, zFmt, va); va_end(va); } } #endif void cmpp_t_out(CmppTokenizer * t, void const *z, unsigned int n){ if(!CT_skip(t)){ if(1!=fwrite(z, n, 1, g.out.pFile)){ int const err = errno; fatal("fwrite() output failed with errno #%d", err); } } } void CmppLevel_push(CmppTokenizer * const t){ CmppLevel * pPrev; CmppLevel * p; if(t->level.ndx+1 == (unsigned)CmppLevel_Max){ fatal("%sif nesting level is too deep. Max=%d\n", g.zDelim, CmppLevel_Max); } pPrev = &CT_level(t); p = &t->level.stack[++t->level.ndx]; *p = CmppLevel_empty; p->token = t->token; p->flags = (CmppLevel_F_INHERIT_MASK & pPrev->flags); if(CLvl_skip(pPrev)) p->flags |= CmppLevel_F_ELIDE; } void CmppLevel_pop(CmppTokenizer * const t){ if(!t->level.ndx){ fatal("Internal error: CmppLevel_pop() at the top of the stack"); } t->level.stack[t->level.ndx--] = CmppLevel_empty; } CmppLevel * CmppLevel_get(CmppTokenizer * const t){ return &t->level.stack[t->level.ndx]; } void db_affirm_rc(int rc, const char * zMsg){ if(rc){ fatal("Db error #%d %s: %s", rc, zMsg, sqlite3_errmsg(g.db)); } } void db_finalize(sqlite3_stmt *pStmt){ sqlite3_finalize(pStmt); } int db_step(sqlite3_stmt *pStmt){ int const rc = sqlite3_step(pStmt); if(SQLITE_ROW!=rc && SQLITE_DONE!=rc){ db_affirm_rc(rc, "from db_step()"); } return rc; } static sqlite3_str * db_str_new(void){ sqlite3_str * rc = sqlite3_str_new(g.db); if(!rc) fatal("Alloc failed for sqlite3_str_new()"); return rc; } static char * db_str_finish(sqlite3_str *s, int * n){ int const rc = sqlite3_str_errcode(s); if(rc) fatal("Error #%d from sqlite3_str_errcode()", rc); if(n) *n = sqlite3_str_length(s); char * z = sqlite3_str_finish(s); if(!z) fatal("Alloc failed for sqlite3_str_new()"); return z; } void db_prepare(sqlite3_stmt **pStmt, const char * zSql, ...){ int rc; sqlite3_str * str = db_str_new(); char * z = 0; int n = 0; va_list va; if(!str) fatal("sqlite3_str_new() failed"); va_start(va, zSql); sqlite3_str_vappendf(str, zSql, va); va_end(va); rc = sqlite3_str_errcode(str); if(rc) fatal("sqlite3_str_errcode() = %d", rc); z = db_str_finish(str, &n); rc = sqlite3_prepare_v2(g.db, z, n, pStmt, 0); if(rc) fatal("Error #%d (%s) preparing: %s", rc, sqlite3_errmsg(g.db), z); sqlite3_free(z); } void db_bind_int(sqlite3_stmt *pStmt, int col, int val){ int const rc = sqlite3_bind_int(pStmt, col, val); db_affirm_rc(rc,"from db_bind_int()"); } #if 0 void db_bind_null(sqlite3_stmt *pStmt, int col){ int const rc = sqlite3_bind_null(pStmt, col); db_affirm_rc(rc,"from db_bind_null()"); } #endif void db_bind_textn(sqlite3_stmt *pStmt, int col, const char * zStr, int n){ int const rc = zStr ? sqlite3_bind_text(pStmt, col, zStr, n, SQLITE_TRANSIENT) : sqlite3_bind_null(pStmt, col); db_affirm_rc(rc,"from db_bind_textn()"); } void db_bind_text(sqlite3_stmt *pStmt, int col, const char * zStr){ db_bind_textn(pStmt, col, zStr, -1); } #if 0 void db_bind_textv(sqlite3_stmt *pStmt, int col, const char * zFmt, ...){ int rc; sqlite3_str * str = db_str_new(); int n = 0; char * z; va_list va; va_start(va,zFmt); sqlite3_str_vappendf(str, zFmt, va); va_end(va); z = db_str_finish(str, &n); rc = sqlite3_bind_text(pStmt, col, z, n, sqlite3_free); db_affirm_rc(rc,"from db_bind_textv()"); } #endif void db_free(void *m){ sqlite3_free(m); } void db_define_add(const char * zKey){ int rc; if(!g.stmt.defIns){ db_prepare(&g.stmt.defIns, "INSERT OR REPLACE INTO def(k) VALUES(?)"); } db_bind_text(g.stmt.defIns, 1, zKey); rc = db_step(g.stmt.defIns); if(SQLITE_DONE != rc){ db_affirm_rc(rc, "Stepping INSERT on def"); } g_debug(2,("define: %s\n",zKey)); sqlite3_reset(g.stmt.defIns); } int db_define_has(const char * zName){ int rc; if(!g.stmt.defHas){ db_prepare(&g.stmt.defHas, "SELECT 1 FROM def WHERE k=?"); } db_bind_text(g.stmt.defHas, 1, zName); rc = db_step(g.stmt.defHas); if(SQLITE_ROW == rc){ rc = 1; }else{ assert(SQLITE_DONE==rc); rc = 0; } g_debug(1,("define has [%s] = %d\n",zName, rc)); sqlite3_clear_bindings(g.stmt.defHas); sqlite3_reset(g.stmt.defHas); return rc; } void db_define_rm(const char * zKey){ int rc; int n = 0; const char *zPos = zKey; if(!g.stmt.defDel){ db_prepare(&g.stmt.defDel, "DELETE FROM def WHERE k=?"); } for( ; *zPos && '='!=*zPos; ++n, ++zPos) {} db_bind_text(g.stmt.defDel, 1, zKey); rc = db_step(g.stmt.defDel); if(SQLITE_DONE != rc){ db_affirm_rc(rc, "Stepping DELETE on def"); } g_debug(2,("undefine: %.*s\n",n, zKey)); sqlite3_clear_bindings(g.stmt.defDel); sqlite3_reset(g.stmt.defDel); } void db_including_add(const char * zKey, const char * zSrc, int srcLine){ int rc; if(!g.stmt.inclIns){ db_prepare(&g.stmt.inclIns, "INSERT OR FAIL INTO incl(file,srcFile,srcLine) VALUES(?,?,?)"); } db_bind_text(g.stmt.inclIns, 1, zKey); db_bind_text(g.stmt.inclIns, 2, zSrc); db_bind_int(g.stmt.inclIns, 3, srcLine); rc = db_step(g.stmt.inclIns); if(SQLITE_DONE != rc){ db_affirm_rc(rc, "Stepping INSERT on incl"); } g_debug(2,("inclpath add [%s] from [%s]:%d\n", zKey, zSrc, srcLine)); sqlite3_clear_bindings(g.stmt.inclIns); sqlite3_reset(g.stmt.inclIns); } void db_include_rm(const char * zKey){ int rc; if(!g.stmt.inclDel){ db_prepare(&g.stmt.inclDel, "DELETE FROM incl WHERE file=?"); } db_bind_text(g.stmt.inclDel, 1, zKey); rc = db_step(g.stmt.inclDel); if(SQLITE_DONE != rc){ db_affirm_rc(rc, "Stepping DELETE on incl"); } g_debug(2,("inclpath rm [%s]\n", zKey)); sqlite3_clear_bindings(g.stmt.inclDel); sqlite3_reset(g.stmt.inclDel); } char * db_include_search(const char * zKey){ char * zName = 0; if(!g.stmt.inclSearch){ db_prepare(&g.stmt.inclSearch, "SELECT ?1 fn WHERE fileExists(fn) " "UNION ALL SELECT * FROM (" "SELECT replace(dir||'/'||?1, '//','/') AS fn " "FROM inclpath WHERE fileExists(fn) ORDER BY seq" ")"); } db_bind_text(g.stmt.inclSearch, 1, zKey); if(SQLITE_ROW==db_step(g.stmt.inclSearch)){ const unsigned char * z = sqlite3_column_text(g.stmt.inclSearch, 0); zName = z ? sqlite3_mprintf("%s", z) : 0; if(!zName) fatal("Alloc failed"); } sqlite3_clear_bindings(g.stmt.inclSearch); sqlite3_reset(g.stmt.inclSearch); return zName; } static int db_including_has(const char * zName){ int rc; if(!g.stmt.inclHas){ db_prepare(&g.stmt.inclHas, "SELECT 1 FROM incl WHERE file=?"); } db_bind_text(g.stmt.inclHas, 1, zName); rc = db_step(g.stmt.inclHas); if(SQLITE_ROW == rc){ rc = 1; }else{ assert(SQLITE_DONE==rc); rc = 0; } g_debug(2,("inclpath has [%s] = %d\n",zName, rc)); sqlite3_clear_bindings(g.stmt.inclHas); sqlite3_reset(g.stmt.inclHas); return rc; } #if 0 /* ** Fails fatally if the `#include` list contains the given key. */ static void db_including_check(const char * zKey); void db_including_check(const char * zName){ if(db_including_has(zName)){ fatal("Recursive include detected: %s\n", zName); } } #endif void db_include_dir_add(const char * zDir){ static int seq = 0; int rc; if(!g.stmt.inclPathAdd){ db_prepare(&g.stmt.inclPathAdd, "INSERT OR FAIL INTO inclpath(seq,dir) VALUES(?,?)"); } db_bind_int(g.stmt.inclPathAdd, 1, ++seq); db_bind_text(g.stmt.inclPathAdd, 2, zDir); rc = db_step(g.stmt.inclPathAdd); if(SQLITE_DONE != rc){ db_affirm_rc(rc, "Stepping INSERT on inclpath"); } g_debug(2,("inclpath add #%d: %s\n",seq, zDir)); sqlite3_clear_bindings(g.stmt.inclPathAdd); sqlite3_reset(g.stmt.inclPathAdd); } static void cmpp_atexit(void){ #define FINI(M) if(g.stmt.M) sqlite3_finalize(g.stmt.M) FINI(defIns); FINI(defDel); FINI(defHas); FINI(inclIns); FINI(inclDel); FINI(inclHas); FINI(inclPathAdd); FINI(inclSearch); #undef FINI FileWrapper_close(&g.out); if(g.db) sqlite3_close(g.db); } /* ** sqlite3 UDF which returns true if its argument refers to an ** accessible file, else false. */ static void udf_file_exists( sqlite3_context *context, int argc, sqlite3_value **argv ){ const char *zName; (void)(argc); /* Unused parameter */ zName = (const char*)sqlite3_value_text(argv[0]); if( zName==0 ) return; sqlite3_result_int(context, 0==access(zName, 0)); } /* Initialize g.db, failing fatally on error. */ static void cmpp_initdb(void){ int rc; char * zErr = 0; const char * zSchema = "CREATE TABLE def(" "k TEXT PRIMARY KEY NOT NULL" /*"v INTEGER DEFAULT 1"*/ ") WITHOUT ROWID;" /* ^^^ defines */ "CREATE TABLE incl(" "file TEXT PRIMARY KEY NOT NULL," "srcFile TEXT DEFAULT NULL," "srcLine INTEGER DEFAULT 0" ") WITHOUT ROWID;" /* ^^^ files currently being included */ "CREATE TABLE inclpath(" "seq INTEGER UNIQUE, " "dir TEXT PRIMARY KEY NOT NULL ON CONFLICT IGNORE" ")" /* ^^^ include path */ ; assert(0==g.db); if(g.db) return; rc = sqlite3_open_v2(":memory:", &g.db, SQLITE_OPEN_READWRITE, 0); if(rc) fatal("Error opening :memory: db."); rc = sqlite3_exec(g.db, zSchema, 0, 0, &zErr); if(rc) fatal("Error initializing database: %s", zErr); rc = sqlite3_create_function(g.db, "fileExists", 1, SQLITE_UTF8|SQLITE_DIRECTONLY, 0, udf_file_exists, 0, 0); db_affirm_rc(rc, "UDF registration failed."); } /* ** For position zPos, which must be in the half-open range ** [zBegin,zEnd), returns g.nDelim if it is at the start of a line and ** starts with g.zDelim, else returns 0. */ static unsigned short cmpp_is_delim(unsigned char const *zBegin, unsigned char const *zEnd, unsigned char const *zPos){ assert(zEnd>zBegin); assert(zPos<zEnd); assert(zPos>=zBegin); if(zPos>zBegin && ('\n'!=*(zPos - 1) || ((unsigned)(zEnd - zPos) <= g.nDelim))){ return 0; }else if(0==memcmp(zPos, g.zDelim, g.nDelim)){ return g.nDelim; }else{ return 0; } } /* ** Scans t to the next keyword line, emitting all input before that ** which is _not_ a keyword line unless it's elided due to being ** inside a block which elides its content. Returns 0 if no keyword ** line was found, in which case the end of the input has been ** reached, else returns a truthy value and sets up t's state for use ** with cmpp_process_keyword(), which should then be called. */ static int cmpp_next_keyword_line(CmppTokenizer * const t){ unsigned char const * zStart; unsigned char const * z; CmppToken * const tok = &t->token; unsigned short isDelim = 0; assert(t->zBegin); assert(t->zEnd > t->zBegin); if(!t->zPos) t->zPos = t->zBegin; t->zAnchor = t->zPos; zStart = z = t->zPos; *tok = CmppToken_empty; while(z<t->zEnd && 0==(isDelim = cmpp_is_delim(t->zBegin, t->zEnd, z))){ ++z; } if(z>zStart){ /* We passed up content */ cmpp_t_out(t, zStart, (unsigned)(z - zStart)); } assert(isDelim==0 || isDelim==g.nDelim); tok->lineNo = t->lineNo += count_lines(zStart, z); if(isDelim){ /* Handle backslash-escaped newlines */ int isEsc = 0, atEol = 0; tok->zBegin = z+isDelim; for( ++z ; z<t->zEnd && 0==atEol; ++z ){ switch((int)*z){ case (int)'\\': isEsc = 0==isEsc; break; case (int)'\n': atEol = 0==isEsc; isEsc = 0; ++t->lineNo; break; default: break; } } tok->zEnd = atEol ? z-1 : z; /* Strip leading spaces */ while(tok->zBegin < tok->zEnd && isspace((char)(*tok->zBegin))){ ++tok->zBegin; } tok->ttype = TT_Line; g_debug(2,("Keyword @ line %u: [[[%.*s]]]\n", tok->lineNo, (int)(tok->zEnd-tok->zBegin), tok->zBegin)); } t->zPos = z; if(isDelim){ /* Split t->token into arguments for the line's keyword */ int i, argc = 0, prevChar = 0; const unsigned tokLen = (unsigned)(tok->zEnd - tok->zBegin); unsigned char * zKwd; unsigned char * zEsc; unsigned char * zz; assert(TT_Line==tok->ttype); if((unsigned)sizeof(t->args.lineBuf) < tokLen + 1){ fatal("Keyword line is unreasonably long: %.*s", tokLen, tok->zBegin); }else if(!tokLen){ fatal("Line #%u has no keyword after delimiter", tok->lineNo); } g_debug(2,("token @ line %u len=%u [[[%.*s]]]\n", tok->lineNo, tokLen, tokLen, tok->zBegin)); zKwd = &t->args.lineBuf[0]; memcpy(zKwd, tok->zBegin, tokLen); memset(zKwd + tokLen, 0, sizeof(t->args.lineBuf) - tokLen); for( zEsc = 0, zz = zKwd; *zz; ++zz ){ /* Convert backslash-escaped newlines to whitespace */ switch((int)*zz){ case (int)'\\': if(zEsc) zEsc = 0; else zEsc = zz; break; case (int)'\n': assert(zEsc && "Should not have an unescaped newline?"); if(zEsc==zz-1){ *zEsc = (unsigned char)' '; /* FIXME?: memmove() lnBuf content one byte to the left here ** to collapse backslash and newline into a single ** byte. Also consider collapsing all leading space on the ** next line. */ } zEsc = 0; *zz = (unsigned char)' '; break; default: zEsc = 0; break; } } t->args.argv[argc++] = zKwd; for( zz = zKwd; *zz; ++zz ){ if(isspace(*zz)){ *zz = 0; break; } } t->args.pKw = CmppKeyword_search((char const *)zKwd); if(!t->args.pKw){ fatal("Unknown keyword '%s' at line %u\n", (char const *)zKwd, tok->lineNo); } for( ++zz ; *zz && isspace(*zz); ++zz ){} if(t->args.pKw->bTokenize){ for( ; *zz; prevChar = *zz, ++zz ){ /* Split string into word-shaped tokens. ** TODO ?= quoted strings, for the sake of the ** #error keyword. */ if(isspace(*zz)){ assert(zz!=zKwd && "Leading space was stripped earlier."); *zz = 0; }else{ if(argc == (int)CmppArgs_Max){ fatal("Too many arguments @ line %u: %.*s", tok->lineNo, tokLen, tok->zBegin); }else if(zz>zKwd && !prevChar){ t->args.argv[argc++] = zz; } } } }else{ /* Treat rest of line as one token */ if(*zz) t->args.argv[argc++] = zz; } tok->ttype = t->args.pKw->ttype; if(g.doDebug>1){ for(i = 0; i < argc; ++i){ g_debug(0,("line %u arg #%d=%s\n", tok->lineNo, i, (char const *)t->args.argv[i])); } } t->args.argc = argc; }else{ t->args.pKw = 0; t->args.argc = 0; } return isDelim; } static void cmpp_kwd__err_prefix(CmppKeyword const * pKw, CmppTokenizer *t, char const *zPrefix){ g_stderr("%s%s%s @ %s line %u: ", zPrefix ? zPrefix : "", zPrefix ? ": " : "", pKw->zName, t->zName, t->token.lineNo); } /* Internal error reporting helper for cmpp_keyword_f() impls. */ static CMPP_NORETURN void cmpp_kwd__misuse(CmppKeyword const * pKw, CmppTokenizer *t, char const *zFmt, ...){ va_list va; cmpp_kwd__err_prefix(pKw, t, "Fatal error"); va_start(va, zFmt); fatalv(zFmt, va); va_end(va); } /* No-op cmpp_keyword_f() impl. */ static void cmpp_kwd_noop(CmppKeyword const * pKw, CmppTokenizer *t){ if(t || pKw){/*unused*/} } /* #error impl. */ static void cmpp_kwd_error(CmppKeyword const * pKw, CmppTokenizer *t){ if(CT_skip(t)) return; else{ assert(t->args.argc < 3); const char *zBegin = t->args.argc>1 ? (const char *)t->args.argv[1] : 0; cmpp_kwd__err_prefix(pKw, t, NULL); fatal("%s", zBegin ? zBegin : "(no additional info)"); } } /* Impl. for #define, #undef */ static void cmpp_kwd_define(CmppKeyword const * pKw, CmppTokenizer *t){ if(CT_skip(t)) return; if(t->args.argc<2){ cmpp_kwd__misuse(pKw, t, "Expecting one or more arguments"); }else{ int i = 1; void (*func)(const char *) = TT_Define==pKw->ttype ? db_define_add : db_define_rm; for( ; i < t->args.argc; ++i){ func( (char const *)t->args.argv[i] ); } } } /* Impl. for #if, #ifnot, #elif, #elifnot. */ static void cmpp_kwd_if(CmppKeyword const * pKw, CmppTokenizer *t){ int buul; CmppParseState tmpState = TS_Start; if(t->args.argc!=2){ cmpp_kwd__misuse(pKw, t, "Expecting exactly 1 argument"); } /*g_debug(0,("%s %s level %u pstate=%d\n", pKw->zName, (char const *)t->args.argv[1], t->level.ndx, (int)CT_pstate(t)));*/ switch(pKw->ttype){ case TT_Elif: case TT_ElifNot: switch(CT_pstate(t)){ case TS_If: break; case TS_IfPassed: CT_level(t).flags |= CmppLevel_F_ELIDE; return; default: goto misuse; } break; case TT_If: case TT_IfNot: CmppLevel_push(t); break; default: cmpp_kwd__misuse(pKw, t, "Unpexected keyword token type"); break; } buul = db_define_has((char const *)t->args.argv[1]); if(TT_IfNot==pKw->ttype || TT_ElifNot==pKw->ttype) buul = !buul; if(buul){ CT_pstate(t) = tmpState = TS_IfPassed; CT_skipLevel(t) = 0; }else{ CT_pstate(t) = TS_If /* also for TT_IfNot, TT_Elif, TT_ElifNot */; CT_skipLevel(t) = 1; } if(TT_If==pKw->ttype || TT_IfNot==pKw->ttype){ unsigned const lvlIf = t->level.ndx; CmppToken const lvlToken = CT_level(t).token; while(cmpp_next_keyword_line(t)){ cmpp_process_keyword(t); if(lvlIf > t->level.ndx){ assert(TT_EndIf == t->token.ttype); break; } if(TS_IfPassed==tmpState){ tmpState = TS_Start; t->level.stack[lvlIf].flags |= CmppLevel_F_ELIDE; } } if(lvlIf <= t->level.ndx){ cmpp_kwd__err_prefix(pKw, t, NULL); fatal("Input ended inside an unterminated %sif " "opened at [%s] line %u", g.zDelim, t->zName, lvlToken.lineNo); } } return; misuse: cmpp_kwd__misuse(pKw, t, "'%s' used out of context", pKw->zName); } /* Impl. for #else. */ static void cmpp_kwd_else(CmppKeyword const * pKw, CmppTokenizer *t){ if(t->args.argc>1){ cmpp_kwd__misuse(pKw, t, "Expecting no arguments"); } switch(CT_pstate(t)){ case TS_IfPassed: CT_skipLevel(t) = 1; break; case TS_If: CT_skipLevel(t) = 0; break; default: cmpp_kwd__misuse(pKw, t, "'%s' with no matching 'if'", pKw->zName); } /*g_debug(0,("else flags=0x%02x skipLevel=%u\n", CT_level(t).flags, CT_level(t).skipLevel));*/ CT_pstate(t) = TS_Else; } /* Impl. for #endif. */ static void cmpp_kwd_endif(CmppKeyword const * pKw, CmppTokenizer *t){ /* Maintenance reminder: we ignore all arguments after the endif ** to allow for constructs like: ** ** #endif // foo ** ** in a manner which does not require a specific comment style */ switch(CT_pstate(t)){ case TS_Else: case TS_If: case TS_IfPassed: break; default: cmpp_kwd__misuse(pKw, t, "'%s' with no matching 'if'", pKw->zName); } CmppLevel_pop(t); } /* Impl. for #include. */ static void cmpp_kwd_include(CmppKeyword const * pKw, CmppTokenizer *t){ char const * zFile; char * zResolved; if(CT_skip(t)) return; else if(t->args.argc!=2){ cmpp_kwd__misuse(pKw, t, "Expecting exactly 1 filename argument"); } zFile = (const char *)t->args.argv[1]; if(db_including_has(zFile)){ /* Note that different spellings of the same filename ** will elude this check, but that seems okay, as different ** spellings means that we're not re-running the exact same ** invocation. We might want some other form of multi-include ** protection, rather than this, however. There may well be ** sensible uses for recursion. */ cmpp_kwd__err_prefix(pKw, t, NULL); fatal("Recursive include of file: %s", zFile); } zResolved = db_include_search(zFile); if(zResolved){ db_including_add(zFile, t->zName, t->token.lineNo); cmpp_process_file(zResolved); db_include_rm(zFile); db_free(zResolved); }else{ cmpp_kwd__err_prefix(pKw, t, NULL); fatal("file not found: %s", zFile); } } /* Impl. for #pragma. */ static void cmpp_kwd_pragma(CmppKeyword const * pKw, CmppTokenizer *t){ const char * zArg; if(CT_skip(t)) return; else if(t->args.argc!=2){ cmpp_kwd__misuse(pKw, t, "Expecting one argument"); } zArg = (const char *)t->args.argv[1]; #define M(X) 0==strcmp(zArg,X) if(M("defines")){ sqlite3_stmt * q = 0; db_prepare(&q, "SELECT k FROM def ORDER BY k"); g_stderr("cmpp defines:\n"); while(SQLITE_ROW==db_step(q)){ int const n = sqlite3_column_bytes(q, 0); const char * z = (const char *)sqlite3_column_text(q, 0); g_stderr("\t%.*s\n", n, z); } db_finalize(q); }else{ cmpp_kwd__misuse(pKw, t, "Unknown pragma"); } #undef M } /* #stder impl. */ static void cmpp_kwd_stderr(CmppKeyword const * pKw, CmppTokenizer *t){ if(CT_skip(t)) return; else{ const char *zBegin = t->args.argc>1 ? (const char *)t->args.argv[1] : 0; if(zBegin){ g_stderr("%s:%u: %s\n", t->zName, t->token.lineNo, zBegin); }else{ g_stderr("%s:%u: (no %.*s%s argument)\n", t->zName, t->token.lineNo, g.nDelim, g.zDelim, pKw->zName); } } } #if 0 /* Impl. for dummy placeholder. */ static void cmpp_kwd_todo(CmppKeyword const * pKw, CmppTokenizer *t){ if(t){/*unused*/} g_debug(0,("TODO: keyword handler for %s\n", pKw->zName)); } #endif CmppKeyword aKeywords[] = { /* Keep these sorted by zName */ {"//", 2, 0, TT_Comment, cmpp_kwd_noop}, {"define", 6, 1, TT_Define, cmpp_kwd_define}, {"elif", 4, 1, TT_Elif, cmpp_kwd_if}, {"elifnot", 7, 1, TT_ElifNot, cmpp_kwd_if}, {"else", 4, 1, TT_Else, cmpp_kwd_else}, {"endif", 5, 0, TT_EndIf, cmpp_kwd_endif}, {"error", 4, 0, TT_Error, cmpp_kwd_error}, {"if", 2, 1, TT_If, cmpp_kwd_if}, {"ifnot", 5, 1, TT_IfNot, cmpp_kwd_if}, {"include", 7, 0, TT_Include, cmpp_kwd_include}, {"pragma", 6, 1, TT_Pragma, cmpp_kwd_pragma}, {"stderr", 6, 0, TT_Stderr, cmpp_kwd_stderr}, {"undef", 5, 1, TT_Undef, cmpp_kwd_define}, {0,0,TT_Invalid, 0} }; static int cmp_CmppKeyword(const void *p1, const void *p2){ char const * zName = (const char *)p1; CmppKeyword const * kw = (CmppKeyword const *)p2; return strcmp(zName, kw->zName); } CmppKeyword const * CmppKeyword_search(const char *zName){ return (CmppKeyword const *)bsearch(zName, &aKeywords[0], sizeof(aKeywords)/sizeof(aKeywords[0]) - 1, sizeof(aKeywords[0]), cmp_CmppKeyword); } void cmpp_process_keyword(CmppTokenizer * const t){ assert(t->args.pKw); assert(t->args.argc); t->args.pKw->xCall(t->args.pKw, t); t->args.pKw = 0; t->args.argc = 0; } void cmpp_process_file(const char * zName){ FileWrapper fw = FileWrapper_empty; CmppTokenizer ct = CmppTokenizer_empty; FileWrapper_open(&fw, zName, "r"); FileWrapper_slurp(&fw); g_debug(1,("Read %u byte(s) from [%s]\n", fw.nContent, fw.zName)); ct.zName = zName; ct.zBegin = fw.zContent; ct.zEnd = fw.zContent + fw.nContent; while(cmpp_next_keyword_line(&ct)){ cmpp_process_keyword(&ct); } FileWrapper_close(&fw); if(0!=ct.level.ndx){ CmppLevel * const lv = CmppLevel_get(&ct); fatal("Input ended inside an unterminated nested construct" "opened at [%s] line %u", zName, lv->token.lineNo); } } static void usage(int isErr){ FILE * const fOut = isErr ? stderr : stdout; fprintf(fOut, "Usage: %s [flags] [infile]\n" "Flags:\n", g.zArgv0); #define arg(F,D) fprintf(fOut," %s\n %s\n",F, D) arg("-f|--file FILE","Read input from FILE (default=- (stdin)).\n" " Alternately, the first non-flag argument is assumed to " "be the input file."); arg("-o|--outfile FILE","Send output to FILE (default=- (stdout))"); arg("-DXYZ","Define XYZ to true"); arg("-UXYZ","Undefine XYZ (equivalent to false)"); arg("-IXYZ","Add dir XYZ to include path"); arg("-d|--delimiter VALUE", "Set keyword delimiter to VALUE " "(default=" CMPP_DEFAULT_DELIM ")"); #undef arg fputs("",fOut); } int main(int argc, char const * const * argv){ int rc = 0; int i; int inclCount = 0; const char * zInfile = 0; #define M(X) (0==strcmp(X,zArg)) #define ISFLAG(X) else if(M(X)) #define ISFLAG2(X,Y) else if(M(X) || M(Y)) #define ARGVAL \ if(i+1>=argc) fatal("Missing value for flag '%s'", zArg); \ zArg = argv[++i] g.zArgv0 = argv[0]; atexit(cmpp_atexit); cmpp_initdb(); for(i = 1; i < argc; ++i){ char const * zArg = argv[i]; while('-'==*zArg) ++zArg; if(M("?") || M("help")) { usage(0); goto end; }else if('D'==*zArg){ ++zArg; if(!*zArg) fatal("Missing key for -D"); db_define_add(zArg); }else if('U'==*zArg){ ++zArg; if(!*zArg) fatal("Missing key for -U"); db_define_rm(zArg); }else if('I'==*zArg){ ++zArg; if(!*zArg) fatal("Missing directory for -I"); db_include_dir_add(zArg); ++inclCount; } ISFLAG2("o","outfile"){ ARGVAL; if(g.out.zName) fatal("Cannot use -o more than once."); g.out.zName = zArg; } ISFLAG2("f","file"){ ARGVAL; do_infile: if(zInfile) fatal("Cannot use -i more than once."); zInfile = zArg; } ISFLAG2("d","delimiter"){ ARGVAL; g.zDelim = zArg; g.nDelim = (unsigned short)strlen(zArg); if(!g.nDelim) fatal("Keyword delimiter may not be empty."); } ISFLAG("debug"){ ++g.doDebug; }else if(!zInfile){ goto do_infile; }else{ fatal("Unhandled flag: %s", argv[i]); } } if(!zInfile) zInfile = "-"; if(!g.out.zName) g.out.zName = "-"; if(!inclCount) db_include_dir_add("."); FileWrapper_open(&g.out, g.out.zName, "w"); cmpp_process_file(zInfile); FileWrapper_close(&g.out); end: return rc ? EXIT_FAILURE : EXIT_SUCCESS; } #undef CT_level #undef CT_pstate #undef CT_skipLevel #undef CT_skip #undef CLvl_skip |
Changes to ext/wasm/common/emscripten.css.
|
| | | 1 2 3 4 5 6 7 8 | /* emscripten-related styling, used during the module load/intialization processes... */ .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } div.emscripten { text-align: center; } div.emscripten_border { border: 1px solid black; } #module-spinner { overflow: visible; } #module-spinner > * { margin-top: 1em; } |
︙ | ︙ |
Changes to ext/wasm/common/testing.css.
︙ | ︙ | |||
57 58 59 60 61 62 63 | display: flex; flex-direction: column; font-family: monospace; } #test-output.reverse { flex-direction: column-reverse; } label[for] { cursor: pointer } | > > > > > > | 57 58 59 60 61 62 63 64 65 66 67 68 69 | display: flex; flex-direction: column; font-family: monospace; } #test-output.reverse { flex-direction: column-reverse; } label[for] { cursor: pointer } h1 { border-radius: 0.25em; padding: 0.15em 0.25em; } h1:first-of-type {margin: 0 0 0.5em 0;} |
Changes to ext/wasm/common/whwasmutil.js.
︙ | ︙ | |||
241 242 243 244 245 246 247 248 249 250 251 252 253 254 | /** Used by scopedAlloc() and friends. */ cache.scopedAlloc = []; cache.utf8Decoder = new TextDecoder(); cache.utf8Encoder = new TextEncoder('utf-8'); /** If (cache.heapSize !== cache.memory.buffer.byteLength), i.e. if the heap has grown since the last call, updates cache.HEAPxyz. Returns the cache object. */ const heapWrappers = function(){ | > > > > > > > > > > > > > > > > > > > | 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 | /** Used by scopedAlloc() and friends. */ cache.scopedAlloc = []; cache.utf8Decoder = new TextDecoder(); cache.utf8Encoder = new TextEncoder('utf-8'); /** For the given IR-like string in the set ('i8', 'i16', 'i32', 'f32', 'float', 'i64', 'f64', 'double', '*'), or any string value ending in '*', returns the sizeof for that value (target.ptrSizeof in the latter case). For any other value, it returns the undefined value. */ target.sizeofIR = (n)=>{ switch(n){ case 'i8': return 1; case 'i16': return 2; case 'i32': case 'f32': case 'float': return 4; case 'i64': case 'f64': case 'double': return 8; case '*': return ptrSizeof; default: return (''+n).endsWith('*') ? ptrSizeof : undefined; } }; /** If (cache.heapSize !== cache.memory.buffer.byteLength), i.e. if the heap has grown since the last call, updates cache.HEAPxyz. Returns the cache object. */ const heapWrappers = function(){ |
︙ | ︙ | |||
312 313 314 315 316 317 318 | this heap, so do not hold a reference longer than needed and do not use a reference after any operation which may allocate. Instead, re-fetch the reference by calling this function again. Throws if passed an invalid n. | | | | < < | | 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 | this heap, so do not hold a reference longer than needed and do not use a reference after any operation which may allocate. Instead, re-fetch the reference by calling this function again. Throws if passed an invalid n. Pedantic side note: the name "heap" is a bit of a misnomer. In a WASM environment, the stack and heap memory are all accessed via the same view(s) of the memory. */ target.heapForSize = function(n,unsigned = true){ let ctor; const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength) ? cache : heapWrappers(); switch(n){ case Int8Array: return c.HEAP8; case Uint8Array: return c.HEAP8U; case Int16Array: return c.HEAP16; case Uint16Array: return c.HEAP16U; case Int32Array: return c.HEAP32; case Uint32Array: return c.HEAP32U; |
︙ | ︙ | |||
357 358 359 360 361 362 363 | - Use `__indirect_function_table` as the import name for the table, which is what LLVM does. */ }; /** Given a function pointer, returns the WASM function table entry | | > | 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 | - Use `__indirect_function_table` as the import name for the table, which is what LLVM does. */ }; /** Given a function pointer, returns the WASM function table entry if found, else returns a falsy value: undefined if fptr is out of range or null if it's in range but the table entry is empty. */ target.functionEntry = function(fptr){ const ft = target.functionTable(); return fptr < ft.length ? ft.get(fptr) : undefined; }; /** |
︙ | ︙ | |||
444 445 446 447 448 449 450 | /** Returns the IR value for the given letter or throws if the letter is invalid. */ letterType: (x)=>f._.sigTypes[x] || toss("Invalid signature letter:",x), /** Returns an object describing the result type and parameter type(s) of the given function signature, or throws if the signature is invalid. */ /******** // only valid for use with the WebAssembly.Function ctor, which | | | 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 | /** Returns the IR value for the given letter or throws if the letter is invalid. */ letterType: (x)=>f._.sigTypes[x] || toss("Invalid signature letter:",x), /** Returns an object describing the result type and parameter type(s) of the given function signature, or throws if the signature is invalid. */ /******** // only valid for use with the WebAssembly.Function ctor, which // is not yet documented on MDN. sigToWasm: function(sig){ const rc = {parameters:[], results: []}; if('v'!==sig[0]) rc.results.push(f.sigTypes(sig[0])); for(const x of f._.sigParams(sig)){ rc.parameters.push(f._.typeCodes(x)); } return rc; |
︙ | ︙ | |||
492 493 494 495 496 497 498 | 0x01, 0x01, 0x66, 0x00, 0x00 ); return (new WebAssembly.Instance( new WebAssembly.Module(new Uint8Array(wasmCode)), { e: { f: func } })).exports['f']; }/*jsFuncToWasm()*/; | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 | 0x01, 0x01, 0x66, 0x00, 0x00 ); return (new WebAssembly.Instance( new WebAssembly.Module(new Uint8Array(wasmCode)), { e: { f: func } })).exports['f']; }/*jsFuncToWasm()*/; /** Documented as target.installFunction() except for the 3rd argument: if truthy, the newly-created function pointer is stashed in the current scoped-alloc scope and will be cleaned up at the matching scopedAllocPop(), else it is not stashed there. */ const __installFunction = function f(func, sig, scoped){ if(scoped && !cache.scopedAlloc.length){ toss("No scopedAllocPush() scope is active."); } if('string'===typeof func){ const x = sig; sig = func; func = x; } if('string'!==typeof sig || !(func instanceof Function)){ toss("Invalid arguments: expecting (function,signature) "+ "or (signature,function)."); } const ft = target.functionTable(); const oldLen = ft.length; let ptr; while(cache.freeFuncIndexes.length){ ptr = cache.freeFuncIndexes.pop(); if(ft.get(ptr)){ /* Table was modified via a different API */ ptr = null; continue; }else{ break; } } if(!ptr){ ptr = oldLen; ft.grow(1); } try{ /*this will only work if func is a WASM-exported function*/ ft.set(ptr, func); if(scoped){ cache.scopedAlloc[cache.scopedAlloc.length-1].push(ptr); } return ptr; }catch(e){ if(!(e instanceof TypeError)){ if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen); throw e; } } // It's not a WASM-exported function, so compile one... try { const fptr = target.jsFuncToWasm(func, sig); ft.set(ptr, fptr); if(scoped){ cache.scopedAlloc[cache.scopedAlloc.length-1].push(ptr); } }catch(e){ if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen); throw e; } return ptr; }; /** Expects a JS function and signature, exactly as for this.jsFuncToWasm(). It uses that function to create a WASM-exported function, installs that function to the next available slot of this.functionTable(), and returns the function's index in that table (which acts as a pointer to that function). The returned pointer can be passed to |
︙ | ︙ | |||
524 525 526 527 528 529 530 | _primarily_ in that it does not share that function's undocumented behavior of reusing a function if it's passed to addFunction() more than once, which leads to uninstallFunction() breaking clients which do not take care to avoid that case: https://github.com/emscripten-core/emscripten/issues/17323 */ | | < < | < < < < > > | > | < < < < | < | < < < < < < < < < | | < < < < < < < < < > | < < < < < < > > > > > > > > > > | | | | | | | | > > > > | | | | | > > | | > | < < < | > > | > > > > | | > > | > | > | > | | | | | > > | | | | | > | < < > | < > > | | | > > > > > | > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > | | 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 | _primarily_ in that it does not share that function's undocumented behavior of reusing a function if it's passed to addFunction() more than once, which leads to uninstallFunction() breaking clients which do not take care to avoid that case: https://github.com/emscripten-core/emscripten/issues/17323 */ target.installFunction = (func, sig)=>__installFunction(func, sig, false); /** EXPERIMENTAL! DO NOT USE IN CLIENT CODE! Works exactly like installFunction() but requires that a scopedAllocPush() is active and uninstalls the given function when that alloc scope is popped via scopedAllocPop(). This is used for implementing JS/WASM function bindings which should only persist for the life of a call into a single C-side function. */ target.scopedInstallFunction = (func, sig)=>__installFunction(func, sig, true); /** Requires a pointer value previously returned from this.installFunction(). Removes that function from the WASM function table, marks its table slot as free for re-use, and returns that function. It is illegal to call this before installFunction() has been called and results are undefined if ptr was not returned by that function. The returned function may be passed back to installFunction() to reinstall it. To simplify certain use cases, if passed a falsy non-0 value (noting that 0 is a valid function table index), this function has no side effects and returns undefined. */ target.uninstallFunction = function(ptr){ if(!ptr && 0!==ptr) return undefined; const fi = cache.freeFuncIndexes; const ft = target.functionTable(); fi.push(ptr); const rc = ft.get(ptr); ft.set(ptr, null); return rc; }; /** Given a WASM heap memory address and a data type name in the form (i8, i16, i32, i64, float (or f32), double (or f64)), this fetches the numeric value from that address and returns it as a number or, for the case of type='i64', a BigInt (noting that that type triggers an exception if this.bigIntEnabled is falsy). Throws if given an invalid type. If the first argument is an array, it is treated as an array of addresses and the result is an array of the values from each of those address, using the same 2nd argument for determining the value type to fetch. As a special case, if type ends with a `*`, it is considered to be a pointer type and is treated as the WASM numeric type appropriate for the pointer size (`i32`). While likely not obvious, this routine and its poke() counterpart are how pointer-to-value _output_ parameters in WASM-compiled C code can be interacted with: ``` const ptr = alloc(4); poke(ptr, 0, 'i32'); // clear the ptr's value aCFuncWithOutputPtrToInt32Arg( ptr ); // e.g. void foo(int *x); const result = peek(ptr, 'i32'); // fetch ptr's value dealloc(ptr); ``` scopedAlloc() and friends can be used to make handling of `ptr` safe against leaks in the case of an exception: ``` let result; const scope = scopedAllocPush(); try{ const ptr = scopedAlloc(4); poke(ptr, 0, 'i32'); aCFuncWithOutputPtrArg( ptr ); result = peek(ptr, 'i32'); }finally{ scopedAllocPop(scope); } ``` As a rule poke() must be called to set (typically zero out) the pointer's value, else it will contain an essentially random value. ACHTUNG: calling this often, e.g. in a loop, can have a noticably painful impact on performance. Rather than doing so, use heapForSize() to fetch the heap object and read directly from it. See: poke() */ target.peek = function f(ptr, type='i8'){ if(type.endsWith('*')) type = ptrIR; const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength) ? cache : heapWrappers(); const list = Array.isArray(ptr) ? [] : undefined; let rc; do{ if(list) ptr = arguments[0].shift(); switch(type){ case 'i1': case 'i8': rc = c.HEAP8[ptr>>0]; break; case 'i16': rc = c.HEAP16[ptr>>1]; break; case 'i32': rc = c.HEAP32[ptr>>2]; break; case 'float': case 'f32': rc = c.HEAP32F[ptr>>2]; break; case 'double': case 'f64': rc = Number(c.HEAP64F[ptr>>3]); break; case 'i64': if(target.bigIntEnabled){ rc = BigInt(c.HEAP64[ptr>>3]); break; } /* fallthru */ default: toss('Invalid type for peek():',type); } if(list) list.push(rc); }while(list && arguments[0].length); return list || rc; }; /** The counterpart of peek(), this sets a numeric value at the given WASM heap address, using the type to define how many bytes are written. Throws if given an invalid type. See peek() for details about the type argument. If the 3rd argument ends with `*` then it is treated as a pointer type and this function behaves as if the 3rd argument were `i32`. If the first argument is an array, it is treated like a list of pointers and the given value is written to each one. Returns `this`. (Prior to 2022-12-09 it returns this function.) ACHTUNG: calling this often, e.g. in a loop, can have a noticably painful impact on performance. Rather than doing so, use heapForSize() to fetch the heap object and assign directly to it or use the heap's set() method. */ target.poke = function(ptr, value, type='i8'){ if (type.endsWith('*')) type = ptrIR; const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength) ? cache : heapWrappers(); for(const p of (Array.isArray(ptr) ? ptr : [ptr])){ switch (type) { case 'i1': case 'i8': c.HEAP8[p>>0] = value; continue; case 'i16': c.HEAP16[p>>1] = value; continue; case 'i32': c.HEAP32[p>>2] = value; continue; case 'float': case 'f32': c.HEAP32F[p>>2] = value; continue; case 'double': case 'f64': c.HEAP64F[p>>3] = value; continue; case 'i64': if(c.HEAP64){ c.HEAP64[p>>3] = BigInt(value); continue; } /* fallthru */ default: toss('Invalid type for poke(): ' + type); } } return this; }; /** Convenience form of peek() intended for fetching pointer-to-pointer values. If passed a single non-array argument it returns the value of that one pointer address. If passed multiple arguments, or a single array of arguments, it returns an array of their values. */ target.peekPtr = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), ptrIR ); /** A variant of poke() intended for setting pointer-to-pointer values. Its differences from poke() are that (1) it defaults to a value of 0 and (2) it always writes to the pointer-sized heap view. */ target.pokePtr = (ptr, value=0)=>target.poke(ptr, value, ptrIR); /** Convenience form of peek() intended for fetching i8 values. If passed a single non-array argument it returns the value of that one pointer address. If passed multiple arguments, or a single array of arguments, it returns an array of their values. */ target.peek8 = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'i8' ); /** Convience form of poke() intended for setting individual bytes. Its difference from poke() is that it always writes to the i8-sized heap view. */ target.poke8 = (ptr, value)=>target.poke(ptr, value, 'i8'); /** i16 variant of peek8(). */ target.peek16 = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'i16' ); /** i16 variant of poke8(). */ target.poke16 = (ptr, value)=>target.poke(ptr, value, 'i16'); /** i32 variant of peek8(). */ target.peek32 = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'i32' ); /** i32 variant of poke8(). */ target.poke32 = (ptr, value)=>target.poke(ptr, value, 'i32'); /** i64 variant of peek8(). Will throw if this build is not configured for BigInt support. */ target.peek64 = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'i64' ); /** i64 variant of poke8(). Will throw if this build is not configured for BigInt support. Note that this returns a BigInt-type value, not a Number-type value. */ target.poke64 = (ptr, value)=>target.poke(ptr, value, 'i64'); /** f32 variant of peek8(). */ target.peek32f = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'f32' ); /** f32 variant of poke8(). */ target.poke32f = (ptr, value)=>target.poke(ptr, value, 'f32'); /** f64 variant of peek8(). */ target.peek64f = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'f64' ); /** f64 variant of poke8(). */ target.poke64f = (ptr, value)=>target.poke(ptr, value, 'f64'); /** Deprecated alias for getMemValue() */ target.getMemValue = target.peek; /** Deprecated alias for peekPtr() */ target.getPtrValue = target.peekPtr; /** Deprecated alias for poke() */ target.setMemValue = target.poke; /** Deprecated alias for pokePtr() */ target.setPtrValue = target.pokePtr; /** Returns true if the given value appears to be legal for use as a WASM pointer value. Its _range_ of values is not (cannot be) validated except to ensure that it is a 32-bit integer with a value of 0 or greater. Likewise, it cannot verify whether the value actually refers to allocated memory in the WASM heap. |
︙ | ︙ | |||
721 722 723 724 725 726 727 | */ target.isPtr = target.isPtr32; /** Expects ptr to be a pointer into the WASM heap memory which refers to a NUL-terminated C-style string encoded as UTF-8. Returns the length, in bytes, of the string, as for `strlen(3)`. | | > | | | 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 | */ target.isPtr = target.isPtr32; /** Expects ptr to be a pointer into the WASM heap memory which refers to a NUL-terminated C-style string encoded as UTF-8. Returns the length, in bytes, of the string, as for `strlen(3)`. As a special case, if !ptr or if it's not a pointer then it returns `null`. Throws if ptr is out of range for target.heap8u(). */ target.cstrlen = function(ptr){ if(!ptr || !target.isPtr(ptr)) return null; const h = heapWrappers().HEAP8U; let pos = ptr; for( ; h[pos] !== 0; ++pos ){} return pos - ptr; }; /** Internal helper to use in operations which need to distinguish |
︙ | ︙ | |||
749 750 751 752 753 754 755 | }; /** Expects ptr to be a pointer into the WASM heap memory which refers to a NUL-terminated C-style string encoded as UTF-8. This function counts its byte length using cstrlen() then returns a JS-format string representing its contents. As a special case, if | | | | 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 | }; /** Expects ptr to be a pointer into the WASM heap memory which refers to a NUL-terminated C-style string encoded as UTF-8. This function counts its byte length using cstrlen() then returns a JS-format string representing its contents. As a special case, if ptr is falsy or not a pointer, `null` is returned. */ target.cstrToJs = function(ptr){ const n = target.cstrlen(ptr); return n ? __utf8Decode(heapWrappers().HEAP8U, ptr, ptr+n) : (null===n ? n : ""); }; /** Given a JS string, this function returns its UTF-8 length in bytes. Returns null if str is not a string. |
︙ | ︙ | |||
938 939 940 941 942 943 944 | "required by",funcName+"()."); } }; const __allocCStr = function(jstr, returnWithLength, allocator, funcName){ __affirmAlloc(target, funcName); if('string'!==typeof jstr) return null; | > | | | | > > > > > > > > | 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 | "required by",funcName+"()."); } }; const __allocCStr = function(jstr, returnWithLength, allocator, funcName){ __affirmAlloc(target, funcName); if('string'!==typeof jstr) return null; if(0){/* older impl, possibly more widely compatible? */ const n = target.jstrlen(jstr), ptr = allocator(n+1); target.jstrcpy(jstr, target.heap8u(), ptr, n+1, true); return returnWithLength ? [ptr, n] : ptr; }else{/* newer, (probably) faster and (certainly) simpler impl */ const u = cache.utf8Encoder.encode(jstr), ptr = allocator(u.length+1), heap = heapWrappers().HEAP8U; heap.set(u, ptr); heap[ptr + u.length] = 0; return returnWithLength ? [ptr, u.length] : ptr; } }; /** Uses target.alloc() to allocate enough memory for jstrlen(jstr)+1 bytes of memory, copies jstr to that memory using jstrcpy(), NUL-terminates it, and returns the pointer to that C-string. Ownership of the pointer is transfered to the caller, who must |
︙ | ︙ | |||
1031 1032 1033 1034 1035 1036 1037 | __affirmAlloc(target, 'scopedAllocPop'); const n = arguments.length ? cache.scopedAlloc.indexOf(state) : cache.scopedAlloc.length-1; if(n<0) toss("Invalid state object for scopedAllocPop()."); if(0===arguments.length) state = cache.scopedAlloc[n]; cache.scopedAlloc.splice(n,1); | | > > > > > > | 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 | __affirmAlloc(target, 'scopedAllocPop'); const n = arguments.length ? cache.scopedAlloc.indexOf(state) : cache.scopedAlloc.length-1; if(n<0) toss("Invalid state object for scopedAllocPop()."); if(0===arguments.length) state = cache.scopedAlloc[n]; cache.scopedAlloc.splice(n,1); for(let p; (p = state.pop()); ){ if(target.functionEntry(p)){ //console.warn("scopedAllocPop() uninstalling transient function",p); target.uninstallFunction(p); } else target.dealloc(p); } }; /** Allocates n bytes of memory using this.alloc() and records that fact in the state for the most recent call of scopedAllocPush(). Ownership of the memory is given to scopedAllocPop(), which will clean it up when it is called. The memory _must not_ be |
︙ | ︙ | |||
1077 1078 1079 1080 1081 1082 1083 | */ target.scopedAllocCString = (jstr, returnWithLength=false)=>__allocCStr(jstr, returnWithLength, target.scopedAlloc, 'scopedAllocCString()'); // impl for allocMainArgv() and scopedAllocMainArgv(). const __allocMainArgv = function(isScoped, list){ | < | | > | | | | > | | | > > > > > | > > > > > > > > > > > > > > > > > > > > > | | | 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 | */ target.scopedAllocCString = (jstr, returnWithLength=false)=>__allocCStr(jstr, returnWithLength, target.scopedAlloc, 'scopedAllocCString()'); // impl for allocMainArgv() and scopedAllocMainArgv(). const __allocMainArgv = function(isScoped, list){ const pList = target[ isScoped ? 'scopedAlloc' : 'alloc' ]((list.length + 1) * target.ptrSizeof); let i = 0; list.forEach((e)=>{ target.pokePtr(pList + (target.ptrSizeof * i++), target[ isScoped ? 'scopedAllocCString' : 'allocCString' ](""+e)); }); target.pokePtr(pList + (target.ptrSizeof * i), 0); return pList; }; /** Creates an array, using scopedAlloc(), suitable for passing to a C-level main() routine. The input is a collection with a length property and a forEach() method. A block of memory (list.length+1) entries long is allocated and each pointer-sized block of that memory is populated with a scopedAllocCString() conversion of the (""+value) of each element, with the exception that the final entry is a NULL pointer. Returns a pointer to the start of the list, suitable for passing as the 2nd argument to a C-style main() function. Throws if scopedAllocPush() is not active. Design note: the returned array is allocated with an extra NULL pointer entry to accommodate certain APIs, but client code which does not need that functionality should treat the returned array as list.length entries long. */ target.scopedAllocMainArgv = (list)=>__allocMainArgv(true, list); /** Identical to scopedAllocMainArgv() but uses alloc() instead of scopedAlloc(). */ target.allocMainArgv = (list)=>__allocMainArgv(false, list); /** Expects to be given a C-style string array and its length. It returns a JS array of strings and/or nulls: any entry in the pArgv array which is NULL results in a null entry in the result array. If argc is 0 then an empty array is returned. Results are undefined if any entry in the first argc entries of pArgv are neither 0 (NULL) nor legal UTF-format C strings. To be clear, the expected C-style arguments to be passed to this function are `(int, char **)` (optionally const-qualified). */ target.cArgvToJs = (argc, pArgv)=>{ const list = []; for(let i = 0; i < argc; ++i){ const arg = target.peekPtr(pArgv + (target.ptrSizeof * i)); list.push( arg ? target.cstrToJs(arg) : null ); } return list; }; /** Wraps function call func() in a scopedAllocPush() and scopedAllocPop() block, such that all calls to scopedAlloc() and friends from within that call will have their memory freed automatically when func() returns. If func throws or propagates an exception, the scope is still popped, otherwise it returns the result of calling func(). */ target.scopedAllocCall = function(func){ target.scopedAllocPush(); try{ return func() } finally{ target.scopedAllocPop() } }; /** Internal impl for allocPtr() and scopedAllocPtr(). */ const __allocPtr = function(howMany, safePtrSize, method){ __affirmAlloc(target, method); const pIr = safePtrSize ? 'i64' : ptrIR; let m = target[method](howMany * (safePtrSize ? 8 : ptrSizeof)); target.poke(m, 0, pIr) if(1===howMany){ return m; } const a = [m]; for(let i = 1; i < howMany; ++i){ m += (safePtrSize ? 8 : ptrSizeof); a[i] = m; target.poke(m, 0, pIr); } return a; }; /** Allocates one or more pointers as a single chunk of memory and zeroes them out. |
︙ | ︙ | |||
1168 1169 1170 1171 1172 1173 1174 | value to dealloc(). The others are part of the same memory chunk and must not be freed separately. The reason for the 2nd argument is.. When one of the returned pointers will refer to a 64-bit value, e.g. a double or int64, an that value must be written or fetched, | | | 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 | value to dealloc(). The others are part of the same memory chunk and must not be freed separately. The reason for the 2nd argument is.. When one of the returned pointers will refer to a 64-bit value, e.g. a double or int64, an that value must be written or fetched, e.g. using poke() or peek(), it is important that the pointer in question be aligned to an 8-byte boundary or else it will not be fetched or written properly and will corrupt or read neighboring memory. It is only safe to pass false when the client code is certain that it will only get/fetch 4-byte values (or smaller). */ target.allocPtr = |
︙ | ︙ | |||
1195 1196 1197 1198 1199 1200 1201 | */ target.xGet = function(name){ return target.exports[name] || toss("Cannot find exported symbol:",name); }; const __argcMismatch = (f,n)=>toss(f+"() requires",n,"argument(s)."); | | | 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 | */ target.xGet = function(name){ return target.exports[name] || toss("Cannot find exported symbol:",name); }; const __argcMismatch = (f,n)=>toss(f+"() requires",n,"argument(s)."); /** Looks up a WASM-exported function named fname from target.exports. If found, it is called, passed all remaining arguments, and its return value is returned to xCall's caller. If not found, an exception is thrown. This function does no conversion of argument or return types, but see xWrap() and xCallWrapped() for variants which do. |
︙ | ︙ | |||
1224 1225 1226 1227 1228 1229 1230 | : f.apply(null, args); }; /** State for use with xWrap() */ cache.xWrap = Object.create(null); | | | | > | > | > | | | > > | | > > > > > | | > > > | > > | | | | > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | < | > | > > | > | > > > > > > > > | > > > > > | > > > > > > > > > > > > | > > > > > > > > > > > > | > > > > > > > > > > > > > > > > | > | < < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | < | | | | < > | < | > > | > | > > > > > > > | > > > > | > > > > > | > > | | | | > > > > | > > > > > > > > | > > > > | | < < < < < | < | < < < < < | | | | > | | > > > > > > | | > > > | | > > > > | | | | | | | | | > > > > > | | | > | > > > > > | | > > > | > > | | | > > > > > > > > > > > > > > > > > | > | | | | | | 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 | : f.apply(null, args); }; /** State for use with xWrap() */ cache.xWrap = Object.create(null); cache.xWrap.convert = Object.create(null); /** Map of type names to argument conversion functions. */ cache.xWrap.convert.arg = new Map; /** Map of type names to return result conversion functions. */ cache.xWrap.convert.result = new Map; const xArg = cache.xWrap.convert.arg, xResult = cache.xWrap.convert.result; if(target.bigIntEnabled){ xArg.set('i64', (i)=>BigInt(i)); } const __xArgPtr = 'i32' === ptrIR ? ((i)=>(i | 0)) : ((i)=>(BigInt(i) | BigInt(0))); xArg.set('i32', __xArgPtr ) .set('i16', (i)=>((i | 0) & 0xFFFF)) .set('i8', (i)=>((i | 0) & 0xFF)) .set('f32', (i)=>Number(i).valueOf()) .set('float', xArg.get('f32')) .set('f64', xArg.get('f32')) .set('double', xArg.get('f64')) .set('int', xArg.get('i32')) .set('null', (i)=>i) .set(null, xArg.get('null')) .set('**', __xArgPtr) .set('*', __xArgPtr); xResult.set('*', __xArgPtr) .set('pointer', __xArgPtr) .set('number', (v)=>Number(v)) .set('void', (v)=>undefined) .set('null', (v)=>v) .set(null, xResult.get('null')); { /* Copy certain xArg[...] handlers to xResult[...] and add pointer-style variants of them. */ const copyToResult = ['i8', 'i16', 'i32', 'int', 'f32', 'float', 'f64', 'double']; if(target.bigIntEnabled) copyToResult.push('i64'); const adaptPtr = xArg.get(ptrIR); for(const t of copyToResult){ xArg.set(t+'*', adaptPtr); xResult.set(t+'*', adaptPtr); xResult.set(t, (xArg.get(t) || toss("Missing arg converter:",t))); } } /** In order for args of type string to work in various contexts in the sqlite3 API, we need to pass them on as, variably, a C-string or a pointer value. Thus for ARGs of type 'string' and '*'/'pointer' we behave differently depending on whether the argument is a string or not: - If v is a string, scopeAlloc() a new C-string from it and return that temp string's pointer. - Else return the value from the arg adapter defined for ptrIR. TODO? Permit an Int8Array/Uint8Array and convert it to a string? Would that be too much magic concentrated in one place, ready to backfire? We handle that at the client level in sqlite3 with a custom argument converter. */ const __xArgString = function(v){ if('string'===typeof v) return target.scopedAllocCString(v); return v ? __xArgPtr(v) : null; }; xArg.set('string', __xArgString) .set('utf8', __xArgString) .set('pointer', __xArgString); //xArg.set('*', __xArgString); xResult.set('string', (i)=>target.cstrToJs(i)) .set('utf8', xResult.get('string')) .set('string:dealloc', (i)=>{ try { return i ? target.cstrToJs(i) : null } finally{ target.dealloc(i) } }) .set('utf8:dealloc', xResult.get('string:dealloc')) .set('json', (i)=>JSON.parse(target.cstrToJs(i))) .set('json:dealloc', (i)=>{ try{ return i ? JSON.parse(target.cstrToJs(i)) : null } finally{ target.dealloc(i) } }); /** Internal-use-only base class for FuncPtrAdapter and potentially additional stateful argument adapter classes. Note that its main interface (convertArg()) is strictly internal, not to be exposed to client code, as it may still need re-shaping. Only the constructors of concrete subclasses should be exposed to clients, and those in such a way that does not hinder internal redesign of the convertArg() interface. */ const AbstractArgAdapter = class { constructor(opt){ this.name = opt.name || 'unnamed adapter'; } /** Gets called via xWrap() to "convert" v to whatever type this specific class supports. argIndex is the argv index of _this_ argument in the being-xWrap()'d call. argv is the current argument list undergoing xWrap() argument conversion. argv entries to the left of argIndex will have already undergone transformation and those to the right will not have (they will have the values the client-level code passed in, awaiting conversion). The RHS indexes must never be relied upon for anything because their types are indeterminate, whereas the LHS values will be WASM-compatible values by the time this is called. */ convertArg(v,argv,argIndex){ toss("AbstractArgAdapter must be subclassed."); } }; /** An attempt at adding function pointer conversion support to xWrap(). This type is recognized by xWrap() as a proxy for converting a JS function to a C-side function, either permanently, for the duration of a single call into the C layer, or semi-contextual, where it may keep track of a single binding for a given context and uninstall the binding if it's replaced. The constructor requires an options object with these properties: - name (optional): string describing the function binding. This is solely for debugging and error-reporting purposes. If not provided, an empty string is assumed. - signature: a function signature string compatible with jsFuncToWasm(). - bindScope (string): one of ('transient', 'context', 'singleton'). Bind scopes are: - 'transient': it will convert JS functions to WASM only for the duration of the xWrap()'d function call, using scopedInstallFunction(). Before that call returns, the WASM-side binding will be uninstalled. - 'singleton': holds one function-pointer binding for this instance. If it's called with a different function pointer, it uninstalls the previous one after converting the new value. This is only useful for use with "global" functions which do not rely on any state other than this function pointer. If the being-converted function pointer is intended to be mapped to some sort of state object (e.g. an `sqlite3*`) then "context" (see below) is the proper mode. - 'context': similar to singleton mode but for a given "context", where the context is a key provided by the user and possibly dependent on a small amount of call-time context. This mode is the default if bindScope is _not_ set but a property named contextKey (described below) is. - callProxy (function): if set, this must be a function which will act as a proxy for any "converted" JS function. It is passed the being-converted function value and must return either that function or a function which acts on its behalf. The returned function will be the one which gets installed into the WASM function table. The proxy must perform any required argument conversion (noting that it will be called from C code, so will receive C-format arguments) before passing them on to the being-converted function. Whether or not the proxy itself must return a value depends on the context. If it does, it must be a WASM-friendly value, as it will be returning from a call made from native code. - contextKey (function): is only used if bindScope is 'context' or if bindScope is not set and this function is, in which case 'context' is assumed. This function gets bound to this object, so its "this" is this object. It gets passed (argv,argIndex), where argIndex is the index of _this_ function pointer in its _wrapping_ function's arguments and argv is the _current_ still-being-xWrap()-processed args array. All arguments to the left of argIndex will have been processed by xWrap() by the time this is called. argv[argIndex] will be the value the user passed in to the xWrap()'d function for the argument this FuncPtrAdapter is mapped to. Arguments to the right of argv[argIndex] will not yet have been converted before this is called. The function must return a key which uniquely identifies this function mapping context for _this_ FuncPtrAdapter instance (other instances are not considered), taking into account that C functions often take some sort of state object as one or more of their arguments. As an example, if the xWrap()'d function takes `(int,T*,functionPtr,X*)` and this FuncPtrAdapter is the argv[2]nd arg, contextKey(argv,2) might return 'T@'+argv[1], or even just argv[1]. Note, however, that the (X*) argument will not yet have been processed by the time this is called and should not be used as part of that key because its pre-conversion data type might be unpredictable. Similarly, care must be taken with C-string-type arguments: those to the left in argv will, when this is called, be WASM pointers, whereas those to the right might (and likely do) have another data type. When using C-strings in keys, never use their pointers in the key because most C-strings in this constellation are transient. Yes, that ^^^ is quite awkward, but it's what we have. The constructor only saves the above state for later, and does not actually bind any functions. Its convertArg() method is called via xWrap() to perform any bindings. Shortcomings: - These "reverse" bindings, i.e. calling into a JS-defined function from a WASM-defined function (the generated proxy wrapper), lack all type conversion support. That means, for example, that... - Function pointers which include C-string arguments may still need a level of hand-written wrappers around them, depending on how they're used, in order to provide the client with JS strings. Alternately, clients will need to perform such conversions on their own, e.g. using cstrtojs(). Or maybe we can find a way to perform such conversions here, via addition of an xWrap()-style function signature to the options argument. */ xArg.FuncPtrAdapter = class FuncPtrAdapter extends AbstractArgAdapter { constructor(opt) { super(opt); if(xArg.FuncPtrAdapter.warnOnUse){ console.warn('xArg.FuncPtrAdapter is an internal-only API', 'and is not intended to be invoked from', 'client-level code. Invoked with:',opt); } this.signature = opt.signature; if(opt.contextKey instanceof Function){ this.contextKey = opt.contextKey; if(!opt.bindScope) opt.bindScope = 'context'; } this.bindScope = opt.bindScope || toss("FuncPtrAdapter options requires a bindScope (explicit or implied)."); if(FuncPtrAdapter.bindScopes.indexOf(opt.bindScope)<0){ toss("Invalid options.bindScope ("+opt.bindMod+") for FuncPtrAdapter. "+ "Expecting one of: ("+FuncPtrAdapter.bindScopes.join(', ')+')'); } this.isTransient = 'transient'===this.bindScope; this.isContext = 'context'===this.bindScope; this.singleton = ('singleton'===this.bindScope) ? [] : undefined; //console.warn("FuncPtrAdapter()",opt,this); this.callProxy = (opt.callProxy instanceof Function) ? opt.callProxy : undefined; } /** If true, the constructor emits a warning. The intent is that this be set to true after bootstrapping of the higher-level client library is complete, to warn downstream clients that they shouldn't be relying on this implemenation detail which does not have a stable interface. */ static warnOnUse = false; /** If true, convertArg() will FuncPtrAdapter.debugOut() when it (un)installs a function binding to/from WASM. */ static debugFuncInstall = false; /** Function used for debug output. */ static debugOut = console.debug.bind(console); static bindScopes = [ 'transient', 'context', 'singleton' ]; /* Dummy impl. Overwritten per-instance as needed. */ contextKey(argv,argIndex){ return this; } /* Returns this objects mapping for the given context key, in the form of an an array, creating the mapping if needed. The key may be anything suitable for use in a Map. */ contextMap(key){ const cm = (this.__cmap || (this.__cmap = new Map)); let rc = cm.get(key); if(undefined===rc) cm.set(key, (rc = [])); return rc; } /** Gets called via xWrap() to "convert" v to a WASM-bound function pointer. If v is one of (a pointer, null, undefined) then (v||0) is returned and any earlier function installed by this mapping _might_, depending on how it's bound, be uninstalled. If v is not one of those types, it must be a Function, for which it creates (if needed) a WASM function binding and returns the WASM pointer to that binding. If this instance is not in 'transient' mode, it will remember the binding for at least the next call, to avoid recreating the function binding unnecessarily. If it's passed a pointer(ish) value for v, it does _not_ perform any function binding, so this object's bindMode is irrelevant for such cases. See the parent class's convertArg() docs for details on what exactly the 2nd and 3rd arguments are. */ convertArg(v,argv,argIndex){ //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.signature,this.transient,v); let pair = this.singleton; if(!pair && this.isContext){ pair = this.contextMap(this.contextKey(argv,argIndex)); } if(pair && pair[0]===v) return pair[1]; if(v instanceof Function){ /* Install a WASM binding and return its pointer. */ if(this.callProxy) v = this.callProxy(v); const fp = __installFunction(v, this.signature, this.isTransient); if(FuncPtrAdapter.debugFuncInstall){ FuncPtrAdapter.debugOut("FuncPtrAdapter installed", this, this.contextKey(argv,argIndex), '@'+fp, v); } if(pair){ /* Replace existing stashed mapping */ if(pair[1]){ if(FuncPtrAdapter.debugFuncInstall){ FuncPtrAdapter.debugOut("FuncPtrAdapter uninstalling", this, this.contextKey(argv,argIndex), '@'+pair[1], v); } try{target.uninstallFunction(pair[1])} catch(e){/*ignored*/} } pair[0] = v; pair[1] = fp; } return fp; }else if(target.isPtr(v) || null===v || undefined===v){ if(pair && pair[1] && pair[1]!==v){ /* uninstall stashed mapping and replace stashed mapping with v. */ if(FuncPtrAdapter.debugFuncInstall){ FuncPtrAdapter.debugOut("FuncPtrAdapter uninstalling", this, this.contextKey(argv,argIndex), '@'+pair[1], v); } try{target.uninstallFunction(pair[1])} catch(e){/*ignored*/} pair[0] = pair[1] = (v | 0); } return v || 0; }else{ throw new TypeError("Invalid FuncPtrAdapter argument type. "+ "Expecting a function pointer or a "+ (this.name ? this.name+' ' : '')+ "function matching signature "+ this.signature+"."); } }/*convertArg()*/ }/*FuncPtrAdapter*/; const __xArgAdapterCheck = (t)=>xArg.get(t) || toss("Argument adapter not found:",t); const __xResultAdapterCheck = (t)=>xResult.get(t) || toss("Result adapter not found:",t); cache.xWrap.convertArg = (t,...args)=>__xArgAdapterCheck(t)(...args); cache.xWrap.convertArgNoCheck = (t,...args)=>xArg.get(t)(...args); cache.xWrap.convertResult = (t,v)=>(null===t ? v : (t ? __xResultAdapterCheck(t)(v) : undefined)); cache.xWrap.convertResultNoCheck = (t,v)=>(null===t ? v : (t ? xResult.get(t)(v) : undefined)); /** Creates a wrapper for another function which converts the arguments of the wrapper to argument types accepted by the wrapped function, then converts the wrapped function's result to another form for the wrapper. The first argument must be one of: - A JavaScript function. - The name of a WASM-exported function. In the latter case xGet() is used to fetch the exported function, which throws if it's not found. - A pointer into the indirect function table. e.g. a pointer returned from target.installFunction(). It returns either the passed-in function or a wrapper for that function which converts the JS-side argument types into WASM-side types and converts the result type. The second argument, `resultType`, describes the conversion for the wrapped functions result. A literal `null` or the string `'null'` both mean to return the original function's value as-is (mnemonic: there is "null" conversion going on). Literal `undefined` or the string `"void"` both mean to ignore the function's result and return `undefined`. Aside from those two special cases, the `resultType` value may be one of the values described below or any mapping installed by the client using xWrap.resultAdapter(). If passed 3 arguments and the final one is an array, that array must contain a list of type names (see below) for adapting the arguments from JS to WASM. If passed 2 arguments, more than 3, or the 3rd is not an array, all arguments after the 2nd (if any) are treated as type names. i.e.: ``` xWrap('funcname', 'i32', 'string', 'f64'); // is equivalent to: xWrap('funcname', 'i32', ['string', 'f64']); ``` This function enforces that the given list of arguments has the same arity as the being-wrapped function (as defined by its `length` property) and it will throw if that is not the case. Similarly, the created wrapper will throw if passed a differing argument count. Type names are symbolic names which map the arguments to an adapter function to convert, if needed, the value before passing it on to WASM or to convert a return result from WASM. The list of built-in names: - `i8`, `i16`, `i32` (args and results): all integer conversions which convert their argument to an integer and truncate it to the given bit length. - `N*` (args): a type name in the form `N*`, where N is a numeric type name, is treated the same as WASM pointer. - `*` and `pointer` (args): are assumed to be WASM pointer values and are returned coerced to an appropriately-sized pointer value (i32 or i64). Non-numeric values will coerce to 0 and out-of-range values will have undefined results (just as with any pointer misuse). - `*` and `pointer` (results): aliases for the current WASM pointer numeric type. - `**` (args): is simply a descriptive alias for the WASM pointer type. It's primarily intended to mark output-pointer arguments. - `i64` (args and results): passes the value to BigInt() to convert it to an int64. Only available if bigIntEnabled is true. - `f32` (`float`), `f64` (`double`) (args and results): pass their argument to Number(). i.e. the adapter does not currently distinguish between the two types of floating-point numbers. - `number` (results): converts the result to a JS Number using Number(theValue).valueOf(). Note that this is for result conversions only, as it's not possible to generically know which type of number to convert arguments to. Non-numeric conversions include: - `null` literal or `"null"` string (args and results): perform no translation and pass the arg on as-is. This is primarily useful for results but may have a use or two for arguments. - `string` or `utf8` (args): has two different semantics in order to accommodate various uses of certain C APIs (e.g. output-style strings)... - If the arg is a string, it creates a _temporary_ UTF-8-encoded C-string to pass to the exported function, cleaning it up before the wrapper returns. If a long-lived C-string pointer is required, that requires client-side code to create the string, then pass its pointer to the function. - Else the arg is assumed to be a pointer to a string the client has already allocated and it's passed on as a WASM pointer. - `string` or `utf8` (results): treats the result value as a const C-string, encoded as UTF-8, copies it to a JS string, and returns that JS string. - `string:dealloc` or `utf8:dealloc) (results): treats the result value as a non-const UTF-8 C-string, ownership of which has just been transfered to the caller. It copies the C-string to a JS string, frees the C-string, and returns the JS string. If such a result value is NULL, the JS result is `null`. Achtung: when using an API which returns results from a specific allocator, e.g. `my_malloc()`, this conversion _is not legal_. Instead, an equivalent conversion which uses the appropriate deallocator is required. For example: ```js target.xWrap.resultAdapter('string:my_free',(i)=>{ try { return i ? target.cstrToJs(i) : null } finally{ target.exports.my_free(i) } }; ``` - `json` (results): treats the result as a const C-string and returns the result of passing the converted-to-JS string to JSON.parse(). Returns `null` if the C-string is a NULL pointer. - `json:dealloc` (results): works exactly like `string:dealloc` but returns the same thing as the `json` adapter. Note the warning in `string:dealloc` regarding maching allocators and deallocators. The type names for results and arguments are validated when xWrap() is called and any unknown names will trigger an exception. Clients may map their own result and argument adapters using xWrap.resultAdapter() and xWrap.argAdapter(), noting that not all type conversions are valid for both arguments _and_ result types as they often have different memory ownership requirements. Design note: the ability to pass in a JS function as the first argument is of relatively limited use, primarily for testing argument and result converters. JS functions, by and large, will not want to deal with C-type arguments. TODOs: - Figure out how/whether we can (semi-)transparently handle pointer-type _output_ arguments. Those currently require explicit handling by allocating pointers, assigning them before the call using poke(), and fetching them with peek() after the call. We may be able to automate some or all of that. - Figure out whether it makes sense to extend the arg adapter interface such that each arg adapter gets an array containing the results of the previous arguments in the current call. That might allow some interesting type-conversion feature. Use case: handling of the final argument to sqlite3_prepare_v2() depends on the type (pointer vs JS string) of its 2nd argument. Currently that distinction requires hand-writing a wrapper for that function. That case is unusual enough that abstracting it into this API (and taking on the associated costs) may well not make good sense. */ target.xWrap = function(fArg, resultType, ...argTypes){ if(3===arguments.length && Array.isArray(arguments[2])){ argTypes = arguments[2]; } if(target.isPtr(fArg)){ fArg = target.functionEntry(fArg) || toss("Function pointer not found in WASM function table."); } const fIsFunc = (fArg instanceof Function); const xf = fIsFunc ? fArg : target.xGet(fArg); if(fIsFunc) fArg = xf.name || 'unnamed function'; if(argTypes.length!==xf.length) __argcMismatch(fArg, xf.length); if((null===resultType) && 0===xf.length){ /* Func taking no args with an as-is return. We don't need a wrapper. We forego the argc check here, though. */ return xf; } /*Verify the arg type conversions are valid...*/; if(undefined!==resultType && null!==resultType) __xResultAdapterCheck(resultType); for(const t of argTypes){ if(t instanceof AbstractArgAdapter) xArg.set(t, (...args)=>t.convertArg(...args)); else __xArgAdapterCheck(t); } const cxw = cache.xWrap; if(0===xf.length){ // No args to convert, so we can create a simpler wrapper... return (...args)=>(args.length ? __argcMismatch(fArg, xf.length) : cxw.convertResult(resultType, xf.call(null))); } return function(...args){ if(args.length!==xf.length) __argcMismatch(fArg, xf.length); const scope = target.scopedAllocPush(); try{ /* Maintenance reminder re. arguments passed to convertArg(): The public interface of argument adapters is that they take ONE argument and return a (possibly) converted result for it. The passing-on of arguments after the first is an internal implementation detail for the sake of AbstractArgAdapter, and not to be relied on or documented for other cases. The fact that this is how AbstractArgAdapter.convertArgs() gets its 2nd+ arguments, and how FuncPtrAdapter.contextKey() gets its args, is also an implementation detail and subject to change. i.e. the public interface of 1 argument is stable. The fact that any arguments may be passed in after that one, and what those arguments are, is _not_ part of the public interface and is _not_ stable. */ for(const i in args) args[i] = cxw.convertArgNoCheck( argTypes[i], args[i], args, i ); return cxw.convertResultNoCheck(resultType, xf.apply(null,args)); }finally{ target.scopedAllocPop(scope); } }; }/*xWrap()*/; /** Internal impl for xWrap.resultAdapter() and argAdapter(). */ const __xAdapter = function(func, argc, typeName, adapter, modeName, xcvPart){ if('string'===typeof typeName){ if(1===argc) return xcvPart.get(typeName); else if(2===argc){ if(!adapter){ delete xcvPart.get(typeName); return func; }else if(!(adapter instanceof Function)){ toss(modeName,"requires a function argument."); } xcvPart.set(typeName, adapter); return func; } } toss("Invalid arguments to",modeName); }; /** |
︙ | ︙ | |||
1541 1542 1543 1544 1545 1546 1547 | freed before because they would be freed before the wrapper returns, leading to chaos and undefined behavior. Except when called as a getter, this function returns itself. */ target.xWrap.resultAdapter = function f(typeName, adapter){ return __xAdapter(f, arguments.length, typeName, adapter, | | | 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 | freed before because they would be freed before the wrapper returns, leading to chaos and undefined behavior. Except when called as a getter, this function returns itself. */ target.xWrap.resultAdapter = function f(typeName, adapter){ return __xAdapter(f, arguments.length, typeName, adapter, 'resultAdapter()', xResult); }; /** Functions identically to xWrap.resultAdapter() but applies to call argument conversions instead of result value conversions. xWrap()-generated wrappers perform argument conversion in the |
︙ | ︙ | |||
1571 1572 1573 1574 1575 1576 1577 | Note that it is perfectly legitimate to use these adapters to perform argument validation, as opposed (or in addition) to conversion. */ target.xWrap.argAdapter = function f(typeName, adapter){ return __xAdapter(f, arguments.length, typeName, adapter, | | > > | < | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 | Note that it is perfectly legitimate to use these adapters to perform argument validation, as opposed (or in addition) to conversion. */ target.xWrap.argAdapter = function f(typeName, adapter){ return __xAdapter(f, arguments.length, typeName, adapter, 'argAdapter()', xArg); }; target.xWrap.FuncPtrAdapter = xArg.FuncPtrAdapter; /** Functions like xCall() but performs argument and result type conversions as for xWrap(). The first, second, and third arguments are as documented for xWrap(), except that the 3rd argument may be either a falsy value or empty array to represent nullary functions. The 4th+ arguments are arguments for the call, with the special case that if the 4th argument is an array, it is used as the arguments for the call. Returns the converted result of the call. This is just a thin wrapper around xWrap(). If the given function is to be called more than once, it's more efficient to use xWrap() to create a wrapper, then to call that wrapper as many times as needed. For one-shot calls, however, this variant is arguably more efficient because it will hypothetically free the wrapper function quickly. */ target.xCallWrapped = function(fArg, resultType, argTypes, ...args){ if(Array.isArray(arguments[3])) args = arguments[3]; return target.xWrap(fArg, resultType, argTypes||[]).apply(null, args||[]); }; /** This function is ONLY exposed in the public API to facilitate testing. It should not be used in application-level code, only in test code. Expects to be given (typeName, value) and returns a conversion of that value as has been registered using argAdapter(). It throws if no adapter is found. ACHTUNG: the adapter may require that a scopedAllocPush() is active and it may allocate memory within that scope. It may also require additional arguments, depending on the type of conversion. */ target.xWrap.testConvertArg = cache.xWrap.convertArg; /** This function is ONLY exposed in the public API to facilitate testing. It should not be used in application-level code, only in test code. Expects to be given (typeName, value) and returns a conversion of that value as has been registered using resultAdapter(). It throws if no adapter is found. ACHTUNG: the adapter may allocate memory which the caller may need to know how to free. */ target.xWrap.testConvertResult = cache.xWrap.convertResult; return target; }; /** yawl (Yet Another Wasm Loader) provides very basic wasm loader. It requires a config object: |
︙ | ︙ |
Changes to ext/wasm/demo-worker1-promiser.js.
︙ | ︙ | |||
72 73 74 75 76 77 78 | const dbFilename = '/testing2.sqlite3'; startTime = performance.now(); let sqConfig; await wtest('config-get', (ev)=>{ const r = ev.result; log('sqlite3.config subset:', r); | | < < | 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | const dbFilename = '/testing2.sqlite3'; startTime = performance.now(); let sqConfig; await wtest('config-get', (ev)=>{ const r = ev.result; log('sqlite3.config subset:', r); T.assert('boolean' === typeof r.bigIntEnabled); sqConfig = r; }); logHtml('', "Sending 'open' message and waiting for its response before continuing..."); await wtest('open', { filename: dbFilename, |
︙ | ︙ | |||
209 210 211 212 213 214 215 | T.assert('number' === typeof ev.row); } }; await wtest('exec',{ sql: "select 'foo' foo, a bar, 'baz' baz from t limit 2", callback: resultRowTest3, columnNames: [], | | | 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | T.assert('number' === typeof ev.row); } }; await wtest('exec',{ sql: "select 'foo' foo, a bar, 'baz' baz from t limit 2", callback: resultRowTest3, columnNames: [], rowMode: '$bar' }, function(ev){ log("exec() result row:",ev); T.assert(2===resultRowTest3.counter); }); await wtest('exec',{ multi: true, |
︙ | ︙ |
Changes to ext/wasm/dist.make.
1 2 3 4 5 6 7 8 9 10 11 12 | #!/do/not/make #^^^ help emacs select edit mode # # Intended to include'd by ./GNUmakefile. # # 'make dist' rules for creating a distribution archive of the WASM/JS # pieces, noting that we only build a dist of the built files, not the # numerous pieces required to build them. ####################################################################### MAKEFILE.dist := $(lastword $(MAKEFILE_LIST)) ######################################################################## | > > > > | | | | > > | | < < | < < < < | > | | | | | | | | | > > | | > | | > > | | > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | #!/do/not/make #^^^ help emacs select edit mode # # Intended to include'd by ./GNUmakefile. # # 'make dist' rules for creating a distribution archive of the WASM/JS # pieces, noting that we only build a dist of the built files, not the # numerous pieces required to build them. # # Use 'make snapshot' to create "snapshot" releases. They use a # distinctly different zip file and top directory name to distinguish # them from release builds. ####################################################################### MAKEFILE.dist := $(lastword $(MAKEFILE_LIST)) ######################################################################## # Chicken/egg situation: we need $(bin.version-info) to get the # version info for the archive name, but that binary may not yet be # built, and won't be built until we expand the dependencies. Thus we # have to use a temporary name for the archive until we can get # that binary built. ifeq (,$(filter snapshot,$(MAKECMDGOALS))) dist-name-prefix := sqlite-wasm else dist-name-prefix := sqlite-wasm-snapshot-$(shell /usr/bin/date +%Y%m%d) endif dist-name := $(dist-name-prefix)-TEMP ######################################################################## # dist.build must be the name of a target which triggers the build of # the files to be packed into the dist archive. The intention is that # it be one of (o0, o1, o2, o3, os, oz), each of which uses like-named # -Ox optimization level flags. The o2 target provides the best # overall runtime speeds. The oz target provides slightly slower # speeds (roughly 10%) with significantly smaller WASM file # sizes. Note that -O2 (the o2 target) results in faster binaries than # both -O3 and -Os (the o3 and os targets) in all tests run to # date. Our general policy is that we want the smallest binaries for # dist zip files, so use the oz build unless there is a compelling # reason not to. dist.build ?= qoz dist-dir.top := $(dist-name) dist-dir.jswasm := $(dist-dir.top)/$(notdir $(dir.dout)) dist-dir.common := $(dist-dir.top)/common dist.top.extras := \ demo-123.html demo-123-worker.html demo-123.js \ tester1.html tester1-worker.html tester1-esm.html \ tester1.js tester1.mjs \ demo-jsstorage.html demo-jsstorage.js \ demo-worker1.html demo-worker1.js \ demo-worker1-promiser.html demo-worker1-promiser.js dist.jswasm.extras := $(sqlite3-api.ext.jses) $(sqlite3.wasm) dist.common.extras := \ $(wildcard $(dir.common)/*.css) \ $(dir.common)/SqliteTestUtil.js .PHONY: dist snapshot ######################################################################## # dist: create the end-user deliverable archive. # # Maintenance reminder: because dist depends on $(dist.build), and # $(dist.build) will depend on clean, having any deps on # $(dist-archive) which themselves may be cleaned up by the clean # target will lead to grief in parallel builds (-j #). Thus # dist's deps must be trimmed to non-generated files or # files which are _not_ cleaned up by the clean target. # # Note that we require $(bin.version-info) in order to figure out the # dist file's name, so cannot (without a recursive make) have the # target name equal to the archive name. dist: \ $(bin.stripccomments) $(bin.version-info) \ $(dist.build) \ $(MAKEFILE) $(MAKEFILE.dist) @echo "Making end-user deliverables..." @rm -fr $(dist-dir.top) @mkdir -p $(dist-dir.jswasm) $(dist-dir.common) @cp -p $(dist.top.extras) $(dist-dir.top) @cp -p README-dist.txt $(dist-dir.top)/README.txt @cp -p index-dist.html $(dist-dir.top)/index.html @cp -p $(dist.jswasm.extras) $(dist-dir.jswasm) @$(bin.stripccomments) -k -k < $(sqlite3.js) \ > $(dist-dir.jswasm)/$(notdir $(sqlite3.js)) @$(bin.stripccomments) -k -k < $(sqlite3.mjs) \ > $(dist-dir.jswasm)/$(notdir $(sqlite3.mjs)) @cp -p $(dist.common.extras) $(dist-dir.common) @set -e; \ vnum=$$($(bin.version-info) --download-version); \ vdir=$(dist-name-prefix)-$$vnum; \ arczip=$$vdir.zip; \ echo "Making $$arczip ..."; \ rm -fr $$arczip $$vdir; \ mv $(dist-dir.top) $$vdir; \ zip -qr $$arczip $$vdir; \ rm -fr $$vdir; \ ls -la $$arczip; \ set +e; \ unzip -lv $$arczip || echo "Missing unzip app? Not fatal." ifeq (,$(wasm.docs.found)) snapshot: dist @echo "To upload the snapshot build to the wasm docs server:"; \ echo "1) move $(dist-name-prefix)*.zip to the top of a wasm docs checkout."; \ echo "2) run 'make uv-sync'" else snapshot: dist @echo "Moving snapshot to [$(wasm.docs.found)]..."; \ mv $(dist-name-prefix)*.zip $(wasm.docs.found)/. @echo "Run 'make uv-sync' from $(wasm.docs.found) to upload it." endif # We need a separate `clean` rule to account for weirdness in # a sub-make, where we get a copy of the $(dist-name) dir # copied into the new $(dist-name) dir. .PHONY: dist-clean clean: dist-clean dist-clean: rm -fr $(dist-name) $(wildcard sqlite-wasm-*.zip) |
Changes to ext/wasm/fiddle.make.
︙ | ︙ | |||
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | -sDYNAMIC_EXECUTION=0 \ -sWASM_BIGINT=$(emcc.WASM_BIGINT) \ -sEXPORT_NAME=$(sqlite3.js.init-func) \ -Wno-limited-postlink-optimizations \ $(sqlite3.js.flags.--post-js) \ $(emcc.exportedRuntimeMethods) \ -sEXPORTED_FUNCTIONS=@$(abspath $(EXPORTED_FUNCTIONS.fiddle)) \ $(SQLITE_OPT) $(SHELL_OPT) \ -DSQLITE_SHELL_FIDDLE # -D_POSIX_C_SOURCE is needed for strdup() with emcc fiddle.EXPORTED_FUNCTIONS.in := \ EXPORTED_FUNCTIONS.fiddle.in \ $(EXPORTED_FUNCTIONS.api) $(EXPORTED_FUNCTIONS.fiddle): $(fiddle.EXPORTED_FUNCTIONS.in) $(MAKEFILE.fiddle) sort -u $(fiddle.EXPORTED_FUNCTIONS.in) > $@ fiddle-module.js := $(dir.fiddle)/fiddle-module.js fiddle-module.wasm := $(subst .js,.wasm,$(fiddle-module.js)) fiddle.cses := $(dir.top)/shell.c $(sqlite3-wasm.c) fiddle.SOAP.js := $(dir.fiddle)/$(notdir $(SOAP.js)) $(fiddle.SOAP.js): $(SOAP.js) cp $< $@ | > | | | | 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | -sDYNAMIC_EXECUTION=0 \ -sWASM_BIGINT=$(emcc.WASM_BIGINT) \ -sEXPORT_NAME=$(sqlite3.js.init-func) \ -Wno-limited-postlink-optimizations \ $(sqlite3.js.flags.--post-js) \ $(emcc.exportedRuntimeMethods) \ -sEXPORTED_FUNCTIONS=@$(abspath $(EXPORTED_FUNCTIONS.fiddle)) \ -sEXPORTED_RUNTIME_METHODS=FS,wasmMemory \ $(SQLITE_OPT) $(SHELL_OPT) \ -DSQLITE_SHELL_FIDDLE # -D_POSIX_C_SOURCE is needed for strdup() with emcc fiddle.EXPORTED_FUNCTIONS.in := \ EXPORTED_FUNCTIONS.fiddle.in \ $(EXPORTED_FUNCTIONS.api) $(EXPORTED_FUNCTIONS.fiddle): $(fiddle.EXPORTED_FUNCTIONS.in) $(MAKEFILE.fiddle) sort -u $(fiddle.EXPORTED_FUNCTIONS.in) > $@ fiddle-module.js := $(dir.fiddle)/fiddle-module.js fiddle-module.wasm := $(subst .js,.wasm,$(fiddle-module.js)) fiddle.cses := $(dir.top)/shell.c $(sqlite3-wasm.c) fiddle.SOAP.js := $(dir.fiddle)/$(notdir $(SOAP.js)) $(fiddle.SOAP.js): $(SOAP.js) cp $< $@ $(eval $(call call-make-pre-js,fiddle-module,vanilla)) $(fiddle-module.js): $(MAKEFILE) $(MAKEFILE.fiddle) \ $(EXPORTED_FUNCTIONS.fiddle) \ $(fiddle.cses) $(pre-post-fiddle-module.deps.vanilla) $(fiddle.SOAP.js) $(emcc.bin) -o $@ $(fiddle.emcc-flags) \ $(pre-post-fiddle-module.flags.vanilla) \ $(fiddle.cses) $(maybe-wasm-strip) $(fiddle-module.wasm) gzip < $@ > $@.gz gzip < $(fiddle-module.wasm) > $(fiddle-module.wasm).gz $(dir.fiddle)/fiddle.js.gz: $(dir.fiddle)/fiddle.js gzip < $< > $@ |
︙ | ︙ |
Changes to ext/wasm/index-dist.html.
︙ | ︙ | |||
15 16 17 18 19 20 21 22 23 24 25 26 27 28 | } textarea { font-family: monospace; } header { font-size: 130%; font-weight: bold; } .hidden, .initially-hidden { position: absolute !important; opacity: 0 !important; pointer-events: none !important; display: none !important; } | > > > > | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | } textarea { font-family: monospace; } header { font-size: 130%; font-weight: bold; background: #044a64; color: white; padding: 0.5em; border-radius: 0.25em; } .hidden, .initially-hidden { position: absolute !important; opacity: 0 !important; pointer-events: none !important; display: none !important; } |
︙ | ︙ | |||
52 53 54 55 56 57 58 59 60 61 62 63 64 65 | <li>Core-most tests <ul> <li><a href='tester1.html'>tester1</a>: Core unit and regression tests for the various APIs and surrounding utility code.</li> <li><a href='tester1-worker.html'>tester1-worker</a>: same thing but running in a Worker.</li> </ul> </li> <li>Higher-level apps and demos... <ul> <li><a href='demo-123.html'>demo-123</a> provides a no-nonsense example of adding sqlite3 support to a web page in the UI thread.</li> | > > > > > > > > > > | 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | <li>Core-most tests <ul> <li><a href='tester1.html'>tester1</a>: Core unit and regression tests for the various APIs and surrounding utility code.</li> <li><a href='tester1-worker.html'>tester1-worker</a>: same thing but running in a Worker.</li> <li><a href='tester1-esm.html'>tester1-esm</a>: same as <code>tester1</code> but loads sqlite3 in the main thread via an ES6 module. </li> <li><a href='tester1-worker.html?esm'>tester1-worker?esm</a>: same as <code>tester1-esm</code> but loads a Worker Module which then loads the sqlite3 API via an ES6 module. Note that not all browsers permit loading modules in Worker threads. </li> </ul> </li> <li>Higher-level apps and demos... <ul> <li><a href='demo-123.html'>demo-123</a> provides a no-nonsense example of adding sqlite3 support to a web page in the UI thread.</li> |
︙ | ︙ |
Changes to ext/wasm/index.html.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <!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/testing.css"/> <title>sqlite3 WASM Testing Page Index</title> </head> <body> <header id='titlebar'><span>sqlite3 WASM test pages</span></header> <hr> <div>Below is the list of test pages for the sqlite3 WASM builds. All of them require that this directory have been "make"d first. The intent is that <em>this</em> page be run using:</div> <blockquote><pre>althttpd -enable-sab -page index.html</pre></blockquote> | > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <!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/testing.css"/> <title>sqlite3 WASM Testing Page Index</title> </head> <body> <style> header { background: #044a64; color: white; padding: 0.5em; border-radius: 0.25em; } </style> <header id='titlebar'><span>sqlite3 WASM test pages</span></header> <hr> <div>Below is the list of test pages for the sqlite3 WASM builds. All of them require that this directory have been "make"d first. The intent is that <em>this</em> page be run using:</div> <blockquote><pre>althttpd -enable-sab -page index.html</pre></blockquote> |
︙ | ︙ | |||
26 27 28 29 30 31 32 | headers. <a href='https://sqlite.org/althttpd'>althttpd</a> requires the <code>-enable-sab</code> flag for that. </li> <li>Any OPFS-related pages require very recent version of Chrome or Chromium (v102 at least, possibly newer). OPFS support in the other major browsers is pending. Development and testing is currently done against a dev-channel release | | < < < < | > > > > > > > > > > | 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | headers. <a href='https://sqlite.org/althttpd'>althttpd</a> requires the <code>-enable-sab</code> flag for that. </li> <li>Any OPFS-related pages require very recent version of Chrome or Chromium (v102 at least, possibly newer). OPFS support in the other major browsers is pending. Development and testing is currently done against a dev-channel release of Chrome (v110 as of 2022-12-02). </li> </ul> </div> <div>The tests and demos... <ul id='test-list'> <li>Core-most tests <ul> <li><a href='tester1.html'>tester1</a>: Core unit and regression tests for the various APIs and surrounding utility code.</li> <li><a href='tester1-worker.html'>tester1-worker</a>: same thing but running in a Worker.</li> <li><a href='tester1-esm.html'>tester1-esm</a>: same as <code>tester1</code> but loads sqlite3 in the main thread via an ES6 module. </li> <li><a href='tester1-worker.html?esm'>tester1-worker?esm</a>: same as <code>tester1-esm</code> but loads a Worker Module which then loads the sqlite3 API via an ES6 module. Note that not all browsers permit loading modules in Worker threads. </li> </ul> </li> <li>High-level apps and demos... <ul> <li><a href='fiddle/index.html'>fiddle</a> is an HTML front-end to a wasm build of the sqlite3 shell.</li> <li><a href='demo-123.html'>demo-123</a> provides a |
︙ | ︙ | |||
68 69 70 71 72 73 74 | <li><a href='demo-worker1-promiser.html'>demo-worker1-promiser</a>: a demo of the Promise-based wrapper of the Worker1 API.</li> </ul> </li> <li>speedtest1 ports (sqlite3's primary benchmarking tool)... <ul> <li><a href='speedtest1.html'>speedtest1</a>: a main-thread WASM build of speedtest1.</li> | < < | | < < < < > > > > > > > > > > > > > > > | 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | <li><a href='demo-worker1-promiser.html'>demo-worker1-promiser</a>: a demo of the Promise-based wrapper of the Worker1 API.</li> </ul> </li> <li>speedtest1 ports (sqlite3's primary benchmarking tool)... <ul> <li><a href='speedtest1.html'>speedtest1</a>: a main-thread WASM build of speedtest1.</li> <li><a href='speedtest1.html?vfs=kvvfs'>speedtest1?vfs=kvvfs</a>: speedtest1 with the kvvfs.</li> <li><a href='speedtest1-worker.html?size=25'>speedtest1-worker</a>: an interactive Worker-thread variant of speedtest1.</li> <li><a href='speedtest1-worker.html?vfs=opfs&size=25'>speedtest1-worker?vfs=opfs</a>: speedtest1-worker with the OPFS VFS preselected and configured for a moderate workload.</li> </ul> </li> <li>The obligatory "misc." category... <ul> <li><a href='module-symbols.html'>module-symbols</a> gives a high-level overview of the symbols exposed by the JS module.</li> <li><a href='batch-runner.html'>batch-runner</a>: runs batches of SQL exported from speedtest1.</li> <li><a href='test-opfs-vfs.html'>test-opfs-vfs</a> (<a href='test-opfs-vfs.html?opfs-sanity-check&opfs-verbose'>same with verbose output and sanity-checking tests</a>) is an sqlite3_vfs OPFS proxy using SharedArrayBuffer and the Atomics APIs to regulate communication between the synchronous sqlite3_vfs interface and the async OPFS impl. </li> <li><a href='tests/opfs/concurrency/index.html'>OPFS concurrency</a> tests using multiple workers. </li> </ul> </li> <!--li>WASMFS-specific tests which currently do not work due to incompatible changes made to the WASMFS+OPFS combination. <ul> <li><a href='speedtest1-wasmfs.html?flags=--size,25'>speedtest1-wasmfs</a>: a variant of speedtest1 built solely for the wasmfs/opfs feature.</li> <li><a href='scratchpad-wasmfs-main.html'>scratchpad-wasmfs-main</a>: experimenting with WASMFS/OPFS-based persistence. Maintenance reminder: we cannot currently (2022-09-15) load WASMFS in a worker due to an Emscripten wasm loader limitation.</li> </ul> </li--> <!--li><a href='x.html'></a></li--> </ul> </div> <style> #test-list { font-size: 120%; } </style> <script>//Assign a distinct target tab name for each test page... document.querySelectorAll('a').forEach(function(e){ e.target = e.href; }); </script> </body> </html> |
Changes to ext/wasm/jaccwabyt/jaccwabyt.js.
1 2 3 4 5 6 7 8 9 10 11 12 | /** 2022-06-30 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. *********************************************************************** | | > > | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /** 2022-06-30 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. *********************************************************************** The Jaccwabyt API is documented in detail in an external file, _possibly_ called jaccwabyt.md in the same directory as this file. Project homes: - https://fossil.wanderinghorse.net/r/jaccwabyt - https://sqlite.org/src/dir/ext/wasm/jaccwabyt */ 'use strict'; self.Jaccwabyt = function StructBinderFactory(config){ /* ^^^^ it is recommended that clients move that object into wherever they'd like to have it and delete the self-held copy ("self" being the global window or worker object). This API does not require the |
︙ | ︙ | |||
57 58 59 60 61 62 63 | memberPrefix = (config.memberPrefix || ""), memberSuffix = (config.memberSuffix || ""), bigIntEnabled = (undefined===config.bigIntEnabled ? !!self['BigInt64Array'] : !!config.bigIntEnabled), BigInt = self['BigInt'], BigInt64Array = self['BigInt64Array'], /* Undocumented (on purpose) config options: */ | < | 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | memberPrefix = (config.memberPrefix || ""), memberSuffix = (config.memberSuffix || ""), bigIntEnabled = (undefined===config.bigIntEnabled ? !!self['BigInt64Array'] : !!config.bigIntEnabled), BigInt = self['BigInt'], BigInt64Array = self['BigInt64Array'], /* Undocumented (on purpose) config options: */ ptrSizeof = config.ptrSizeof || 4, ptrIR = config.ptrIR || 'i32' ; if(!SBF.debugFlags){ SBF.__makeDebugFlags = function(deriveFrom=null){ /* This is disgustingly overengineered. :/ */ |
︙ | ︙ | |||
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | const isPtrSig = (s)=>'p'===s || 'P'===s; const isAutoPtrSig = (s)=>'P'===s /*EXPERIMENTAL*/; const sigLetter = (s)=>isFuncSig(s) ? 'p' : s[0]; /** Returns the WASM IR form of the Emscripten-conventional letter at SIG s[0]. Throws for an unknown SIG. */ const sigIR = function(s){ switch(sigLetter(s)){ case 'i': return 'i32'; case 'p': case 'P': case 's': return ptrIR; case 'j': return 'i64'; case 'f': return 'float'; case 'd': return 'double'; } toss("Unhandled signature IR:",s); }; | > < < < < < < < < < | < < < < < < < < < < < < < < < > > > > | > > < < < < < < < < < < < < < < < < < < < < < < < < | < < < < < < | > | > > > > | > > > | > < < < | < < | 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 | const isPtrSig = (s)=>'p'===s || 'P'===s; const isAutoPtrSig = (s)=>'P'===s /*EXPERIMENTAL*/; const sigLetter = (s)=>isFuncSig(s) ? 'p' : s[0]; /** Returns the WASM IR form of the Emscripten-conventional letter at SIG s[0]. Throws for an unknown SIG. */ const sigIR = function(s){ switch(sigLetter(s)){ case 'c': case 'C': return 'i8'; case 'i': return 'i32'; case 'p': case 'P': case 's': return ptrIR; case 'j': return 'i64'; case 'f': return 'float'; case 'd': return 'double'; } toss("Unhandled signature IR:",s); }; const affirmBigIntArray = BigInt64Array ? ()=>true : ()=>toss('BigInt64Array is not available.'); /** Returns the name of a DataView getter method corresponding to the given SIG. */ const sigDVGetter = function(s){ switch(sigLetter(s)) { case 'p': case 'P': case 's': { switch(ptrSizeof){ case 4: return 'getInt32'; case 8: return affirmBigIntArray() && 'getBigInt64'; } break; } case 'i': return 'getInt32'; case 'c': return 'getInt8'; case 'C': return 'getUint8'; case 'j': return affirmBigIntArray() && 'getBigInt64'; case 'f': return 'getFloat32'; case 'd': return 'getFloat64'; } toss("Unhandled DataView getter for signature:",s); }; /** Returns the name of a DataView setter method corresponding to the given SIG. */ const sigDVSetter = function(s){ switch(sigLetter(s)){ case 'p': case 'P': case 's': { switch(ptrSizeof){ case 4: return 'setInt32'; case 8: return affirmBigIntArray() && 'setBigInt64'; } break; } case 'i': return 'setInt32'; case 'c': return 'setInt8'; case 'C': return 'setUint8'; case 'j': return affirmBigIntArray() && 'setBigInt64'; case 'f': return 'setFloat32'; case 'd': return 'setFloat64'; } toss("Unhandled DataView setter for signature:",s); }; /** Returns either Number of BigInt, depending on the given SIG. This constructor is used in property setters to coerce the being-set value to the correct size. */ const sigDVSetWrapper = function(s){ switch(sigLetter(s)) { case 'i': case 'f': case 'c': case 'C': case 'd': return Number; case 'j': return affirmBigIntArray() && BigInt; case 'p': case 'P': case 's': switch(ptrSizeof){ case 4: return Number; case 8: return affirmBigIntArray() && BigInt; } break; } toss("Unhandled DataView set wrapper for signature:",s); }; /** Returns the given struct and member name in a form suitable for debugging and error output. */ const sPropName = (s,k)=>s+'::'+k; const __propThrowOnSet = function(structName,propName){ return ()=>toss(sPropName(structName,propName),"is read-only."); }; /** In order to completely hide StructBinder-bound struct pointers from JS code, we store them in a scope-local WeakMap which maps the struct-bound objects to their WASM pointers. The pointers are accessible via boundObject.pointer, which is gated behind an accessor function, but are not exposed anywhere else in the object. The main intention of that is to make it impossible for stale copies to be made. */ const __instancePointerMap = new WeakMap(); /** Property name for the pointer-is-external marker. */ const xPtrPropName = '(pointer-is-external)'; /** Frees the obj.pointer memory and clears the pointer property. */ const __freeStruct = function(ctor, obj, m){ if(!m) m = __instancePointerMap.get(obj); if(m) { __instancePointerMap.delete(obj); if(Array.isArray(obj.ondispose)){ let x; while((x = obj.ondispose.shift())){ try{ if(x instanceof Function) x.call(obj); else if(x instanceof StructType) x.dispose(); else if('number' === typeof x) dealloc(x); // else ignore. Strings are permitted to annotate entries // to assist in debugging. }catch(e){ console.warn("ondispose() for",ctor.structName,'@', m,'threw. NOT propagating it.',e); } } }else if(obj.ondispose instanceof Function){ try{obj.ondispose()} catch(e){ /*do not rethrow: destructors must not throw*/ console.warn("ondispose() for",ctor.structName,'@', m,'threw. NOT propagating it.',e); } } delete obj.ondispose; if(ctor.debugFlags.__flags.dealloc){ log("debug.dealloc:",(obj[xPtrPropName]?"EXTERNAL":""), ctor.structName,"instance:", ctor.structInfo.sizeof,"bytes @"+m); } if(!obj[xPtrPropName]) dealloc(m); } }; /** Returns a skeleton for a read-only property accessor wrapping value v. */ const rop = (v)=>{return {configurable: false, writable: false, iterable: false, value: v}}; /** Allocates obj's memory buffer based on the size defined in ctor.structInfo.sizeof. */ const __allocStruct = function(ctor, obj, m){ let fill = !m; if(m) Object.defineProperty(obj, xPtrPropName, rop(m)); else{ m = alloc(ctor.structInfo.sizeof); if(!m) toss("Allocation of",ctor.structName,"structure failed."); } try { if(ctor.debugFlags.__flags.alloc){ log("debug.alloc:",(fill?"":"EXTERNAL"), ctor.structName,"instance:", ctor.structInfo.sizeof,"bytes @"+m); } if(fill) heap().fill(0, m, m + ctor.structInfo.sizeof); __instancePointerMap.set(obj, m); }catch(e){ __freeStruct(ctor, obj, m); throw e; } }; /** Gets installed as the memoryDump() method of all structs. */ const __memoryDump = function(){ |
︙ | ︙ | |||
335 336 337 338 339 340 341 | const __memberKeyProp = rop(__memberKey); /** Looks up a struct member in structInfo.members. Throws if found if tossIfNotFound is true, else returns undefined if not found. The given name may be either the name of the structInfo.members key (faster) or the key as modified by the | | | 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 | const __memberKeyProp = rop(__memberKey); /** Looks up a struct member in structInfo.members. Throws if found if tossIfNotFound is true, else returns undefined if not found. The given name may be either the name of the structInfo.members key (faster) or the key as modified by the memberPrefix and memberSuffix settings. */ const __lookupMember = function(structInfo, memberName, tossIfNotFound=true){ let m = structInfo.members[memberName]; if(!m && (memberPrefix || memberSuffix)){ // Check for a match on members[X].key for(const v of Object.values(structInfo.members)){ if(v.key===memberName){ m = v; break; } |
︙ | ︙ | |||
357 358 359 360 361 362 363 | /** Uses __lookupMember(obj.structInfo,memberName) to find a member, throwing if not found. Returns its signature, either in this framework's native format or in Emscripten format. */ const __memberSignature = function f(obj,memberName,emscriptenFormat=false){ | | < < < < < < < < < < | > > | 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 | /** Uses __lookupMember(obj.structInfo,memberName) to find a member, throwing if not found. Returns its signature, either in this framework's native format or in Emscripten format. */ const __memberSignature = function f(obj,memberName,emscriptenFormat=false){ if(!f._) f._ = (x)=>x.replace(/[^vipPsjrdcC]/g,"").replace(/[pPscC]/g,'i'); const m = __lookupMember(obj.structInfo, memberName, true); return emscriptenFormat ? f._(m.signature) : m.signature; }; const __ptrPropDescriptor = { configurable: false, enumerable: false, get: function(){return __instancePointerMap.get(this)}, set: ()=>toss("Cannot assign the 'pointer' property of a struct.") // Reminder: leaving `set` undefined makes assignments // to the property _silently_ do nothing. Current unit tests // rely on it throwing, though. }; /** Impl of X.memberKeys() for StructType and struct ctors. */ const __structMemberKeys = rop(function(){ const a = []; for(const k of Object.keys(this.structInfo.members)){ a.push(this.memberKey(k)); } return a; }); const __utf8Decoder = new TextDecoder('utf-8'); const __utf8Encoder = new TextEncoder(); /** Internal helper to use in operations which need to distinguish between SharedArrayBuffer heap memory and non-shared heap. */ |
︙ | ︙ | |||
450 451 452 453 454 455 456 | return (addr===pos) ? "" : __utf8Decode(mem, addr, pos); }; /** Adds value v to obj.ondispose, creating ondispose, or converting it to an array, if needed. */ | | | < > | | | > > > > > | 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 | return (addr===pos) ? "" : __utf8Decode(mem, addr, pos); }; /** Adds value v to obj.ondispose, creating ondispose, or converting it to an array, if needed. */ const __addOnDispose = function(obj, ...v){ if(obj.ondispose){ if(!Array.isArray(obj.ondispose)){ obj.ondispose = [obj.ondispose]; } }else{ obj.ondispose = []; } obj.ondispose.push(...v); }; /** Allocates a new UTF-8-encoded, NUL-terminated copy of the given JS string and returns its address relative to heap(). If allocation returns 0 this function throws. Ownership of the memory is transfered to the caller, who must eventually pass it to the configured dealloc() function. */ const __allocCString = function(str){ const u = __utf8Encoder.encode(str); const mem = alloc(u.length+1); if(!mem) toss("Allocation error while duplicating string:",str); const h = heap(); //let i = 0; //for( ; i < u.length; ++i ) h[mem + i] = u[i]; h.set(u, mem); h[mem + u.length] = 0; //log("allocCString @",mem," =",u); return mem; }; /** Sets the given struct member of obj to a dynamically-allocated, UTF-8-encoded, NUL-terminated copy of str. It is up to the caller to free any prior memory, if appropriate. The newly-allocated string is added to obj.ondispose so will be freed when the object is disposed. The given name may be either the name of the structInfo.members key (faster) or the key as modified by the memberPrefix and memberSuffix settings. */ const __setMemberCString = function(obj, memberName, str){ const m = __lookupMember(obj.structInfo, memberName, true); __affirmCStringSignature(m); /* Potential TODO: if obj.ondispose contains obj[m.key] then dealloc that value and clear that ondispose entry */ const mem = __allocCString(str); |
︙ | ︙ | |||
540 541 542 543 544 545 546 547 548 549 550 551 552 | }), memoryDump: rop(__memoryDump), pointer: __ptrPropDescriptor, setMemberCString: rop(function(memberName, str){ return __setMemberCString(this, memberName, str); }) }); /** "Static" properties for StructType. */ Object.defineProperties(StructType, { allocCString: rop(__allocCString), | > > > > > > > < | | | < | | < < < < | | > > > > > > > > > | > | | | > | > | 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 | }), memoryDump: rop(__memoryDump), pointer: __ptrPropDescriptor, setMemberCString: rop(function(memberName, str){ return __setMemberCString(this, memberName, str); }) }); // Function-type non-Property inherited members Object.assign(StructType.prototype,{ addOnDispose: function(...v){ __addOnDispose(this,...v); return this; } }); /** "Static" properties for StructType. */ Object.defineProperties(StructType, { allocCString: rop(__allocCString), isA: rop((v)=>v instanceof StructType), hasExternalPointer: rop((v)=>(v instanceof StructType) && !!v[xPtrPropName]), memberKey: __memberKeyProp }); const isNumericValue = (v)=>Number.isFinite(v) || (v instanceof (BigInt || Number)); /** Pass this a StructBinder-generated prototype, and the struct member description object. It will define property accessors for proto[memberKey] which read from/write to memory in this.pointer. It modifies descr to make certain downstream operations much simpler. */ const makeMemberWrapper = function f(ctor,name, descr){ if(!f._){ /*cache all available getters/setters/set-wrappers for direct reuse in each accessor function. */ f._ = {getters: {}, setters: {}, sw:{}}; const a = ['i','c','C','p','P','s','f','d','v()']; if(bigIntEnabled) a.push('j'); a.forEach(function(v){ //const ir = sigIR(v); f._.getters[v] = sigDVGetter(v) /* DataView[MethodName] values for GETTERS */; f._.setters[v] = sigDVSetter(v) /* DataView[MethodName] values for SETTERS */; f._.sw[v] = sigDVSetWrapper(v) /* BigInt or Number ctor to wrap around values for conversion */; }); const rxSig1 = /^[ipPsjfdcC]$/, rxSig2 = /^[vipPsjfdcC]\([ipPsjfdcC]*\)$/; f.sigCheck = function(obj, name, key,sig){ if(Object.prototype.hasOwnProperty.call(obj, key)){ toss(obj.structName,'already has a property named',key+'.'); } rxSig1.test(sig) || rxSig2.test(sig) || toss("Malformed signature for", sPropName(obj.structName,name)+":",sig); }; } const key = ctor.memberKey(name); f.sigCheck(ctor.prototype, name, key, descr.signature); descr.key = key; descr.name = name; const sigGlyph = sigLetter(descr.signature); const xPropName = sPropName(ctor.prototype.structName,key); const dbg = ctor.prototype.debugFlags.__flags; /* TODO?: set prototype of descr to an object which can set/fetch its prefered representation, e.g. conversion to string or mapped function. Advantage: we can avoid doing that via if/else if/else in the get/set methods. */ const prop = Object.create(null); prop.configurable = false; prop.enumerable = false; prop.get = function(){ if(dbg.getter){ log("debug.getter:",f._.getters[sigGlyph],"for", sigIR(sigGlyph), xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof); } let rc = ( new DataView(heap().buffer, this.pointer + descr.offset, descr.sizeof) )[f._.getters[sigGlyph]](0, isLittleEndian); if(dbg.getter) log("debug.getter:",xPropName,"result =",rc); return rc; }; if(descr.readOnly){ prop.set = __propThrowOnSet(ctor.prototype.structName,key); }else{ prop.set = function(v){ if(dbg.setter){ log("debug.setter:",f._.setters[sigGlyph],"for", sigIR(sigGlyph), xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof, v); } if(!this.pointer){ toss("Cannot set struct property on disposed instance."); } if(null===v) v = 0; else while(!isNumericValue(v)){ if(isAutoPtrSig(descr.signature) && (v instanceof StructType)){ // It's a struct instance: let's store its pointer value! v = v.pointer || 0; if(dbg.setter) log("debug.setter:",xPropName,"resolved to",v); break; } toss("Invalid value for pointer-type",xPropName+'.'); } ( new DataView(heap().buffer, this.pointer + descr.offset, descr.sizeof) )[f._.setters[sigGlyph]](0, f._.sw[sigGlyph](v), isLittleEndian); }; } Object.defineProperty(ctor.prototype, key, prop); }/*makeMemberWrapper*/; /** The main factory function which will be returned to the caller. */ const StructBinder = function StructBinder(structName, structInfo){ if(1===arguments.length){ structInfo = structName; structName = structInfo.name; }else if(!structInfo.name){ structInfo.name = structName; } if(!structName) toss("Struct name is required."); let lastMember = false; Object.keys(structInfo.members).forEach((k)=>{ // Sanity checks of sizeof/offset info... const m = structInfo.members[k]; if(!m.sizeof) toss(structName,"member",k,"is missing sizeof."); else if(m.sizeof===1){ (m.signature === 'c' || m.signature === 'C') || toss("Unexpected sizeof==1 member", sPropName(structInfo.name,k), "with signature",m.signature); }else{ // sizes and offsets of size-1 members may be odd values, but // others may not. if(0!==(m.sizeof%4)){ console.warn("Invalid struct member description =",m,"from",structInfo); toss(structName,"member",k,"sizeof is not aligned. sizeof="+m.sizeof); } if(0!==(m.offset%4)){ console.warn("Invalid struct member description =",m,"from",structInfo); toss(structName,"member",k,"offset is not aligned. offset="+m.offset); } } if(!lastMember || lastMember.offset < m.offset) lastMember = m; }); if(!lastMember) toss("No member property descriptions found."); else if(structInfo.sizeof < lastMember.offset+lastMember.sizeof){ toss("Invalid struct config:",structName, "max member offset ("+lastMember.offset+") ", |
︙ | ︙ | |||
693 694 695 696 697 698 699 | __allocStruct(StructCtor, this, externalMemory); }else{ __allocStruct(StructCtor, this); } }; Object.defineProperties(StructCtor,{ debugFlags: debugFlags, | < < < < < < < < < < < < < < < < < < < | 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 | __allocStruct(StructCtor, this, externalMemory); }else{ __allocStruct(StructCtor, this); } }; Object.defineProperties(StructCtor,{ debugFlags: debugFlags, isA: rop((v)=>v instanceof StructCtor), memberKey: __memberKeyProp, memberKeys: __structMemberKeys, methodInfoForKey: rop(function(mKey){ }), structInfo: rop(structInfo), structName: rop(structName) }); StructCtor.prototype = new StructType(structName, structInfo, rop); Object.defineProperties(StructCtor.prototype,{ debugFlags: debugFlags, constructor: rop(StructCtor) /*if we assign StructCtor.prototype and don't do this then StructCtor!==instance.constructor!*/ }); Object.keys(structInfo.members).forEach( (name)=>makeMemberWrapper(StructCtor, name, structInfo.members[name]) ); return StructCtor; }; StructBinder.StructType = StructType; StructBinder.config = config; StructBinder.allocCString = __allocCString; if(!StructBinder.debugFlags){ StructBinder.debugFlags = SBF.__makeDebugFlags(SBF.debugFlags); } return StructBinder; }/*StructBinderFactory*/; |
Changes to ext/wasm/jaccwabyt/jaccwabyt.md.
1 2 3 4 5 6 | Jaccwabyt 🐇 ============================================================ **Jaccwabyt**: _JavaScript ⇄ C Struct Communication via WASM Byte Arrays_ | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 | Jaccwabyt 🐇 ============================================================ **Jaccwabyt**: _JavaScript ⇄ C Struct Communication via WASM Byte Arrays_ Welcome to Jaccwabyt, a JavaScript API which creates bindings for WASM-compiled C structs, defining them in such a way that changes to their state in JS are visible in C/WASM, and vice versa, permitting two-way interchange of struct state with very little user-side friction. (If that means nothing to you, neither will the rest of this page!) |
︙ | ︙ | |||
23 24 25 26 27 28 29 | Firefox and Chrome on Linux and all claims of Safari compatibility are based solely on feature compatibility tables provided at [MDN][]. **Formalities:** - Author: [Stephan Beal][sgb] | | | > > > > > > > > > > > > > > > > > > > > | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | Firefox and Chrome on Linux and all claims of Safari compatibility are based solely on feature compatibility tables provided at [MDN][]. **Formalities:** - Author: [Stephan Beal][sgb] - Project Homes: - <https://fossil.wanderinghorse.net/r/jaccwabyt>\ Is the primary home but... - <https://sqlite.org/src/dir/ext/wasm/jaccwabyt>\ ... most development happens here. The license for both this documentation and the software it documents is the same as [sqlite3][], the project from which this spinoff project was spawned: ----- > 2022-06-30: > > 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. ----- <a name='overview'></a> Table of Contents ============================================================ - [Overview](#overview) - [Architecture](#architecture) |
︙ | ︙ | |||
201 202 203 204 205 206 207 | dealloc: (m)=>Module['_free'](m) } ``` The StructBinder factory function returns a function which can then be used to create bindings for our structs. | < | 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | dealloc: (m)=>Module['_free'](m) } ``` The StructBinder factory function returns a function which can then be used to create bindings for our structs. <a name='step-2'></a> Step 2: Create a Struct Description ------------------------------------------------------------ The primary input for this framework is a JSON-compatible construct which describes a struct we want to bind. For example, given this C struct: |
︙ | ︙ | |||
277 278 279 280 281 282 283 | - **`i`** = `int32` (4 bytes) - **`j`** = `int64` (8 bytes) is only really usable if this code is built with BigInt support (e.g. using the Emscripten `-sWASM_BIGINT` build flag). Without that, this API may throw when encountering the `j` signature entry. - **`f`** = `float` (4 bytes) - **`d`** = `double` (8 bytes) | > > | | | | | | | | | | | > > > > > > | 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 | - **`i`** = `int32` (4 bytes) - **`j`** = `int64` (8 bytes) is only really usable if this code is built with BigInt support (e.g. using the Emscripten `-sWASM_BIGINT` build flag). Without that, this API may throw when encountering the `j` signature entry. - **`f`** = `float` (4 bytes) - **`d`** = `double` (8 bytes) - **`c`** = `int8` (1 byte) char - see notes below! - **`C`** = `uint8` (1 byte) unsigned char - see notes below! - **`p`** = `int32` (see notes below!) - **`P`** = Like `p` but with extra handling. Described below. - **`s`** = like `int32` but is a _hint_ that it's a pointer to a string so that _some_ (very limited) contexts may treat it as such, noting that such algorithms must, for lack of information to the contrary, assume both that the encoding is UTF-8 and that the pointer's member is NUL-terminated. If that is _not_ the case for a given string member, do not use `s`: use `i` or `p` instead and do any string handling yourself. Noting that: - **All of these types are numeric**. Attempting to set any struct-bound property to a non-numeric value will trigger an exception except in cases explicitly noted otherwise. - **"Char" types**: WASM does not define an `int8` type, nor does it distinguish between signed and unsigned. This API treats `c` as `int8` and `C` as `uint8` for purposes of getting and setting values when using the `DataView` class. It is _not_ recommended that client code use these types in new WASM-capable code, but they were added for the sake of binding some immutable legacy code to WASM. > Sidebar: Emscripten's public docs do not mention `p`, but their generated code includes `p` as an alias for `i`, presumably to mean "pointer". Though `i` is legal for pointer types in the signature, `p` is more descriptive, so this framework encourages the use of `p` for pointer-type members. Using `p` for pointers also helps future-proof the signatures against the eventuality that WASM eventually supports |
︙ | ︙ | |||
313 314 315 316 317 318 319 | some contexts, so its use is encouraged for pointer types. Signatures in the form `x(...)` denote function-pointer members and `x` denotes non-function members. Functions with no arguments use the form `x()`. For function-type signatures, the strings are formulated such that they can be passed to Emscripten's `addFunction()` after stripping out the `(` and `)` characters. For good measure, to match | | | | < < < < < < | 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 | some contexts, so its use is encouraged for pointer types. Signatures in the form `x(...)` denote function-pointer members and `x` denotes non-function members. Functions with no arguments use the form `x()`. For function-type signatures, the strings are formulated such that they can be passed to Emscripten's `addFunction()` after stripping out the `(` and `)` characters. For good measure, to match the public Emscripten docs, `p`, `c`, and `C`, should also be replaced with `i`. In JavaScript that might look like: > ``` signature.replace(/[^vipPsjfdcC]/g,'').replace(/[pPscC]/g,'i'); ``` <a name='step-2-pvsp'></a> ### `P` vs `p` in Method Signatures *This support is experimental and subject to change.* The method signature letter `p` means "pointer," which, in WASM, means "integer." `p` is treated as an integer for most contexts, while still also being a separate type (analog to how pointers in C are just a special use of unsigned numbers). A capital `P` changes the semantics of plain member pointers (but not, as of this writing, function pointer members) as follows: - When a `P`-type member is **set** via `myStruct.x=y`, if [`(y instanceof StructType)`][StructType] then the value of `y.pointer` is stored in `myStruct.x`. If `y` is neither a number nor a [StructType][], an exception is triggered (regardless of whether `p` or `P` is used). |
︙ | ︙ | |||
384 385 386 387 388 389 390 | const my = new MyStruct(); ``` It is important to understand that creating a new instance allocates memory on the WASM heap. We must not simply rely on garbage collection to clean up the instances because doing so will not free up the WASM heap memory. The correct way to free up that memory is to use the | | < < < < < < < < > > > > > > > > > > | 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 | const my = new MyStruct(); ``` It is important to understand that creating a new instance allocates memory on the WASM heap. We must not simply rely on garbage collection to clean up the instances because doing so will not free up the WASM heap memory. The correct way to free up that memory is to use the object's `dispose()` method. The following usage pattern offers one way to easily ensure proper cleanup of struct instances: > ```javascript const my = new MyStruct(); try { console.log(my.member1, my.member2, my.member3); my.member1 = 12; assert(12 === my.member1); /* ^^^ it may seem silly to test that, but recall that assigning that property encodes the value into a byte array in heap memory, not a normal JS property. Similarly, fetching the property decodes it from the byte array. */ // Pass the struct to C code which takes a MyStruct pointer: aCFunction( my.pointer ); } finally { my.dispose(); } ``` > Sidebar: the `finally` block will be run no matter how the `try` exits, whether it runs to completion, propagates an exception, or uses flow-control keywords like `return` or `break`. It is perfectly legal to use `try`/`finally` without a `catch`, and doing so is an ideal match for the memory management requirements of Jaccwaby-bound struct instances. It is often useful to wrap an existing instance of a C-side struct without taking over ownership of its memory. That can be achieved by simply passing a pointer to the constructor. For example: ```js const m = new MyStruct( functionReturningASharedPtr() ); // calling m.dispose() will _not_ free the wrapped C-side instance // but will trigger any ondispose handler. ``` Now that we have struct instances, there are a number of things we can do with them, as covered in the rest of this document. <a name='api'></a> API Reference |
︙ | ︙ | |||
544 545 546 547 548 549 550 | configured `config.dealloc()` function. - `config` The configuration object passed to the [StructBinderFactory][], primarily for accessing the memory (de)allocator and memory. Modifying any of its "significant" configuration values may have undefined results. | < < < < < < < < < < < < < < > > > > > > > > < < < | 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 | configured `config.dealloc()` function. - `config` The configuration object passed to the [StructBinderFactory][], primarily for accessing the memory (de)allocator and memory. Modifying any of its "significant" configuration values may have undefined results. <a name='api-structtype'></a> API: Struct Type ------------------------------------------------------------ The StructType class is a property of the [StructBinder][] function. Each constructor created by a [StructBinder][] inherits from _its own instance_ of the StructType class, which contains state specific to that struct type (e.g. the struct name and description metadata). StructTypes which are created via different [StructBinder][] instances are unrelated to each other, sharing no state except [StructBinderFactory][] config options. The StructType constructor cannot be called from client code. It is only called by the [StructBinder][]-generated [constructors][StructCtors]. The `StructBinder.StructType` object has the following "static" properties (^Which are accessible from individual instances via `theInstance.constructor`.): - `addOnDispose(...value)`\ If this object has no `ondispose` property, this function creates it as an array and pushes the given value(s) onto it. If the object has a function-typed `ondispose` property, this call replaces it with an array and moves that function into the array. In all other cases, `ondispose` is assumed to be an array and the argument(s) is/are appended to it. Returns `this`. - `allocCString(str)` Identical to the [StructBinder][] method of the same name. - `hasExternalPointer(object)` Returns true if the given object's `pointer` member refers to an "external" object. That is the case when a pointer is passed to a [struct's constructor][StructCtors]. If true, the memory is owned by someone other than the object and must outlive the object. - `isA(value)` Returns true if its argument is a StructType instance _from the same [StructBinder][]_ as this StructType. - `memberKey(string)` Returns the given string wrapped in the configured `memberPrefix` and `memberSuffix` values. e.g. if passed `"x"` and `memberPrefix` |
︙ | ︙ | |||
615 616 617 618 619 620 621 | by the constructor. If this is not called before the JS engine cleans up the object, a leak in the WASM heap memory pool will result. When `dispose()` is called, if the object has a property named `ondispose` then it is treated as follows: - If it is a function, it is called with the struct object as its `this`. That method must not throw - if it does, the exception will be ignored. | | > | | | > | | | < | 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 | by the constructor. If this is not called before the JS engine cleans up the object, a leak in the WASM heap memory pool will result. When `dispose()` is called, if the object has a property named `ondispose` then it is treated as follows: - If it is a function, it is called with the struct object as its `this`. That method must not throw - if it does, the exception will be ignored. - If it is an array, it may contain functions, pointers, other [StructType] instances, and/or JS strings. If an entry is a function, it is called as described above. If it's a number, it's assumed to be a pointer and is passed to the `dealloc()` function configured for the parent [StructBinder][]. If it's a [StructType][] instance then its `dispose()` method is called. If it's a JS string, it's assumed to be a helpful description of the next entry in the list and is simply ignored. Strings are supported primarily for use as debugging information. - Some struct APIs will manipulate the `ondispose` member, creating it as an array or converting it from a function to array as needed. - `lookupMember(memberName,throwIfNotFound=true)` Given the name of a mapped struct member, it returns the member description object. If not found, it either throws (if the 2nd |
︙ | ︙ | |||
734 735 736 737 738 739 740 | to the JS-side object. e.g. maybe simply pass `true` as the second argument to tell the constructor to take over ownership. Currently the pointer can be taken over using something like `myStruct.ondispose=[myStruct.pointer]` immediately after creation. These constructors have the following "static" members: | < < < < < < < < < < < < < < < < < < < < < < < < | 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 | to the JS-side object. e.g. maybe simply pass `true` as the second argument to tell the constructor to take over ownership. Currently the pointer can be taken over using something like `myStruct.ondispose=[myStruct.pointer]` immediately after creation. These constructors have the following "static" members: - `isA(value)` Returns true if its argument was created by this constructor. - `memberKey(string)` Works exactly as documented for [StructType][]. - `memberKeys(string)` Works exactly as documented for [StructType][]. - `structInfo` The structure description passed to [StructBinder][] when this constructor was generated. - `structName` The structure name passed to [StructBinder][] when this constructor was generated. |
︙ | ︙ |
Changes to ext/wasm/module-symbols.html.
︙ | ︙ | |||
88 89 90 91 92 93 94 | <li>Some APIs are explicitly filtered out of these lists because they are strictly for internal use within the JS/WASM APIs and its own test code. </li> <li>This page runs in the main UI thread so cannot see features which are only available in a Worker thread. If this page were to function via a Worker, it would not be able to see | | | < | 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | <li>Some APIs are explicitly filtered out of these lists because they are strictly for internal use within the JS/WASM APIs and its own test code. </li> <li>This page runs in the main UI thread so cannot see features which are only available in a Worker thread. If this page were to function via a Worker, it would not be able to see functionality only available in the main thread. Either way, it would be missing certain APIs. </li> </ul> <div class='initially-hidden'> <p>This page exposes a global symbol named <code>sqlite3</code> which can be inspected using the browser's dev tools. |
︙ | ︙ | |||
130 131 132 133 134 135 136 | <a id="sqlite3-version"></a> <h1><code>sqlite3.version</code> Object</h1> <p> The <code>sqlite3.version</code> object exposes the following... </p> <div id='list-version' class='pseudolist wide wrap-anywhere'></div> | | | | 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | <a id="sqlite3-version"></a> <h1><code>sqlite3.version</code> Object</h1> <p> The <code>sqlite3.version</code> object exposes the following... </p> <div id='list-version' class='pseudolist wide wrap-anywhere'></div> <a id="sqlite3-functions"></a> <h1><code>sqlite3_...()</code> Function List</h1> <p>The <code>sqlite3.capi</code> namespace exposes the following <a href='https://sqlite.org/c3ref/funclist.html'><code>sqlite3_...()</code> functions</a>... </p> <div id='list-functions' class='pseudolist wide'></div> <p> <code class='func-wasm'></code> = function is specific to the JS/WASM |
︙ | ︙ | |||
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | <div id='list-compile-options' class='pseudolist wide2'></div> </div><!-- .initially-hidden --> <script src="jswasm/sqlite3.js">/* This tag MUST be inside the fossil-doc block so that this part can work without modification in the wasm docs repo. */</script> <script>(async function(){ const eNew = (tag,parent)=>{ const e = document.createElement(tag); if(parent) parent.appendChild(e); return e; }; const eLi = (label,parent)=>{ const e = eNew('span',parent); e.innerText = label; return e; }; const E = (sel)=>document.querySelector(sel); const EAll = (sel)=>document.querySelectorAll(sel); const eFuncs = E('#list-functions'), eConst = E('#list-constants'); const renderConst = function(name){ eLi(name, eConst); }; const renderFunc = function(name){ let lbl = name+'()'; | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 | <div id='list-compile-options' class='pseudolist wide2'></div> </div><!-- .initially-hidden --> <script src="jswasm/sqlite3.js">/* This tag MUST be inside the fossil-doc block so that this part can work without modification in the wasm docs repo. */</script> <script>(async function(){ const apiLinks = Object.assign(Object.create(null),{ sqlite3_aggregate_context: 'www:/c3ref/aggregate_context.html', sqlite3_bind_blob: 'www:/c3ref/bind_blob.html', sqlite3_bind_double: 'www:/c3ref/bind_blob.html', sqlite3_bind_int: 'www:/c3ref/bind_blob.html', sqlite3_bind_int64: 'www:/c3ref/bind_blob.html', sqlite3_bind_null: 'www:/c3ref/bind_blob.html', sqlite3_bind_parameter_count: 'www:/c3ref/bind_parameter_count.html', sqlite3_bind_parameter_index: 'www:/c3ref/bind_parameter_index.html', sqlite3_bind_pointer: 'www:/c3ref/bind_blob.html', sqlite3_bind_text: 'www:/c3ref/bind_blob.html', sqlite3_busy_handler: 'www:/c3ref/busy_handler.html', sqlite3_busy_timeout: 'www:/c3ref/busy_timeout.html', sqlite3_changes: 'www:/c3ref/changes.html', sqlite3_changes64: 'www:/c3ref/changes.html', sqlite3_clear_bindings: 'www:/c3ref/clear_bindings.html', sqlite3_close_v2: 'www:/c3ref/close.html', sqlite3_collation_needed: 'www:/c3ref/collation_needed.html', sqlite3_column_blob: 'www:/c3ref/column_blob.html', sqlite3_column_bytes: 'www:/c3ref/column.html', sqlite3_column_count: 'www:/c3ref/column_count.html', sqlite3_column_double: 'www:/c3ref/column_blob.html', sqlite3_column_int: 'www:/c3ref/column_blob.html', sqlite3_column_int64: 'www:/c3ref/column_blob.html', sqlite3_column_name: 'www:/c3ref/column_name.html', sqlite3_column_text: 'www:/c3ref/column_blob.html', sqlite3_column_type: 'www:/c3ref/column_blob.html', sqlite3_column_value: 'www:/c3ref/column_blob.html', sqlite3_compileoption_get: 'www:/c3ref/compileoption_get.html', sqlite3_compileoption_used: 'www:/c3ref/compileoption_get.html', sqlite3_complete: 'www:/c3ref/complete.html', sqlite3_config: 'www:/c3ref/config.html', sqlite3_context_db_handle: 'www:/c3ref/context_db_handle.html', sqlite3_create_collation: 'www:/c3ref/create_collation.html', sqlite3_create_collation_v2: 'www:/c3ref/create_collation.html', sqlite3_create_function: 'wasm:/api-c-style.md#sqlite3_create_function', sqlite3_create_function_v2: 'wasm:/api-c-style.md#sqlite3_create_function', sqlite3_create_module: 'www:/c3ref/create_module.html', sqlite3_create_module_v2: 'www:/c3ref/create_module.html', sqlite3_create_window_function: 'wasm:/api-c-style.md#sqlite3_create_function', sqlite3_db_config: 'wasm:/api-c-style.md#sqlite3_db_config', sqlite3_data_count: 'www:/c3ref/data_count.html', sqlite3_db_filename: 'www:/c3ref/db_filename.html', sqlite3_db_handle: 'www:/c3ref/db_handle.html', sqlite3_db_name: 'www:/c3ref/db_name.html', sqlite3_db_status: 'www:/c3ref/db_status.html', sqlite3_declare_vtab: 'www:/c3ref/declare_vtab.html', sqlite3_deserialize: 'wasm:/api-c-style.md#sqlite3_deserialize', sqlite3_drop_modules: 'www:/c3ref/drop_modules.html', sqlite3_errcode: 'www:/c3ref/errcode.html', sqlite3_errmsg: 'www:/c3ref/errcode.html', sqlite3_error_offset: 'www:/c3ref/error_offset.html', sqlite3_errstr: 'www:/c3ref/errcode.html', sqlite3_exec: 'wasm:/api-c-style.md#sqlite3_exec', sqlite3_expanded_sql: 'www:/c3ref/expanded_sql.html', sqlite3_extended_errcode: 'www:/c3ref/errcode.html', sqlite3_extended_result_codes: 'www:/c3ref/extended_result_codes.html', sqlite3_file_control: 'www:/c3ref/file_control.html', sqlite3_finalize: 'www:/c3ref/finalize.html', sqlite3_free: 'www:/c3ref/free.html', sqlite3_get_auxdata: 'www:/c3ref/get_auxdata.html', sqlite3_initialize: 'www:/c3ref/initialize.html', sqlite3_keyword_check: 'www:/c3ref/keyword_check.html', sqlite3_keyword_count: 'www:/c3ref/keyword_check.html', sqlite3_keyword_name: 'www:/c3ref/keyword_check.html', sqlite3_last_insert_rowid: 'www:/c3ref/last_insert_rowid.html', sqlite3_libversion: 'www:/c3ref/libversion.html', sqlite3_libversion_number: 'www:/c3ref/libversion.html', sqlite3_limit: 'www:/c3ref/limit.html', sqlite3_malloc: 'www:/c3ref/free.html', sqlite3_malloc64: 'www:/c3ref/free.html', sqlite3_msize: 'www:/c3ref/free.html', sqlite3_open: 'www:/c3ref/open.html', sqlite3_open_v2: 'www:/c3ref/open.html', sqlite3_overload_function: 'www:/c3ref/overload_function.html', sqlite3_prepare_v2: 'wasm:/api-c-style.md#sqlite3_prepare_v2', sqlite3_prepare_v3: 'wasm:/api-c-style.md#sqlite3_prepare_v2', sqlite3_progress_handler: 'www:/c3ref/progress_handler.html', sqlite3_randomness: 'wasm:/api-c-style.md#sqlite3_randomness', sqlite3_realloc: 'www:/c3ref/free.html', sqlite3_realloc64: 'www:/c3ref/free.html', sqlite3_reset: 'www:/c3ref/reset.html', sqlite3_result_blob: 'www:/c3ref/result_blob.html', sqlite3_result_double: 'www:/c3ref/result_blob.html', sqlite3_result_error: 'www:/c3ref/result_blob.html', sqlite3_result_error_code: 'www:/c3ref/result_blob.html', sqlite3_result_error_nomem: 'www:/c3ref/result_blob.html', sqlite3_result_error_toobig: 'www:/c3ref/result_blob.html', sqlite3_result_int: 'www:/c3ref/result_blob.html', sqlite3_result_int64: 'www:/c3ref/result_blob.html', sqlite3_result_null: 'www:/c3ref/result_blob.html', sqlite3_result_pointer: 'www:/c3ref/result_blob.html', sqlite3_result_subtype: 'www:/c3ref/result_subtype.html', sqlite3_result_text: 'www:/c3ref/result_blob.html', sqlite3_result_zeroblob: 'www:/c3ref/result_blob.html', sqlite3_result_zeroblob64: 'www:/c3ref/result_blob.html', sqlite3_serialize: 'www:/c3ref/serialize.html', sqlite3_set_authorizer: 'wasm:/api-c-style.md#sqlite3_set_authorizer', sqlite3_set_auxdata: 'www:/c3ref/get_auxdata.html', sqlite3_set_last_insert_rowid: 'www:/c3ref/set_last_insert_rowid', sqlite3_shutdown: 'www:/c3ref/initialize.html', sqlite3_sourceid: 'www:/c3ref/libversion.html', sqlite3_sql: 'www:/c3ref/expanded_sql.html', sqlite3_status: 'www:/c3ref/status.html', sqlite3_status64: 'www:/c3ref/status.html', sqlite3_step: 'www:/c3ref/step.html', sqlite3_stmt_isexplain: 'www:/c3ref/stmt_isexplain.html', sqlite3_stmt_readonly: 'www:/c3ref/stmt_readonly.html', sqlite3_stmt_status: 'www:/c3ref/stmt_status.html', sqlite3_strglob: 'www:/c3ref/strglob.html', sqlite3_stricmp: 'www:/c3ref/stricmp.html', sqlite3_strlike: 'www:/c3ref/strlike.html', sqlite3_strnicmp: 'www:/c3ref/strnicmp.html', sqlite3_table_column_metadata: 'www:/c3ref/table_column_metadata.html', sqlite3_total_changes: 'www:/c3ref/total_changes.html', sqlite3_total_changes64: 'www:/c3ref/total_changes.html', sqlite3_trace_v2: 'www:/c3ref/trace_v2.html', sqlite3_txn_state: 'www:/c3ref/txn_state.html', sqlite3_uri_boolean: 'www:/c3ref/uri_boolean.html', sqlite3_uri_int64: 'www:/c3ref/uri_boolean.html', sqlite3_uri_key: 'www:/c3ref/uri_boolean.html', sqlite3_uri_parameter: 'www:/c3ref/uri_boolean.html', sqlite3_user_data: 'www:/c3ref/user_data.html', sqlite3_value_blob: 'www:/c3ref/value_blob.html', sqlite3_value_bytes: 'www:/c3ref/value_blob.html', sqlite3_value_double: 'www:/c3ref/value_blob.html', sqlite3_value_dup: 'www:/c3ref/value_dup.html', sqlite3_value_free: 'www:/c3ref/value_dup.html', sqlite3_value_frombind: 'www:/c3ref/value_blob.html', sqlite3_value_int: 'www:/c3ref/value_blob.html', sqlite3_value_int64: 'www:/c3ref/value_blob.html', sqlite3_value_nochange: 'www:/c3ref/value_blob.html', sqlite3_value_numeric_type: 'www:/c3ref/value_blob.html', sqlite3_value_pointer: 'www:/c3ref/value_blob.html', sqlite3_value_subtype: 'www:/c3ref/value_subtype.html', sqlite3_value_text: 'www:/c3ref/value_blob.html', sqlite3_value_type: 'www:/c3ref/value_blob.html', sqlite3_vfs_find: 'www:/c3ref/vfs_find.html', sqlite3_vfs_register: 'www:/c3ref/vfs_find.html', sqlite3_vfs_unregister: 'www:/c3ref/vfs_find.html', sqlite3_vtab_collation: 'www:/c3ref/vtab_collation.html', sqlite3_vtab_config: 'www:/c3ref/vtab_config.html', sqlite3_vtab_distinct: 'www:/c3ref/vtab_distinct.html', sqlite3_vtab_in: 'www:/c3ref/vtab_in.html', sqlite3_vtab_in_first: 'www:/c3ref/vtab_in_first.html', sqlite3_vtab_in_next: 'www:/c3ref/vtab_in_next.html', sqlite3_vtab_nochange: 'www:/c3ref/vtab_nochange.html', sqlite3_vtab_on_conflict: 'www:/c3ref/vtab_on_conflict.html', sqlite3_vtab_rhs_value: 'www:/c3ref/vtab_rhs_value.html', sqlite3_column_js: 'wasm:/api-c-style.md#sqlite3_column_js', sqlite3_js_aggregate_context: 'wasm:/api-c-style.md#sqlite3_js_aggregate_context', sqlite3_js_db_export: 'wasm:/api-c-style.md#sqlite3_js_db_export', sqlite3_js_db_uses_vfs: 'wasm:/api-c-style.md#sqlite3_js_db_uses_vfs', sqlite3_js_db_vfs: 'wasm:/api-c-style.md#sqlite3_js_db_vfs', sqlite3_js_kvvfs_clear: 'wasm:/api-c-style.md#sqlite3_js_kvvfs', sqlite3_js_kvvfs_size: 'wasm:/api-c-style.md#sqlite3_js_kvvfs', sqlite3_js_rc_str: 'wasm:/api-c-style.md#sqlite3_js_rc_str', sqlite3_js_vfs_create_file: 'wasm:/api-c-style.md#sqlite3_js_vfs_create_file', sqlite3_js_vfs_list: 'wasm:/api-c-style.md#sqlite3_js_vfs_list', sqlite3_result_error_js: 'wasm:/api-c-style.md#sqlite3_result_error_js', sqlite3_result_js: 'wasm:/api-c-style.md#sqlite3_result_js', sqlite3_value_to_js: 'wasm:/api-c-style.md#sqlite3_value_to_js', sqlite3_values_to_js: 'wasm:/api-c-style.md#sqlite3_values_to_js', xform: (v)=>{ if(v){ return v.replace('www:','https://sqlite.org') .replace('wasm:','https://sqlite.org/wasm/doc/trunk'); }else{ return undefined; } } }); const eNew = (tag,parent)=>{ const e = document.createElement(tag); if(parent) parent.appendChild(e); return e; }; const eLi = (label,parent)=>{ const e = eNew('span',parent); e.innerText = label; return e; }; const eLink = (label,url,parent)=>{ const w = eNew('span',parent); const e = eNew('a',w); if(url){ e.href = url; e.target = 'sqlite3-api-docs'; } e.innerText = label; return w; }; const E = (sel)=>document.querySelector(sel); const EAll = (sel)=>document.querySelectorAll(sel); const eFuncs = E('#list-functions'), eConst = E('#list-constants'); const renderConst = function(name){ eLi(name, eConst); }; const renderFunc = function(name){ let lbl = name+'()'; const e = eLink(lbl, apiLinks.xform(apiLinks[name]), eFuncs); if(name.indexOf('_js')>0 || name.indexOf('_wasm')>0){ e.classList.add('func-wasm'); } }; const renderGeneric = function(name,value,eParent){ let lbl; if(value instanceof Function) lbl = name+'()'; else{ |
︙ | ︙ | |||
254 255 256 257 258 259 260 | ); renderX( E('#list-version'), sqlite3.version, Object.keys(sqlite3.version) ); /* sqlite3_...() and SQLITE_... */ | | | | < < > | > | > | > > > > > > > | | | 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 | ); renderX( E('#list-version'), sqlite3.version, Object.keys(sqlite3.version) ); /* sqlite3_...() and SQLITE_... */ const lists = {c: [/*constants*/], f: [/*functions*/], s: [/*structs*/]}; /* Exclude these from the function list... */ const excludeCapi = [ // WASMFS stuff: 'sqlite3_wasmfs_filename_is_persistent', 'sqlite3_wasmfs_opfs_dir' ]; for(const [k,v] of Object.entries(capi)){ if(k.startsWith('SQLITE_')){ lists.c.push(k); }else if(k.startsWith('sqlite3_')){ if(excludeCapi.indexOf(k)>=0) continue; if(v.structInfo){ // assume this is a StructType-type. continue; } lists.f.push(k); } } lists.c.sort().forEach(renderConst); lists.f.sort().forEach(renderFunc); lists.c = lists.f = null; renderX(E('#list-oo1'), sqlite3.oo1, Object.keys(sqlite3.oo1) ); const excludeWasm = ['ctype']; renderX(E('#list-wasm'), |
︙ | ︙ |
Changes to ext/wasm/speedtest1-wasmfs.html.
︙ | ︙ | |||
88 89 90 91 92 93 94 | const logErr = function(...args){ console.error(...args); logList.push('ERROR: '+args.join(' ')); }; const runTests = function(sqlite3){ console.log("Module inited."); | | | > | 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | const logErr = function(...args){ console.error(...args); logList.push('ERROR: '+args.join(' ')); }; const runTests = function(sqlite3){ console.log("Module inited."); const wasm = sqlite3.wasm; const __unlink = wasm.xWrap("sqlite3_wasm_vfs_unlink", "int", ["*","string"]); const unlink = (fn)=>__unlink(0,fn); const pDir = wasmfsDir(wasm); if(pDir) log2('',"Persistent storage:",pDir); else{ log2('error',"Expecting persistent storage in this build."); return; } const scope = wasm.scopedAllocPush(); |
︙ | ︙ |
Changes to ext/wasm/speedtest1-worker.html.
︙ | ︙ | |||
36 37 38 39 40 41 42 | </div> <div class='toolbar-inner-vertical'> <div id='toolbar-selected-flags'></div> <div class='toolbar-inner-vertical'> <span>→ <a id='link-main-thread' href='#' target='speedtest-main' title='Start speedtest1.html with the selected flags'>speedtest1</a> </span> | | | 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | </div> <div class='toolbar-inner-vertical'> <div id='toolbar-selected-flags'></div> <div class='toolbar-inner-vertical'> <span>→ <a id='link-main-thread' href='#' target='speedtest-main' title='Start speedtest1.html with the selected flags'>speedtest1</a> </span> <span class='hidden'>→ <a id='link-wasmfs' href='#' target='speedtest-wasmfs' title='Start speedtest1-wasmfs.html with the selected flags'>speedtest1-wasmfs</a> </span> <span>→ <a id='link-kvvfs' href='#' target='speedtest-kvvfs' title='Start kvvfs speedtest1 with the selected flags'>speedtest1-kvvfs</a> </span> </div> </div> |
︙ | ︙ | |||
71 72 73 74 75 76 77 | of the bigger tests. Without it, those tests create a combined total of 140k implicit transactions, reducing their speed to an absolute crawl, especially when WASMFS is activated. </li> <li>The easiest way to try different optimization levels is, from this directory: | | | 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | of the bigger tests. Without it, those tests create a combined total of 140k implicit transactions, reducing their speed to an absolute crawl, especially when WASMFS is activated. </li> <li>The easiest way to try different optimization levels is, from this directory: <pre>$ rm -f jswasm/speedtest1.js; make -e emcc_opt='-O2' speedtest1</pre> Then reload this page. -O2 seems to consistently produce the fastest results. </li> </ul> </div> <style> #test-output { white-space: break-spaces; |
︙ | ︙ |
Changes to ext/wasm/speedtest1.html.
︙ | ︙ | |||
36 37 38 39 40 41 42 | <div>Output is delayed/buffered because we cannot update the UI while the speedtest is running. Output will appear below when ready... <div id='test-output'></div> <script src="common/SqliteTestUtil.js"></script> <script src="jswasm/speedtest1.js"></script> <script>(function(){ /** | | | | | 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | <div>Output is delayed/buffered because we cannot update the UI while the speedtest is running. Output will appear below when ready... <div id='test-output'></div> <script src="common/SqliteTestUtil.js"></script> <script src="jswasm/speedtest1.js"></script> <script>(function(){ /** If this environment contains WASMFS with OPFS, this function initializes it and returns the name of the dir on which OPFS is mounted, else it returns an empty string. */ const wasmfsDir = function f(wasmUtil){ if(undefined !== f._) return f._; const pdir = '/persistent'; if( !self.FileSystemHandle || !self.FileSystemDirectoryHandle || !self.FileSystemFileHandle){ |
︙ | ︙ |
Changes to ext/wasm/test-opfs-vfs.js.
︙ | ︙ | |||
24 25 26 27 28 29 30 | if(!sqlite3.opfs){ const e = toss("OPFS is not available."); error(e); throw e; } const capi = sqlite3.capi; const pVfs = capi.sqlite3_vfs_find("opfs") || toss("Missing 'opfs' VFS."); | | | | 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | if(!sqlite3.opfs){ const e = toss("OPFS is not available."); error(e); throw e; } const capi = sqlite3.capi; const pVfs = capi.sqlite3_vfs_find("opfs") || toss("Missing 'opfs' VFS."); const oVfs = new capi.sqlite3_vfs(pVfs); log("OPFS VFS:",pVfs, oVfs); const wait = async (ms)=>{ return new Promise((resolve)=>setTimeout(resolve, ms)); }; const urlArgs = new URL(self.location.href).searchParams; const dbFile = "my-persistent.db"; if(urlArgs.has('delete')) sqlite3.opfs.unlink(dbFile); const db = new sqlite3.oo1.OpfsDb(dbFile,'ct'); log("db file:",db.filename); try{ if(opfs.entryExists(dbFile)){ let n = db.selectValue("select count(*) from sqlite_schema"); log("Persistent data found. sqlite_schema entry count =",n); } db.transaction((db)=>{ |
︙ | ︙ |
Changes to ext/wasm/tester1-worker.html.
1 2 3 4 5 6 7 8 | <!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"/> | | | < < < < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <!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>sqlite3 tester #1: Worker thread</title> <style></style> </head> <body> <h1 id='color-target'>sqlite3 tester #1: Worker thread</h1> <div>See <a href='tester1.html' target='tester1.html'>tester1.html</a> for the UI-thread variant.</div> <div class='input-wrapper'> <input type='checkbox' id='cb-log-reverse'> <label for='cb-log-reverse'>Reverse log order?</label> </div> <div id='test-output'></div> |
︙ | ︙ | |||
36 37 38 39 40 41 42 | }; const cbReverse = document.querySelector('#cb-log-reverse'); const cbReverseIt = ()=>{ logTarget.classList[cbReverse.checked ? 'add' : 'remove']('reverse'); }; cbReverse.addEventListener('change',cbReverseIt,true); cbReverseIt(); | > > > > > > > > > > > > > | | | | | > > > > | > | 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | }; const cbReverse = document.querySelector('#cb-log-reverse'); const cbReverseIt = ()=>{ logTarget.classList[cbReverse.checked ? 'add' : 'remove']('reverse'); }; cbReverse.addEventListener('change',cbReverseIt,true); cbReverseIt(); const urlParams = new URL(self.location.href).searchParams; const workerArgs = []; if(urlParams.has('esm')){ logHtml('warning',"Attempting to run an ES6 Worker Module, "+ "which is not supported by all browsers! "+ "e.g. Firefox (as of 2022-12) cannot do this."); workerArgs.push("tester1.mjs",{type:"module"}); document.querySelectorAll('title,#color-target').forEach((e)=>{ e.innerText = "sqlite3 tester #1: ES6 Worker Module"; }); }else{ workerArgs.push("tester1.js?sqlite3.dir=jswasm"); } const w = new Worker(...workerArgs); w.onmessage = function({data}){ switch(data.type){ case 'log': logHtml(data.payload.cssClass, ...data.payload.args); break; case 'error': logHtml('error', ...data.payload.args); break; case 'test-result':{ document.querySelector('#color-target').classList.add( data.payload.pass ? 'tests-pass' : 'tests-fail' ); const e = document.querySelector('title'); e.innerText = ( data.payload.pass ? 'PASS' : 'FAIL' ) + ': ' + e.innerText; break; } default: logHtml('error',"Unhandled message:",data.type); }; }; })();</script> </body> </html> |
Added ext/wasm/tester1.c-pp.html.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | <!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>sqlite3 tester #1: //#if target=es6-module ES6 Module in UI thread //#else UI thread //#endif </title> <style></style> </head> <body><h1 id='color-target'></h1> <div>See <a href='tester1-worker.html' target='tester1-worker.html'>tester1-worker.html</a> for the Worker-thread variant.</div> <div class='input-wrapper'> <input type='checkbox' id='cb-log-reverse'> <label for='cb-log-reverse'>Reverse log order?</label> </div> <div id='test-output'></div> <script>(function(){ document.querySelector('h1').innerHTML = document.querySelector('title').innerHTML; })();</script> //#if target=es6-module <script src="tester1.mjs" type="module"></script> //#else <script src="jswasm/sqlite3.js"></script> <script src="tester1.js"></script> //#endif </body> </html> |
Added ext/wasm/tester1.c-pp.js.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 | /* 2022-10-12 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. *********************************************************************** Main functional and regression tests for the sqlite3 WASM API. This mini-framework works like so: This script adds a series of test groups, each of which contains an arbitrary number of tests, into a queue. After loading of the sqlite3 WASM/JS module is complete, that queue is processed. If any given test fails, the whole thing fails. This script is built such that it can run from the main UI thread or worker thread. Test groups and individual tests can be assigned a predicate function which determines whether to run them or not, and this is specifically intended to be used to toggle certain tests on or off for the main/worker threads or the availability (or not) of optional features such as int64 support. Each test group defines a single state object which gets applied as the test functions' `this` for all tests in that group. Test functions can use that to, e.g., set up a db in an early test and close it in a later test. Each test gets passed the sqlite3 namespace object as its only argument. */ /* This file is intended to be processed by c-pp to inject (or not) code specific to ES6 modules which is illegal in non-module code. Non-ES6 module build and ES6 module for the main-thread: ./c-pp -f tester1.c-pp.js -o tester1.js ES6 worker module build: ./c-pp -f tester1.c-pp.js -o tester1-esm.js -Dtarget=es6-module */ //#if target=es6-module import {default as sqlite3InitModule} from './jswasm/sqlite3.mjs'; self.sqlite3InitModule = sqlite3InitModule; //#else 'use strict'; //#endif (function(self){ /** Set up our output channel differently depending on whether we are running in a worker thread or the main (UI) thread. */ let logClass; /* Predicate for tests/groups. */ const isUIThread = ()=>(self.window===self && self.document); /* Predicate for tests/groups. */ const isWorker = ()=>!isUIThread(); /* Predicate for tests/groups. */ const testIsTodo = ()=>false; const haveWasmCTests = ()=>{ return !!wasm.exports.sqlite3_wasm_test_intptr; }; { const mapToString = (v)=>{ switch(typeof v){ case 'number': case 'string': 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); if( isUIThread() ){ console.log("Running in the UI thread."); const logTarget = document.querySelector('#test-output'); 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 = 'tester1:cb-log-reverse'; const cbReverseIt = ()=>{ logTarget.classList[cbReverse.checked ? 'add' : 'remove']('reverse'); //localStorage.setItem(cbReverseKey, cbReverse.checked ? 1 : 0); }; cbReverse.addEventListener('change', cbReverseIt, true); /*if(localStorage.getItem(cbReverseKey)){ cbReverse.checked = !!(+localStorage.getItem(cbReverseKey)); }*/ cbReverseIt(); }else{ /* Worker thread */ console.log("Running in a Worker thread."); logClass = function(cssClass,...args){ postMessage({ type:'log', payload:{cssClass, args: normalizeArgs(args)} }); }; } } const reportFinalTestStatus = function(pass){ if(isUIThread()){ let e = document.querySelector('#color-target'); e.classList.add(pass ? 'tests-pass' : 'tests-fail'); e = document.querySelector('title'); e.innerText = (pass ? 'PASS' : 'FAIL') + ': ' + e.innerText; }else{ postMessage({type:'test-result', payload:{pass}}); } }; const log = (...args)=>{ //console.log(...args); logClass('',...args); } const warn = (...args)=>{ console.warn(...args); logClass('warning',...args); } const error = (...args)=>{ console.error(...args); logClass('error',...args); }; const toss = (...args)=>{ error(...args); throw new Error(args.join(' ')); }; const tossQuietly = (...args)=>{ throw new Error(args.join(' ')); }; const roundMs = (ms)=>Math.round(ms*100)/100; /** Helpers for writing sqlite3-specific tests. */ const TestUtil = { /** Running total of the number of tests run via this API. */ counter: 0, /* Separator line for log messages. */ separator: '------------------------------------------------------------', /** If expr is a function, it is called and its result is returned, coerced to a bool, else expr, coerced to a bool, is returned. */ toBool: function(expr){ return (expr instanceof Function) ? !!expr() : !!expr; }, /** Throws if expr is false. If expr is a function, it is called and its result is evaluated. If passed multiple arguments, those after the first are a message string which get applied as an exception message if the assertion fails. The message arguments are concatenated together with a space between each. */ assert: function f(expr, ...msg){ ++this.counter; if(!this.toBool(expr)){ throw new Error(msg.length ? msg.join(' ') : "Assertion failed."); } return this; }, /** Calls f() and squelches any exception it throws. If it does not throw, this function throws. */ mustThrow: function(f, msg){ ++this.counter; let err; try{ f(); } catch(e){err=e;} if(!err) throw new Error(msg || "Expected exception."); return this; }, /** Works like mustThrow() but expects filter to be a regex, function, or string to match/filter the resulting exception against. If f() does not throw, this test fails and an Error is thrown. If filter is a regex, the test passes if filter.test(error.message) passes. If it's a function, the test passes if filter(error) returns truthy. If it's a string, the test passes if the filter matches the exception message precisely. In all other cases the test fails, throwing an Error. If it throws, msg is used as the error report unless it's falsy, in which case a default is used. */ mustThrowMatching: function(f, filter, msg){ ++this.counter; let err; try{ f(); } catch(e){err=e;} if(!err) throw new Error(msg || "Expected exception."); let pass = false; if(filter instanceof RegExp) pass = filter.test(err.message); else if(filter instanceof Function) pass = filter(err); else if('string' === typeof filter) pass = (err.message === filter); if(!pass){ throw new Error(msg || ("Filter rejected this exception: "+err.message)); } return this; }, /** Throws if expr is truthy or expr is a function and expr() returns truthy. */ throwIf: function(expr, msg){ ++this.counter; if(this.toBool(expr)) throw new Error(msg || "throwIf() failed"); return this; }, /** Throws if expr is falsy or expr is a function and expr() returns falsy. */ throwUnless: function(expr, msg){ ++this.counter; if(!this.toBool(expr)) throw new Error(msg || "throwUnless() failed"); return this; }, eqApprox: (v1,v2,factor=0.05)=>(v1>=(v2-factor) && v1<=(v2+factor)), TestGroup: (function(){ let groupCounter = 0; const TestGroup = function(name, predicate){ this.number = ++groupCounter; this.name = name; this.predicate = predicate; this.tests = []; }; TestGroup.prototype = { addTest: function(testObj){ this.tests.push(testObj); return this; }, run: async function(sqlite3){ log(TestUtil.separator); logClass('group-start',"Group #"+this.number+':',this.name); const indent = ' '; if(this.predicate){ const p = this.predicate(sqlite3); if(!p || 'string'===typeof p){ logClass('warning',indent, "SKIPPING group:", p ? p : "predicate says to" ); return; } } const assertCount = TestUtil.counter; const groupState = Object.create(null); const skipped = []; let runtime = 0, i = 0; for(const t of this.tests){ ++i; const n = this.number+"."+i; log(indent, n+":", t.name); if(t.predicate){ const p = t.predicate(sqlite3); if(!p || 'string'===typeof p){ logClass('warning',indent, "SKIPPING:", p ? p : "predicate says to" ); skipped.push( n+': '+t.name ); continue; } } const tc = TestUtil.counter, now = performance.now(); await t.test.call(groupState, sqlite3); const then = performance.now(); runtime += then - now; logClass('faded',indent, indent, TestUtil.counter - tc, 'assertion(s) in', roundMs(then-now),'ms'); } logClass('green', "Group #"+this.number+":",(TestUtil.counter - assertCount), "assertion(s) in",roundMs(runtime),"ms"); if(0 && skipped.length){ logClass('warning',"SKIPPED test(s) in group",this.number+":",skipped); } } }; return TestGroup; })()/*TestGroup*/, testGroups: [], currentTestGroup: undefined, addGroup: function(name, predicate){ this.testGroups.push( this.currentTestGroup = new this.TestGroup(name, predicate) ); return this; }, addTest: function(name, callback){ let predicate; if(1===arguments.length){ this.currentTestGroup.addTest(arguments[0]); }else{ this.currentTestGroup.addTest({ name, predicate, test: callback }); } return this; }, runTests: async function(sqlite3){ return new Promise(async function(pok,pnok){ try { let runtime = 0; for(let g of this.testGroups){ const now = performance.now(); await g.run(sqlite3); runtime += performance.now() - now; } log(TestUtil.separator); logClass(['strong','green'], "Done running tests.",TestUtil.counter,"assertions in", roundMs(runtime),'ms'); pok(); reportFinalTestStatus(true); }catch(e){ error(e); pnok(e); reportFinalTestStatus(false); } }.bind(this)); } }/*TestUtil*/; const T = TestUtil; T.g = T.addGroup; T.t = T.addTest; let capi, wasm/*assigned after module init*/; //////////////////////////////////////////////////////////////////////// // End of infrastructure setup. Now define the tests... //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// T.g('Basic sanity checks') .t({ name:'sqlite3_config()', test:function(sqlite3){ for(const k of [ 'SQLITE_CONFIG_GETMALLOC', 'SQLITE_CONFIG_URI' ]){ T.assert(capi[k] > 0); } T.assert(capi.SQLITE_MISUSE===capi.sqlite3_config( capi.SQLITE_CONFIG_URI, 1 ), "MISUSE because the library has already been initialized."); T.assert(capi.SQLITE_MISUSE === capi.sqlite3_config( // not enough args capi.SQLITE_CONFIG_GETMALLOC )); T.assert(capi.SQLITE_NOTFOUND === capi.sqlite3_config( // unhandled-in-JS config option capi.SQLITE_CONFIG_GETMALLOC, 1 )); if(0){ log("We cannot _fully_ test sqlite3_config() after the library", "has been initialized (which it necessarily has been to", "set up various bindings) and we cannot shut it down ", "without losing the VFS registrations."); T.assert(0 === capi.sqlite3_config( capi.SQLITE_CONFIG_URI, 1 )); } } })/*sqlite3_config()*/ //////////////////////////////////////////////////////////////////// .t({ name: "JS wasm-side allocator", test: function(sqlite3){ if(sqlite3.config.useStdAlloc){ warn("Using system allocator. This violates the docs and", "may cause grief with certain APIs", "(e.g. sqlite3_deserialize())."); T.assert(wasm.alloc.impl === wasm.exports.malloc) .assert(wasm.dealloc === wasm.exports.free) .assert(wasm.realloc.impl === wasm.exports.realloc); }else{ T.assert(wasm.alloc.impl === wasm.exports.sqlite3_malloc) .assert(wasm.dealloc === wasm.exports.sqlite3_free) .assert(wasm.realloc.impl === wasm.exports.sqlite3_realloc); } } }) .t('Namespace object checks', function(sqlite3){ const wasmCtypes = wasm.ctype; T.assert(wasmCtypes.structs[0].name==='sqlite3_vfs'). assert(wasmCtypes.structs[0].members.szOsFile.sizeof>=4). assert(wasmCtypes.structs[1/*sqlite3_io_methods*/ ].members.xFileSize.offset>0); [ /* Spot-check a handful of constants to make sure they got installed... */ 'SQLITE_SCHEMA','SQLITE_NULL','SQLITE_UTF8', 'SQLITE_STATIC', 'SQLITE_DIRECTONLY', 'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE' ].forEach((k)=>T.assert('number' === typeof capi[k])); [/* Spot-check a few of the WASM API methods. */ 'alloc', 'dealloc', 'installFunction' ].forEach((k)=>T.assert(wasm[k] instanceof Function)); T.assert(capi.sqlite3_errstr(capi.SQLITE_IOERR_ACCESS).indexOf("I/O")>=0). assert(capi.sqlite3_errstr(capi.SQLITE_CORRUPT).indexOf('malformed')>0). assert(capi.sqlite3_errstr(capi.SQLITE_OK) === 'not an error'); try { throw new sqlite3.WasmAllocError; }catch(e){ T.assert(e instanceof Error) .assert(e instanceof sqlite3.WasmAllocError) .assert("Allocation failed." === e.message); } try { throw new sqlite3.WasmAllocError("test",{ cause: 3 }); }catch(e){ T.assert(3 === e.cause) .assert("test" === e.message); } try {throw new sqlite3.WasmAllocError("test","ing",".")} catch(e){T.assert("test ing ." === e.message)} try{ throw new sqlite3.SQLite3Error(capi.SQLITE_SCHEMA) } catch(e){ T.assert('SQLITE_SCHEMA' === e.message) .assert(capi.SQLITE_SCHEMA === e.resultCode); } try{ sqlite3.SQLite3Error.toss(capi.SQLITE_CORRUPT,{cause: true}) } catch(e){ T.assert('SQLITE_CORRUPT' === e.message) .assert(capi.SQLITE_CORRUPT === e.resultCode) .assert(true===e.cause); } try{ sqlite3.SQLite3Error.toss("resultCode check") } catch(e){ T.assert(capi.SQLITE_ERROR === e.resultCode) .assert('resultCode check' === e.message); } }) //////////////////////////////////////////////////////////////////// .t('strglob/strlike', function(sqlite3){ T.assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")). assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")). assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)). assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0)); }) //////////////////////////////////////////////////////////////////// ;/*end of basic sanity checks*/ //////////////////////////////////////////////////////////////////// T.g('C/WASM Utilities') .t('sqlite3.wasm namespace', function(sqlite3){ // TODO: break this into smaller individual test functions. const w = wasm; const chr = (x)=>x.charCodeAt(0); //log("heap getters..."); { const li = [8, 16, 32]; if(w.bigIntEnabled) li.push(64); for(const n of li){ const bpe = n/8; const s = w.heapForSize(n,false); T.assert(bpe===s.BYTES_PER_ELEMENT). assert(w.heapForSize(s.constructor) === s); const u = w.heapForSize(n,true); T.assert(bpe===u.BYTES_PER_ELEMENT). assert(s!==u). assert(w.heapForSize(u.constructor) === u); } } // alloc(), realloc(), allocFromTypedArray() { let m = w.alloc(14); let m2 = w.realloc(m, 16); T.assert(m === m2/* because of alignment */); T.assert(0 === w.realloc(m, 0)); m = m2 = 0; // Check allocation limits and allocator's responses... T.assert('number' === typeof sqlite3.capi.SQLITE_MAX_ALLOCATION_SIZE); if(!sqlite3.config.useStdAlloc){ const tooMuch = sqlite3.capi.SQLITE_MAX_ALLOCATION_SIZE + 1, isAllocErr = (e)=>e instanceof sqlite3.WasmAllocError; T.mustThrowMatching(()=>w.alloc(tooMuch), isAllocErr) .assert(0 === w.alloc.impl(tooMuch)) .mustThrowMatching(()=>w.realloc(0, tooMuch), isAllocErr) .assert(0 === w.realloc.impl(0, tooMuch)); } // Check allocFromTypedArray()... const byteList = [11,22,33] const u = new Uint8Array(byteList); m = w.allocFromTypedArray(u); for(let i = 0; i < u.length; ++i){ T.assert(u[i] === byteList[i]) .assert(u[i] === w.peek8(m + i)); } w.dealloc(m); m = w.allocFromTypedArray(u.buffer); for(let i = 0; i < u.length; ++i){ T.assert(u[i] === byteList[i]) .assert(u[i] === w.peek8(m + i)); } w.dealloc(m); T.mustThrowMatching( ()=>w.allocFromTypedArray(1), 'Value is not of a supported TypedArray type.' ); } { // Test peekXYZ()/pokeXYZ()... const m = w.alloc(8); T.assert( 17 === w.poke8(m,17).peek8(m) ) .assert( 31987 === w.poke16(m,31987).peek16(m) ) .assert( 345678 === w.poke32(m,345678).peek32(m) ) .assert( T.eqApprox( 345678.9, w.poke32f(m,345678.9).peek32f(m) ) ).assert( T.eqApprox( 4567890123.4, w.poke64f(m, 4567890123.4).peek64f(m) ) ); if(w.bigIntEnabled){ T.assert( BigInt(Number.MAX_SAFE_INTEGER) === w.poke64(m, Number.MAX_SAFE_INTEGER).peek64(m) ); } w.dealloc(m); } // isPtr32() { const ip = w.isPtr32; T.assert(ip(0)) .assert(!ip(-1)) .assert(!ip(1.1)) .assert(!ip(0xffffffff)) .assert(ip(0x7fffffff)) .assert(!ip()) .assert(!ip(null)/*might change: under consideration*/) ; } //log("jstrlen()..."); { T.assert(3 === w.jstrlen("abc")).assert(4 === w.jstrlen("äbc")); } //log("jstrcpy()..."); { const fillChar = 10; let ua = new Uint8Array(8), rc, refill = ()=>ua.fill(fillChar); refill(); rc = w.jstrcpy("hello", ua); T.assert(6===rc).assert(0===ua[5]).assert(chr('o')===ua[4]); refill(); ua[5] = chr('!'); rc = w.jstrcpy("HELLO", ua, 0, -1, false); T.assert(5===rc).assert(chr('!')===ua[5]).assert(chr('O')===ua[4]); refill(); rc = w.jstrcpy("the end", ua, 4); //log("rc,ua",rc,ua); T.assert(4===rc).assert(0===ua[7]). assert(chr('e')===ua[6]).assert(chr('t')===ua[4]); refill(); rc = w.jstrcpy("the end", ua, 4, -1, false); T.assert(4===rc).assert(chr(' ')===ua[7]). assert(chr('e')===ua[6]).assert(chr('t')===ua[4]); refill(); rc = w.jstrcpy("", ua, 0, 1, true); //log("rc,ua",rc,ua); T.assert(1===rc).assert(0===ua[0]); refill(); rc = w.jstrcpy("x", ua, 0, 1, true); //log("rc,ua",rc,ua); T.assert(1===rc).assert(0===ua[0]); refill(); rc = w.jstrcpy('äbä', ua, 0, 1, true); T.assert(1===rc, 'Must not write partial multi-byte char.') .assert(0===ua[0]); refill(); rc = w.jstrcpy('äbä', ua, 0, 2, true); T.assert(1===rc, 'Must not write partial multi-byte char.') .assert(0===ua[0]); refill(); rc = w.jstrcpy('äbä', ua, 0, 2, false); T.assert(2===rc).assert(fillChar!==ua[1]).assert(fillChar===ua[2]); }/*jstrcpy()*/ //log("cstrncpy()..."); { const scope = w.scopedAllocPush(); try { let cStr = w.scopedAllocCString("hello"); const n = w.cstrlen(cStr); let cpy = w.scopedAlloc(n+10); let rc = w.cstrncpy(cpy, cStr, n+10); T.assert(n+1 === rc). assert("hello" === w.cstrToJs(cpy)). assert(chr('o') === w.peek8(cpy+n-1)). assert(0 === w.peek8(cpy+n)); let cStr2 = w.scopedAllocCString("HI!!!"); rc = w.cstrncpy(cpy, cStr2, 3); T.assert(3===rc). assert("HI!lo" === w.cstrToJs(cpy)). assert(chr('!') === w.peek8(cpy+2)). assert(chr('l') === w.peek8(cpy+3)); }finally{ w.scopedAllocPop(scope); } } //log("jstrToUintArray()..."); { let a = w.jstrToUintArray("hello", false); T.assert(5===a.byteLength).assert(chr('o')===a[4]); a = w.jstrToUintArray("hello", true); T.assert(6===a.byteLength).assert(chr('o')===a[4]).assert(0===a[5]); a = w.jstrToUintArray("äbä", false); T.assert(5===a.byteLength).assert(chr('b')===a[2]); a = w.jstrToUintArray("äbä", true); T.assert(6===a.byteLength).assert(chr('b')===a[2]).assert(0===a[5]); } //log("allocCString()..."); { const jstr = "hällo, world!"; const [cstr, n] = w.allocCString(jstr, true); T.assert(14 === n) .assert(0===w.peek8(cstr+n)) .assert(chr('!')===w.peek8(cstr+n-1)); w.dealloc(cstr); } //log("scopedAlloc() and friends..."); { const alloc = w.alloc, dealloc = w.dealloc; w.alloc = w.dealloc = null; T.assert(!w.scopedAlloc.level) .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/) .mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/); w.alloc = alloc; T.mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/); w.dealloc = dealloc; T.mustThrowMatching(()=>w.scopedAllocPop(), /^Invalid state/) .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/) .mustThrowMatching(()=>w.scopedAlloc.level=0, /read-only/); const asc = w.scopedAllocPush(); let asc2; try { const p1 = w.scopedAlloc(16), p2 = w.scopedAlloc(16); T.assert(1===w.scopedAlloc.level) .assert(Number.isFinite(p1)) .assert(Number.isFinite(p2)) .assert(asc[0] === p1) .assert(asc[1]===p2); asc2 = w.scopedAllocPush(); const p3 = w.scopedAlloc(16); T.assert(2===w.scopedAlloc.level) .assert(Number.isFinite(p3)) .assert(2===asc.length) .assert(p3===asc2[0]); const [z1, z2, z3] = w.scopedAllocPtr(3); T.assert('number'===typeof z1).assert(z2>z1).assert(z3>z2) .assert(0===w.peek32(z1), 'allocPtr() must zero the targets') .assert(0===w.peek32(z3)); }finally{ // Pop them in "incorrect" order to make sure they behave: w.scopedAllocPop(asc); T.assert(0===asc.length); T.mustThrowMatching(()=>w.scopedAllocPop(asc), /^Invalid state object/); if(asc2){ T.assert(2===asc2.length,'Should be p3 and z1'); w.scopedAllocPop(asc2); T.assert(0===asc2.length); T.mustThrowMatching(()=>w.scopedAllocPop(asc2), /^Invalid state object/); } } T.assert(0===w.scopedAlloc.level); w.scopedAllocCall(function(){ T.assert(1===w.scopedAlloc.level); const [cstr, n] = w.scopedAllocCString("hello, world", true); T.assert(12 === n) .assert(0===w.peek8(cstr+n)) .assert(chr('d')===w.peek8(cstr+n-1)); }); }/*scopedAlloc()*/ //log("xCall()..."); { const pJson = w.xCall('sqlite3_wasm_enum_json'); T.assert(Number.isFinite(pJson)).assert(w.cstrlen(pJson)>300); } //log("xWrap()..."); { T.mustThrowMatching(()=>w.xWrap('sqlite3_libversion',null,'i32'), /requires 0 arg/). assert(w.xWrap.resultAdapter('i32') instanceof Function). assert(w.xWrap.argAdapter('i32') instanceof Function); let fw = w.xWrap('sqlite3_libversion','utf8'); T.mustThrowMatching(()=>fw(1), /requires 0 arg/); let rc = fw(); T.assert('string'===typeof rc).assert(rc.length>5); rc = w.xCallWrapped('sqlite3_wasm_enum_json','*'); T.assert(rc>0 && Number.isFinite(rc)); rc = w.xCallWrapped('sqlite3_wasm_enum_json','utf8'); T.assert('string'===typeof rc).assert(rc.length>300); { // 'string:static' argAdapter() sanity checks... let argAd = w.xWrap.argAdapter('string:static'); let p0 = argAd('foo'), p1 = argAd('bar'); T.assert(w.isPtr(p0) && w.isPtr(p1)) .assert(p0 !== p1) .assert(p0 === argAd('foo')) .assert(p1 === argAd('bar')); } // 'string:flexible' argAdapter() sanity checks... w.scopedAllocCall(()=>{ const argAd = w.xWrap.argAdapter('string:flexible'); const cj = (v)=>w.cstrToJs(argAd(v)); T.assert('Hi' === cj('Hi')) .assert('hi' === cj(['h','i'])) .assert('HI' === cj(new Uint8Array([72, 73]))); }); // jsFuncToWasm() { const fsum3 = (x,y,z)=>x+y+z; fw = w.jsFuncToWasm('i(iii)', fsum3); T.assert(fw instanceof Function) .assert( fsum3 !== fw ) .assert( 3 === fw.length ) .assert( 6 === fw(1,2,3) ); T.mustThrowMatching( ()=>w.jsFuncToWasm('x()', function(){}), 'Invalid signature letter: x'); } // xWrap(Function,...) { let fp; try { const fmy = function fmy(i,s,d){ if(fmy.debug) log("fmy(",...arguments,")"); T.assert( 3 === i ) .assert( w.isPtr(s) ) .assert( w.cstrToJs(s) === 'a string' ) .assert( T.eqApprox(1.2, d) ); return w.allocCString("hi"); }; fmy.debug = false; const xwArgs = ['string:dealloc', ['i32', 'string', 'f64']]; fw = w.xWrap(fmy, ...xwArgs); const fmyArgs = [3, 'a string', 1.2]; let rc = fw(...fmyArgs); T.assert( 'hi' === rc ); if(0){ /* Retain this as a "reminder to self"... This extra level of indirection does not work: the string argument is ending up as a null in fmy() but the numeric arguments are making their ways through What's happening is: installFunction() is creating a WASM-compatible function instance. When we pass a JS string into there it's getting coerced into `null` before being passed on to the lower-level wrapper. */ fmy.debug = true; fp = wasm.installFunction('i(isd)', fw); fw = w.functionEntry(fp); rc = fw(...fmyArgs); log("rc =",rc); T.assert( 'hi' === rc ); // Similarly, this does not work: //let fpw = w.xWrap(fp, null, [null,null,null]); //rc = fpw(...fmyArgs); //log("rc =",rc); //T.assert( 'hi' === rc ); } }finally{ wasm.uninstallFunction(fp); } } if(haveWasmCTests()){ if(!sqlite3.config.useStdAlloc){ fw = w.xWrap('sqlite3_wasm_test_str_hello', 'utf8:dealloc',['i32']); rc = fw(0); T.assert('hello'===rc); rc = fw(1); T.assert(null===rc); } if(w.bigIntEnabled){ w.xWrap.resultAdapter('thrice', (v)=>3n*BigInt(v)); w.xWrap.argAdapter('twice', (v)=>2n*BigInt(v)); fw = w.xWrap('sqlite3_wasm_test_int64_times2','thrice','twice'); rc = fw(1); T.assert(12n===rc); w.scopedAllocCall(function(){ const pI1 = w.scopedAlloc(8), pI2 = pI1+4; w.pokePtr([pI1, pI2], 0); const f = w.xWrap('sqlite3_wasm_test_int64_minmax',undefined,['i64*','i64*']); const [r1, r2] = w.peek64([pI1, pI2]); T.assert(!Number.isSafeInteger(r1)).assert(!Number.isSafeInteger(r2)); }); } } }/*xWrap()*/ }/*WhWasmUtil*/) //////////////////////////////////////////////////////////////////// .t('sqlite3.StructBinder (jaccwabyt🐇)', function(sqlite3){ const S = sqlite3, W = S.wasm; const MyStructDef = { sizeof: 16, members: { p4: {offset: 0, sizeof: 4, signature: "i"}, pP: {offset: 4, sizeof: 4, signature: "P"}, ro: {offset: 8, sizeof: 4, signature: "i", readOnly: true}, cstr: {offset: 12, sizeof: 4, signature: "s"} } }; if(W.bigIntEnabled){ const m = MyStructDef; m.members.p8 = {offset: m.sizeof, sizeof: 8, signature: "j"}; m.sizeof += m.members.p8.sizeof; } const StructType = S.StructBinder.StructType; const K = S.StructBinder('my_struct',MyStructDef); T.mustThrowMatching(()=>K(), /via 'new'/). mustThrowMatching(()=>new K('hi'), /^Invalid pointer/); const k1 = new K(), k2 = new K(); try { T.assert(k1.constructor === K). assert(K.isA(k1)). assert(k1 instanceof K). assert(K.prototype.lookupMember('p4').key === '$p4'). assert(K.prototype.lookupMember('$p4').name === 'p4'). mustThrowMatching(()=>K.prototype.lookupMember('nope'), /not a mapped/). assert(undefined === K.prototype.lookupMember('nope',false)). assert(k1 instanceof StructType). assert(StructType.isA(k1)). mustThrowMatching(()=>k1.$ro = 1, /read-only/); Object.keys(MyStructDef.members).forEach(function(key){ key = K.memberKey(key); T.assert(0 == k1[key], "Expecting allocation to zero the memory "+ "for "+key+" but got: "+k1[key]+ " from "+k1.memoryDump()); }); T.assert('number' === typeof k1.pointer). mustThrowMatching(()=>k1.pointer = 1, /pointer/); k1.$p4 = 1; k1.$pP = 2; T.assert(1 === k1.$p4).assert(2 === k1.$pP); if(MyStructDef.members.$p8){ k1.$p8 = 1/*must not throw despite not being a BigInt*/; k1.$p8 = BigInt(Number.MAX_SAFE_INTEGER * 2); T.assert(BigInt(2 * Number.MAX_SAFE_INTEGER) === k1.$p8); } T.assert(!k1.ondispose); k1.setMemberCString('cstr', "A C-string."); T.assert(Array.isArray(k1.ondispose)). assert(k1.ondispose[0] === k1.$cstr). assert('number' === typeof k1.$cstr). assert('A C-string.' === k1.memberToJsString('cstr')); k1.$pP = k2; T.assert(k1.$pP === k2.pointer); k1.$pP = null/*null is special-cased to 0.*/; T.assert(0===k1.$pP); let ptr = k1.pointer; k1.dispose(); T.assert(undefined === k1.pointer). mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/); }finally{ k1.dispose(); k2.dispose(); } if(!W.bigIntEnabled){ log("Skipping WasmTestStruct tests: BigInt not enabled."); return; } const WTStructDesc = W.ctype.structs.filter((e)=>'WasmTestStruct'===e.name)[0]; const autoResolvePtr = true /* EXPERIMENTAL */; if(autoResolvePtr){ WTStructDesc.members.ppV.signature = 'P'; } const WTStruct = S.StructBinder(WTStructDesc); //log(WTStruct.structName, WTStruct.structInfo); const wts = new WTStruct(); //log("WTStruct.prototype keys:",Object.keys(WTStruct.prototype)); try{ T.assert(wts.constructor === WTStruct). assert(WTStruct.memberKeys().indexOf('$ppV')>=0). assert(wts.memberKeys().indexOf('$v8')>=0). assert(!K.isA(wts)). assert(WTStruct.isA(wts)). assert(wts instanceof WTStruct). assert(wts instanceof StructType). assert(StructType.isA(wts)). assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8). assert(0===wts.$ppV).assert(0===wts.$xFunc); const testFunc = W.xGet('sqlite3_wasm_test_struct'/*name gets mangled in -O3 builds!*/); let counter = 0; //log("wts.pointer =",wts.pointer); const wtsFunc = function(arg){ /*log("This from a JS function called from C, "+ "which itself was called from JS. arg =",arg);*/ ++counter; if(3===counter){ tossQuietly("Testing exception propagation."); } } wts.$v4 = 10; wts.$v8 = 20; wts.$xFunc = W.installFunction(wtsFunc, wts.memberSignature('xFunc')) T.assert(0===counter).assert(10 === wts.$v4).assert(20n === wts.$v8) .assert(0 === wts.$ppV).assert('number' === typeof wts.$xFunc) .assert(0 === wts.$cstr) .assert(wts.memberIsString('$cstr')) .assert(!wts.memberIsString('$v4')) .assert(null === wts.memberToJsString('$cstr')) .assert(W.functionEntry(wts.$xFunc) instanceof Function); /* It might seem silly to assert that the values match what we just set, but recall that all of those property reads and writes are, via property interceptors, actually marshaling their data to/from a raw memory buffer, so merely reading them back is actually part of testing the struct-wrapping API. */ testFunc(wts.pointer); //log("wts.pointer, wts.$ppV",wts.pointer, wts.$ppV); T.assert(1===counter).assert(20 === wts.$v4).assert(40n === wts.$v8) .assert(wts.$ppV === wts.pointer) .assert('string' === typeof wts.memberToJsString('cstr')) .assert(wts.memberToJsString('cstr') === wts.memberToJsString('$cstr')) .mustThrowMatching(()=>wts.memberToJsString('xFunc'), /Invalid member type signature for C-string/) ; testFunc(wts.pointer); T.assert(2===counter).assert(40 === wts.$v4).assert(80n === wts.$v8) .assert(wts.$ppV === wts.pointer); /** The 3rd call to wtsFunc throw from JS, which is called from C, which is called from JS. Let's ensure that that exception propagates back here... */ T.mustThrowMatching(()=>testFunc(wts.pointer),/^Testing/); W.uninstallFunction(wts.$xFunc); wts.$xFunc = 0; wts.$ppV = 0; T.assert(!wts.$ppV); //WTStruct.debugFlags(0x03); wts.$ppV = wts; T.assert(wts.pointer === wts.$ppV) wts.setMemberCString('cstr', "A C-string."); T.assert(Array.isArray(wts.ondispose)). assert(wts.ondispose[0] === wts.$cstr). assert('A C-string.' === wts.memberToJsString('cstr')); const ptr = wts.pointer; wts.dispose(); T.assert(ptr).assert(undefined === wts.pointer); }finally{ wts.dispose(); } if(1){ // ondispose of other struct instances const s1 = new WTStruct, s2 = new WTStruct, s3 = new WTStruct; T.assert(s1.lookupMember instanceof Function) .assert(s1.addOnDispose instanceof Function); s1.addOnDispose(s2,"testing variadic args"); T.assert(2===s1.ondispose.length); s2.addOnDispose(s3); s1.dispose(); T.assert(!s2.pointer,"Expecting s2 to be ondispose'd by s1."); T.assert(!s3.pointer,"Expecting s3 to be ondispose'd by s2."); } }/*StructBinder*/) //////////////////////////////////////////////////////////////////// .t('sqlite3.wasm.pstack', function(sqlite3){ const P = wasm.pstack; const isAllocErr = (e)=>e instanceof sqlite3.WasmAllocError; const stack = P.pointer; T.assert(0===stack % 8 /* must be 8-byte aligned */); try{ const remaining = P.remaining; T.assert(P.quota >= 4096) .assert(remaining === P.quota) .mustThrowMatching(()=>P.alloc(0), isAllocErr) .mustThrowMatching(()=>P.alloc(-1), isAllocErr) .mustThrowMatching( ()=>P.alloc('i33'), (e)=>e instanceof sqlite3.WasmAllocError ); ; let p1 = P.alloc(12); T.assert(p1 === stack - 16/*8-byte aligned*/) .assert(P.pointer === p1); let p2 = P.alloc(7); T.assert(p2 === p1-8/*8-byte aligned, stack grows downwards*/) .mustThrowMatching(()=>P.alloc(remaining), isAllocErr) .assert(24 === stack - p2) .assert(P.pointer === p2); let n = remaining - (stack - p2); let p3 = P.alloc(n); T.assert(p3 === stack-remaining) .mustThrowMatching(()=>P.alloc(1), isAllocErr); }finally{ P.restore(stack); } T.assert(P.pointer === stack); try { const [p1, p2, p3] = P.allocChunks(3,'i32'); T.assert(P.pointer === stack-16/*always rounded to multiple of 8*/) .assert(p2 === p1 + 4) .assert(p3 === p2 + 4); T.mustThrowMatching(()=>P.allocChunks(1024, 1024 * 16), (e)=>e instanceof sqlite3.WasmAllocError) }finally{ P.restore(stack); } T.assert(P.pointer === stack); try { let [p1, p2, p3] = P.allocPtr(3,false); let sPos = stack-16/*always rounded to multiple of 8*/; T.assert(P.pointer === sPos) .assert(p2 === p1 + 4) .assert(p3 === p2 + 4); [p1, p2, p3] = P.allocPtr(3); T.assert(P.pointer === sPos-24/*3 x 8 bytes*/) .assert(p2 === p1 + 8) .assert(p3 === p2 + 8); p1 = P.allocPtr(); T.assert('number'===typeof p1); }finally{ P.restore(stack); } }/*pstack tests*/) //////////////////////////////////////////////////////////////////// ;/*end of C/WASM utils checks*/ T.g('sqlite3_randomness()') .t('To memory buffer', function(sqlite3){ const stack = wasm.pstack.pointer; try{ const n = 520; const p = wasm.pstack.alloc(n); T.assert(0===wasm.peek8(p)) .assert(0===wasm.peek8(p+n-1)); T.assert(undefined === capi.sqlite3_randomness(n - 10, p)); let j, check = 0; const heap = wasm.heap8u(); for(j = 0; j < 10 && 0===check; ++j){ check += heap[p + j]; } T.assert(check > 0); check = 0; // Ensure that the trailing bytes were not modified... for(j = n - 10; j < n && 0===check; ++j){ check += heap[p + j]; } T.assert(0===check); }finally{ wasm.pstack.restore(stack); } }) .t('To byte array', function(sqlite3){ const ta = new Uint8Array(117); let i, n = 0; for(i=0; i<ta.byteLength && 0===n; ++i){ n += ta[i]; } T.assert(0===n) .assert(ta === capi.sqlite3_randomness(ta)); for(i=ta.byteLength-10; i<ta.byteLength && 0===n; ++i){ n += ta[i]; } T.assert(n>0); const t0 = new Uint8Array(0); T.assert(t0 === capi.sqlite3_randomness(t0), "0-length array is a special case"); }) ;/*end sqlite3_randomness() checks*/ //////////////////////////////////////////////////////////////////////// T.g('sqlite3.oo1') .t('Create db', function(sqlite3){ const dbFile = '/tester1.db'; wasm.sqlite3_wasm_vfs_unlink(0, dbFile); const db = this.db = new sqlite3.oo1.DB(dbFile, 0 ? 'ct' : 'c'); db.onclose = { disposeAfter: [], disposeBefore: [ (db)=>{ //console.debug("db.onclose.before dropping modules"); //sqlite3.capi.sqlite3_drop_modules(db.pointer, 0); } ], before: function(db){ while(this.disposeBefore.length){ const v = this.disposeBefore.shift(); console.debug("db.onclose.before cleaning up:",v); if(wasm.isPtr(v)) wasm.dealloc(v); else if(v instanceof sqlite3.StructBinder.StructType){ v.dispose(); }else if(v instanceof Function){ try{ v(db) } catch(e){ console.warn("beforeDispose() callback threw:",e); } } } }, after: function(){ while(this.disposeAfter.length){ const v = this.disposeAfter.shift(); console.debug("db.onclose.after cleaning up:",v); if(wasm.isPtr(v)) wasm.dealloc(v); else if(v instanceof sqlite3.StructBinder.StructType){ v.dispose(); }else if(v instanceof Function){ try{v()} catch(e){/*ignored*/} } } } }; T.assert(wasm.isPtr(db.pointer)) .mustThrowMatching(()=>db.pointer=1, /read-only/) .assert(0===sqlite3.capi.sqlite3_extended_result_codes(db.pointer,1)) .assert('main'===db.dbName(0)) .assert('string' === typeof db.dbVfsName()) .assert(db.pointer === wasm.xWrap.testConvertArg('sqlite3*',db)); // Custom db error message handling via sqlite3_prepare_v2/v3() let rc = capi.sqlite3_prepare_v3(db.pointer, {/*invalid*/}, -1, 0, null, null); T.assert(capi.SQLITE_MISUSE === rc) .assert(0 === capi.sqlite3_errmsg(db.pointer).indexOf("Invalid SQL")) .assert(dbFile === db.dbFilename()) .assert(!db.dbFilename('nope')); //Sanity check DB.checkRc()... let ex; try{db.checkRc(rc)} catch(e){ex = e} T.assert(ex instanceof sqlite3.SQLite3Error) .assert(0===ex.message.indexOf("sqlite3 result code")) .assert(ex.message.indexOf("Invalid SQL")>0); T.assert(db === db.checkRc(0)) .assert(db === sqlite3.oo1.DB.checkRc(db,0)) .assert(null === sqlite3.oo1.DB.checkRc(null,0)); this.progressHandlerCount = 0; capi.sqlite3_progress_handler(db, 5, (p)=>{ ++this.progressHandlerCount; return 0; }, 0); }) //////////////////////////////////////////////////////////////////// .t('sqlite3_db_config() and sqlite3_db_status()', function(sqlite3){ let rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE, 0, 0); T.assert(0===rc); rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_MAX+1, 0); T.assert(capi.SQLITE_MISUSE === rc); rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_MAINDBNAME, "main"); T.assert(0 === rc); const stack = wasm.pstack.pointer; try { const [pCur, pHi] = wasm.pstack.allocChunks(2,'i64'); rc = capi.sqlite3_db_status(this.db, capi.SQLITE_DBSTATUS_LOOKASIDE_USED, pCur, pHi, 0); T.assert(0===rc); if(wasm.peek32(pCur)){ warn("Cannot test db_config(SQLITE_DBCONFIG_LOOKASIDE)", "while lookaside memory is in use."); }else{ rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_LOOKASIDE, 0, 4096, 12); T.assert(0 === rc); } wasm.poke32([pCur, pHi], 0); let [vCur, vHi] = wasm.peek32(pCur, pHi); T.assert(0===vCur).assert(0===vHi); rc = capi.sqlite3_status(capi.SQLITE_STATUS_MEMORY_USED, pCur, pHi, 0); [vCur, vHi] = wasm.peek32(pCur, pHi); //console.warn("i32 vCur,vHi",vCur,vHi); T.assert(0 === rc).assert(vCur > 0).assert(vHi >= vCur); if(wasm.bigIntEnabled){ // Again in 64-bit. Recall that pCur and pHi are allocated // large enough to account for this re-use. wasm.poke64([pCur, pHi], 0); rc = capi.sqlite3_status64(capi.SQLITE_STATUS_MEMORY_USED, pCur, pHi, 0); [vCur, vHi] = wasm.peek64([pCur, pHi]); //console.warn("i64 vCur,vHi",vCur,vHi); T.assert(0 === rc).assert(vCur > 0).assert(vHi >= vCur); } }finally{ wasm.pstack.restore(stack); } }) //////////////////////////////////////////////////////////////////// .t('DB.Stmt', function(sqlite3){ let st = this.db.prepare( new TextEncoder('utf-8').encode("select 3 as a") ); //debug("statement =",st); this.progressHandlerCount = 0; try { T.assert(wasm.isPtr(st.pointer)) .mustThrowMatching(()=>st.pointer=1, /read-only/) .assert(1===this.db.openStatementCount()) .assert( capi.sqlite3_stmt_status( st, capi.SQLITE_STMTSTATUS_RUN, 0 ) === 0) .assert(!st._mayGet) .assert('a' === st.getColumnName(0)) .assert(1===st.columnCount) .assert(0===st.parameterCount) .mustThrow(()=>st.bind(1,null)) .assert(true===st.step()) .assert(3 === st.get(0)) .mustThrow(()=>st.get(1)) .mustThrow(()=>st.get(0,~capi.SQLITE_INTEGER)) .assert(3 === st.get(0,capi.SQLITE_INTEGER)) .assert(3 === st.getInt(0)) .assert('3' === st.get(0,capi.SQLITE_TEXT)) .assert('3' === st.getString(0)) .assert(3.0 === st.get(0,capi.SQLITE_FLOAT)) .assert(3.0 === st.getFloat(0)) .assert(3 === st.get({}).a) .assert(3 === st.get([])[0]) .assert(3 === st.getJSON(0)) .assert(st.get(0,capi.SQLITE_BLOB) instanceof Uint8Array) .assert(1===st.get(0,capi.SQLITE_BLOB).length) .assert(st.getBlob(0) instanceof Uint8Array) .assert('3'.charCodeAt(0) === st.getBlob(0)[0]) .assert(st._mayGet) .assert(false===st.step()) .assert(!st._mayGet) .assert( capi.sqlite3_stmt_status( st, capi.SQLITE_STMTSTATUS_RUN, 0 ) > 0); T.assert(this.progressHandlerCount > 0, "Expecting progress callback."). assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")). assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")). assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)). assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0)); }finally{ st.finalize(); } T.assert(!st.pointer) .assert(0===this.db.openStatementCount()); T.mustThrowMatching(()=>new sqlite3.oo1.Stmt("hi"), function(err){ return (err instanceof sqlite3.SQLite3Error) && capi.SQLITE_MISUSE === err.resultCode && 0 < err.message.indexOf("Do not call the Stmt constructor directly.") }); }) //////////////////////////////////////////////////////////////////////// .t('sqlite3_js_...()', function(){ const db = this.db; if(1){ const vfsList = capi.sqlite3_js_vfs_list(); T.assert(vfsList.length>1); //log("vfsList =",vfsList); wasm.scopedAllocCall(()=>{ const vfsArg = (v)=>wasm.xWrap.testConvertArg('sqlite3_vfs*',v); for(const v of vfsList){ T.assert('string' === typeof v); const pVfs = capi.sqlite3_vfs_find(v); T.assert(wasm.isPtr(pVfs)) .assert(pVfs===vfsArg(v)); const vfs = new capi.sqlite3_vfs(pVfs); try { T.assert(vfsArg(vfs)===pVfs) } finally{ vfs.dispose() } } }); } /** Trivia: the magic db name ":memory:" does not actually use the "memdb" VFS unless "memdb" is _explicitly_ provided as the VFS name. Instead, it uses the default VFS with an in-memory btree. Thus this.db's VFS may not be memdb even though it's an in-memory db. */ const pVfsMem = capi.sqlite3_vfs_find('memdb'), pVfsDflt = capi.sqlite3_vfs_find(0), pVfsDb = capi.sqlite3_js_db_vfs(db.pointer); T.assert(pVfsMem > 0) .assert(pVfsDflt > 0) .assert(pVfsDb > 0) .assert(pVfsMem !== pVfsDflt /* memdb lives on top of the default vfs */) .assert(pVfsDb === pVfsDflt || pVfsdb === pVfsMem) ; /*const vMem = new capi.sqlite3_vfs(pVfsMem), vDflt = new capi.sqlite3_vfs(pVfsDflt), vDb = new capi.sqlite3_vfs(pVfsDb);*/ const duv = capi.sqlite3_js_db_uses_vfs; T.assert(pVfsDflt === duv(db.pointer, 0) || pVfsMem === duv(db.pointer,0)) .assert(!duv(db.pointer, "foo")) ; }/*sqlite3_js_...()*/) //////////////////////////////////////////////////////////////////// .t('Table t', function(sqlite3){ const db = this.db; let list = []; this.progressHandlerCount = 0; let rc = db.exec({ sql:['CREATE TABLE t(a,b);', // ^^^ using TEMP TABLE breaks the db export test "INSERT INTO t(a,b) VALUES(1,2),(3,4),", "(?,?),('blob',X'6869')"/*intentionally missing semicolon to test for off-by-one bug in string-to-WASM conversion*/], saveSql: list, bind: [5,6] }); //debug("Exec'd SQL:", list); T.assert(rc === db) .assert(2 === list.length) .assert('string'===typeof list[1]) .assert(4===db.changes()) .assert(this.progressHandlerCount > 0, "Expecting progress callback.") if(wasm.bigIntEnabled){ T.assert(4n===db.changes(false,true)); } let vals = db.selectValues('select a from t order by a limit 2'); T.assert( 2 === vals.length ) .assert( 1===vals[0] && 3===vals[1] ); vals = db.selectValues('select a from t order by a limit $L', {$L:2}, capi.SQLITE_TEXT); T.assert( 2 === vals.length ) .assert( '1'===vals[0] && '3'===vals[1] ); vals = undefined; let blob = db.selectValue("select b from t where a='blob'"); T.assert(blob instanceof Uint8Array). assert(0x68===blob[0] && 0x69===blob[1]); blob = null; let counter = 0, colNames = []; list.length = 0; db.exec(new TextEncoder('utf-8').encode("SELECT a a, b b FROM t"),{ rowMode: 'object', resultRows: list, columnNames: colNames, _myState: 3 /* Accessible from the callback */, callback: function(row,stmt){ ++counter; T.assert( 3 === this._myState /* Recall that "this" is the options object. */ ).assert( this.columnNames[0]==='a' && this.columnNames[1]==='b' /* options.columnNames is filled out before the first Stmt.step(). */ ).assert( (row.a%2 && row.a<6) || 'blob'===row.a ); } }); T.assert(2 === colNames.length) .assert('a' === colNames[0]) .assert(4 === counter) .assert(4 === list.length); list.length = 0; db.exec("SELECT a a, b b FROM t",{ rowMode: 'array', callback: function(row,stmt){ ++counter; T.assert(Array.isArray(row)) .assert((0===row[1]%2 && row[1]<7) || (row[1] instanceof Uint8Array)); } }); T.assert(8 === counter); T.assert(Number.MIN_SAFE_INTEGER === db.selectValue("SELECT "+Number.MIN_SAFE_INTEGER)). assert(Number.MAX_SAFE_INTEGER === db.selectValue("SELECT "+Number.MAX_SAFE_INTEGER)); counter = 0; db.exec({ sql: "SELECT a FROM t", callback: ()=>(1===++counter), }); T.assert(2===counter, "Expecting exec step() loop to stop if callback returns false."); if(wasm.bigIntEnabled && haveWasmCTests()){ const mI = wasm.xCall('sqlite3_wasm_test_int64_max'); const b = BigInt(Number.MAX_SAFE_INTEGER * 2); T.assert(b === db.selectValue("SELECT "+b)). assert(b === db.selectValue("SELECT ?", b)). assert(mI == db.selectValue("SELECT $x", {$x:mI})); }else{ /* Curiously, the JS spec seems to be off by one with the definitions of MIN/MAX_SAFE_INTEGER: https://github.com/emscripten-core/emscripten/issues/17391 */ T.mustThrow(()=>db.selectValue("SELECT "+(Number.MAX_SAFE_INTEGER+1))). mustThrow(()=>db.selectValue("SELECT "+(Number.MIN_SAFE_INTEGER-1))); } let st = db.prepare("update t set b=:b where a='blob'"); try { const ndx = st.getParamIndex(':b'); T.assert(1===ndx); st.bindAsBlob(ndx, "ima blob").reset(true); } finally { st.finalize(); } try { db.prepare("/*empty SQL*/"); toss("Must not be reached."); }catch(e){ T.assert(e instanceof sqlite3.SQLite3Error) .assert(0==e.message.indexOf('Cannot prepare empty')); } })/*setup table T*/ //////////////////////////////////////////////////////////////////// .t({ name: "sqlite3_set_authorizer()", test:function(sqlite3){ T.assert(capi.SQLITE_IGNORE>0) .assert(capi.SQLITE_DENY>0); const db = this.db; const ssa = capi.sqlite3_set_authorizer; const n = db.selectValue('select count(*) from t'); T.assert(n>0); let authCount = 0; let rc = ssa(db, function(pV, iCode, s0, s1, s2, s3){ ++authCount; return capi.SQLITE_IGNORE; }, 0); T.assert(0===rc) .assert( undefined === db.selectValue('select count(*) from t') /* Note that the count() never runs, so we get undefined instead of 0. */ ) .assert(authCount>0); authCount = 0; db.exec("update t set a=-9999"); T.assert(authCount>0); /* Reminder: we don't use DELETE because, from the C API docs: "If the action code is [SQLITE_DELETE] and the callback returns [SQLITE_IGNORE] then the [DELETE] operation proceeds but the [truncate optimization] is disabled and all rows are deleted individually." */ rc = ssa(db, null, 0); authCount = 0; T.assert(-9999 != db.selectValue('select a from t')) .assert(0===authCount); rc = ssa(db, function(pV, iCode, s0, s1, s2, s3){ ++authCount; return capi.SQLITE_DENY; }, 0); T.assert(0===rc); let err; try{ db.exec("select 1 from t") } catch(e){ err = e } T.assert(err instanceof sqlite3.SQLite3Error) .assert(err.message.indexOf('not authorized'>0)) .assert(1===authCount); authCount = 0; rc = ssa(db, function(...args){ ++authCount; return capi.SQLITE_OK; }, 0); T.assert(0===rc); T.assert(n === db.selectValue('select count(*) from t')) .assert(authCount>0); authCount = 0; rc = ssa(db, function(pV, iCode, s0, s1, s2, s3){ ++authCount; throw new Error("Testing catching of authorizer."); }, 0); T.assert(0===rc); authCount = 0; err = undefined; try{ db.exec("select 1 from t") } catch(e){err = e} T.assert(err instanceof Error) .assert(err.message.indexOf('not authorized')>0) /* Note that the thrown message is trumped/overwritten by the authorizer process. */ .assert(1===authCount); rc = ssa(db, 0, 0); authCount = 0; T.assert(0===rc); T.assert(n === db.selectValue('select count(*) from t')) .assert(0===authCount); } })/*sqlite3_set_authorizer()*/ //////////////////////////////////////////////////////////////////////// .t("sqlite3_table_column_metadata()", function(sqlite3){ const stack = wasm.pstack.pointer; try{ const [pzDT, pzColl, pNotNull, pPK, pAuto] = wasm.pstack.allocPtr(5); const rc = capi.sqlite3_table_column_metadata( this.db, "main", "t", "rowid", pzDT, pzColl, pNotNull, pPK, pAuto ); T.assert(0===rc) .assert("INTEGER"===wasm.cstrToJs(wasm.peekPtr(pzDT))) .assert("BINARY"===wasm.cstrToJs(wasm.peekPtr(pzColl))) .assert(0===wasm.peek32(pNotNull)) .assert(1===wasm.peek32(pPK)) .assert(0===wasm.peek32(pAuto)) }finally{ wasm.pstack.restore(stack); } }) //////////////////////////////////////////////////////////////////////// .t('selectArray/Object()', function(sqlite3){ const db = this.db; let rc = db.selectArray('select a, b from t where a=?', 5); T.assert(Array.isArray(rc)) .assert(2===rc.length) .assert(5===rc[0] && 6===rc[1]); rc = db.selectArray('select a, b from t where b=-1'); T.assert(undefined === rc); rc = db.selectObject('select a A, b b from t where b=?', 6); T.assert(rc && 'object'===typeof rc) .assert(5===rc.A) .assert(6===rc.b); rc = db.selectArray('select a, b from t where b=-1'); T.assert(undefined === rc); }) //////////////////////////////////////////////////////////////////////// .t('selectArrays/Objects()', function(sqlite3){ const db = this.db; const sql = 'select a, b from t where a=? or b=? order by a'; let rc = db.selectArrays(sql, [1, 4]); T.assert(Array.isArray(rc)) .assert(2===rc.length) .assert(2===rc[0].length) .assert(1===rc[0][0]) .assert(2===rc[0][1]) .assert(3===rc[1][0]) .assert(4===rc[1][1]) rc = db.selectArrays(sql, [99,99]); T.assert(Array.isArray(rc)).assert(0===rc.length); rc = db.selectObjects(sql, [1,4]); T.assert(Array.isArray(rc)) .assert(2===rc.length) .assert('object' === typeof rc[1]) .assert(1===rc[0].a) .assert(2===rc[0].b) .assert(3===rc[1].a) .assert(4===rc[1].b); }) //////////////////////////////////////////////////////////////////////// .t({ name: 'sqlite3_js_db_export()', predicate: ()=>true, test: function(sqlite3){ const db = this.db; const xp = capi.sqlite3_js_db_export(db.pointer); T.assert(xp instanceof Uint8Array) .assert(xp.byteLength>0) .assert(0 === xp.byteLength % 512); this.dbExport = xp; } }/*sqlite3_js_db_export()*/) .t({ name: 'sqlite3_js_vfs_create_file() with db in default VFS', predicate: ()=>true, test: function(sqlite3){ const db = this.db; const pVfs = capi.sqlite3_js_db_vfs(db); const filename = "sqlite3_js_vfs_create_file().db"; capi.sqlite3_js_vfs_create_file(pVfs, filename, this.dbExport); delete this.dbExport; const db2 = new sqlite3.oo1.DB(filename,'r'); try { const sql = "select count(*) from t"; const n = db.selectValue(sql); T.assert(n>0 && db2.selectValue(sql) === n); }finally{ db2.close(); wasm.sqlite3_wasm_vfs_unlink(pVfs, filename); } } }/*sqlite3_js_vfs_create_file()*/) //////////////////////////////////////////////////////////////////// .t({ name:'Scalar UDFs', test: function(sqlite3){ const db = this.db; db.createFunction("foo",(pCx,a,b)=>a+b); T.assert(7===db.selectValue("select foo(3,4)")). assert(5===db.selectValue("select foo(3,?)",2)). assert(5===db.selectValue("select foo(?,?2)",[1,4])). assert(5===db.selectValue("select foo($a,$b)",{$a:0,$b:5})); db.createFunction("bar", { arity: -1, xFunc: (pCx,...args)=>{ T.assert(db.pointer === capi.sqlite3_context_db_handle(pCx)); let rc = 0; for(const v of args) rc += v; return rc; } }).createFunction({ name: "asis", xFunc: (pCx,arg)=>arg }); T.assert(0===db.selectValue("select bar()")). assert(1===db.selectValue("select bar(1)")). assert(3===db.selectValue("select bar(1,2)")). assert(-1===db.selectValue("select bar(1,2,-4)")). assert('hi' === db.selectValue("select asis('hi')")). assert('hi' === db.selectValue("select ?",'hi')). assert(null === db.selectValue("select null")). assert(null === db.selectValue("select asis(null)")). assert(1 === db.selectValue("select ?",1)). assert(2 === db.selectValue("select ?",[2])). assert(3 === db.selectValue("select $a",{$a:3})). assert(T.eqApprox(3.1,db.selectValue("select 3.0 + 0.1"))). assert(T.eqApprox(1.3,db.selectValue("select asis(1 + 0.3)"))); let blobArg = new Uint8Array([0x68, 0x69]); let blobRc = db.selectValue( "select asis(?1)", blobArg.buffer/*confirm that ArrayBuffer is handled as a Uint8Array*/ ); T.assert(blobRc instanceof Uint8Array). assert(2 === blobRc.length). assert(0x68==blobRc[0] && 0x69==blobRc[1]); blobRc = db.selectValue("select asis(X'6869')"); T.assert(blobRc instanceof Uint8Array). assert(2 === blobRc.length). assert(0x68==blobRc[0] && 0x69==blobRc[1]); blobArg = new Int8Array([0x68, 0x69]); //debug("blobArg=",blobArg); blobRc = db.selectValue("select asis(?1)", blobArg); T.assert(blobRc instanceof Uint8Array). assert(2 === blobRc.length); //debug("blobRc=",blobRc); T.assert(0x68==blobRc[0] && 0x69==blobRc[1]); let rc = sqlite3.capi.sqlite3_create_function_v2( this.db, "foo", 0, -1, 0, 0, 0, 0, 0 ); T.assert( sqlite3.capi.SQLITE_FORMAT === rc, "For invalid eTextRep argument." ); rc = sqlite3.capi.sqlite3_create_function_v2(this.db, "foo", 0); T.assert( sqlite3.capi.SQLITE_MISUSE === rc, "For invalid arg count." ); /* Confirm that we can map and unmap the same function with multiple arities... */ const fCounts = [0,0]; const fArityCheck = function(pCx){ return ++fCounts[arguments.length-1]; }; //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true; rc = capi.sqlite3_create_function_v2( db, "nary", 0, capi.SQLITE_UTF8, 0, fArityCheck, 0, 0, 0 ); T.assert( 0===rc ); rc = capi.sqlite3_create_function_v2( db, "nary", 1, capi.SQLITE_UTF8, 0, fArityCheck, 0, 0, 0 ); T.assert( 0===rc ); const sqlFArity0 = "select nary()"; const sqlFArity1 = "select nary(1)"; T.assert( 1 === db.selectValue(sqlFArity0) ) .assert( 1 === fCounts[0] ).assert( 0 === fCounts[1] ); T.assert( 1 === db.selectValue(sqlFArity1) ) .assert( 1 === fCounts[0] ).assert( 1 === fCounts[1] ); capi.sqlite3_create_function_v2( db, "nary", 0, capi.SQLITE_UTF8, 0, 0, 0, 0, 0 ); T.mustThrowMatching((()=>db.selectValue(sqlFArity0)), (e)=>((e instanceof sqlite3.SQLite3Error) && e.message.indexOf("wrong number of arguments")>0), "0-arity variant was uninstalled."); T.assert( 2 === db.selectValue(sqlFArity1) ) .assert( 1 === fCounts[0] ).assert( 2 === fCounts[1] ); capi.sqlite3_create_function_v2( db, "nary", 1, capi.SQLITE_UTF8, 0, 0, 0, 0, 0 ); T.mustThrowMatching((()=>db.selectValue(sqlFArity1)), (e)=>((e instanceof sqlite3.SQLite3Error) && e.message.indexOf("no such function")>0), "1-arity variant was uninstalled."); //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false; } }) //////////////////////////////////////////////////////////////////// .t({ name: 'Aggregate UDFs', //predicate: ()=>false, test: function(sqlite3){ const db = this.db; const sjac = capi.sqlite3_js_aggregate_context; db.createFunction({ name: 'summer', xStep: (pCtx, n)=>{ const ac = sjac(pCtx, 4); wasm.poke32(ac, wasm.peek32(ac) + Number(n)); }, xFinal: (pCtx)=>{ const ac = sjac(pCtx, 0); return ac ? wasm.peek32(ac) : 0; } }); let v = db.selectValue([ "with cte(v) as (", "select 3 union all select 5 union all select 7", ") select summer(v), summer(v+1) from cte" /* ------------------^^^^^^^^^^^ ensures that we're handling sqlite3_aggregate_context() properly. */ ]); T.assert(15===v); T.mustThrowMatching(()=>db.selectValue("select summer(1,2)"), /wrong number of arguments/); db.createFunction({ name: 'summerN', arity: -1, xStep: (pCtx, ...args)=>{ const ac = sjac(pCtx, 4); let sum = wasm.peek32(ac); for(const v of args) sum += Number(v); wasm.poke32(ac, sum); }, xFinal: (pCtx)=>{ const ac = sjac(pCtx, 0); capi.sqlite3_result_int( pCtx, ac ? wasm.peek32(ac) : 0 ); // xFinal() may either return its value directly or call // sqlite3_result_xyz() and return undefined. Both are // functionally equivalent. } }); T.assert(18===db.selectValue('select summerN(1,8,9), summerN(2,3,4)')); T.mustThrowMatching(()=>{ db.createFunction('nope',{ xFunc: ()=>{}, xStep: ()=>{} }); }, /scalar or aggregate\?/); T.mustThrowMatching(()=>{ db.createFunction('nope',{xStep: ()=>{}}); }, /Missing xFinal/); T.mustThrowMatching(()=>{ db.createFunction('nope',{xFinal: ()=>{}}); }, /Missing xStep/); T.mustThrowMatching(()=>{ db.createFunction('nope',{}); }, /Missing function-type properties/); T.mustThrowMatching(()=>{ db.createFunction('nope',{xFunc:()=>{}, xDestroy:'nope'}); }, /xDestroy property must be a function/); T.mustThrowMatching(()=>{ db.createFunction('nope',{xFunc:()=>{}, pApp:'nope'}); }, /Invalid value for pApp/); } }/*aggregate UDFs*/) //////////////////////////////////////////////////////////////////////// .t({ name: 'Aggregate UDFs (64-bit)', predicate: ()=>wasm.bigIntEnabled, //predicate: ()=>false, test: function(sqlite3){ const db = this.db; const sjac = capi.sqlite3_js_aggregate_context; db.createFunction({ name: 'summer64', xStep: (pCtx, n)=>{ const ac = sjac(pCtx, 8); wasm.poke64(ac, wasm.peek64(ac) + BigInt(n)); }, xFinal: (pCtx)=>{ const ac = sjac(pCtx, 0); return ac ? wasm.peek64(ac) : 0n; } }); let v = db.selectValue([ "with cte(v) as (", "select 9007199254740991 union all select 1 union all select 2", ") select summer64(v), summer64(v+1) from cte" ]); T.assert(9007199254740994n===v); } }/*aggregate UDFs*/) //////////////////////////////////////////////////////////////////// .t({ name: 'Window UDFs', //predicate: ()=>false, test: function(){ /* Example window function, table, and results taken from: https://sqlite.org/windowfunctions.html#udfwinfunc */ const db = this.db; const sjac = (cx,n=4)=>capi.sqlite3_js_aggregate_context(cx,n); const xValueFinal = (pCtx)=>{ const ac = sjac(pCtx, 0); return ac ? wasm.peek32(ac) : 0; }; const xStepInverse = (pCtx, n)=>{ const ac = sjac(pCtx); wasm.poke32(ac, wasm.peek32(ac) + Number(n)); }; db.createFunction({ name: 'winsumint', xStep: (pCtx, n)=>xStepInverse(pCtx, n), xInverse: (pCtx, n)=>xStepInverse(pCtx, -n), xFinal: xValueFinal, xValue: xValueFinal }); db.exec([ "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES", "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)" ]); let rc = db.exec({ returnValue: 'resultRows', sql:[ "SELECT x, winsumint(y) OVER (", "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING", ") AS sum_y ", "FROM twin ORDER BY x;" ] }); T.assert(Array.isArray(rc)) .assert(5 === rc.length); let count = 0; for(const row of rc){ switch(++count){ case 1: T.assert('a'===row[0] && 9===row[1]); break; case 2: T.assert('b'===row[0] && 12===row[1]); break; case 3: T.assert('c'===row[0] && 16===row[1]); break; case 4: T.assert('d'===row[0] && 12===row[1]); break; case 5: T.assert('e'===row[0] && 9===row[1]); break; default: toss("Too many rows to window function."); } } const resultRows = []; rc = db.exec({ resultRows, returnValue: 'resultRows', sql:[ "SELECT x, winsumint(y) OVER (", "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING", ") AS sum_y ", "FROM twin ORDER BY x;" ] }); T.assert(rc === resultRows) .assert(5 === rc.length); rc = db.exec({ returnValue: 'saveSql', sql: "select 1; select 2; -- empty\n; select 3" }); T.assert(Array.isArray(rc)) .assert(3===rc.length) .assert('select 1;' === rc[0]) .assert('select 2;' === rc[1]) .assert('-- empty\n; select 3' === rc[2] /* Strange but true. */); T.mustThrowMatching(()=>{ db.exec({sql:'', returnValue: 'nope'}); }, /^Invalid returnValue/); db.exec("DROP TABLE twin"); } }/*window UDFs*/) //////////////////////////////////////////////////////////////////// .t("ATTACH", function(){ const db = this.db; const resultRows = []; db.exec({ sql:new TextEncoder('utf-8').encode([ // ^^^ testing string-vs-typedarray handling in exec() "attach 'session' as foo;", "create table foo.bar(a);", "insert into foo.bar(a) values(1),(2),(3);", "select a from foo.bar order by a;" ].join('')), rowMode: 0, resultRows }); T.assert(3===resultRows.length) .assert(2===resultRows[1]); T.assert(2===db.selectValue('select a from foo.bar where a>1 order by a')); /** Demonstrate the JS-simplified form of the sqlite3_exec() callback... */ let colCount = 0, rowCount = 0; let rc = capi.sqlite3_exec( db, "select a, a*2 from foo.bar", function(aVals, aNames){ //console.warn("execCallback(",arguments,")"); colCount = aVals.length; ++rowCount; T.assert(2===aVals.length) .assert(2===aNames.length) .assert(+(aVals[1]) === 2 * +(aVals[0])); }, 0, 0 ); T.assert(0===rc).assert(3===rowCount).assert(2===colCount); rc = capi.sqlite3_exec( db.pointer, "select a from foo.bar", ()=>{ tossQuietly("Testing throwing from exec() callback."); }, 0, 0 ); T.assert(capi.SQLITE_ABORT === rc); /* Demonstrate how to get access to the "full" callback signature, as opposed to the simplified JS-specific one... */ rowCount = colCount = 0; const pCb = wasm.installFunction('i(pipp)', function(pVoid,nCols,aVals,aCols){ /* Tip: wasm.cArgvToJs() can be used to convert aVals and aCols to arrays: const vals = wasm.cArgvToJs(nCols, aVals); */ ++rowCount; colCount = nCols; T.assert(2 === nCols) .assert(wasm.isPtr(pVoid)) .assert(wasm.isPtr(aVals)) .assert(wasm.isPtr(aCols)) .assert(+wasm.cstrToJs(wasm.peekPtr(aVals + wasm.ptrSizeof)) === 2 * +wasm.cstrToJs(wasm.peekPtr(aVals))); return 0; }); try { T.assert(wasm.isPtr(pCb)); rc = capi.sqlite3_exec( db, new TextEncoder('utf-8').encode("select a, a*2 from foo.bar"), pCb, 0, 0 ); T.assert(0===rc) .assert(3===rowCount) .assert(2===colCount); }finally{ wasm.uninstallFunction(pCb); } // Demonstrate that an OOM result does not propagate through sqlite3_exec()... rc = capi.sqlite3_exec( db, ["select a,"," a*2 from foo.bar"], (aVals, aNames)=>{ sqlite3.WasmAllocError.toss("just testing"); }, 0, 0 ); T.assert(capi.SQLITE_ABORT === rc); db.exec("detach foo"); T.mustThrow(()=>db.exec("select * from foo.bar"), "Because foo is no longer attached."); }) //////////////////////////////////////////////////////////////////// .t({ name: 'C-side WASM tests', predicate: ()=>(haveWasmCTests() || "Not compiled in."), test: function(){ const w = wasm, db = this.db; const stack = w.scopedAllocPush(); let ptrInt; const origValue = 512; try{ ptrInt = w.scopedAlloc(4); w.poke32(ptrInt,origValue); const cf = w.xGet('sqlite3_wasm_test_intptr'); const oldPtrInt = ptrInt; T.assert(origValue === w.peek32(ptrInt)); const rc = cf(ptrInt); T.assert(2*origValue === rc). assert(rc === w.peek32(ptrInt)). assert(oldPtrInt === ptrInt); const pi64 = w.scopedAlloc(8)/*ptr to 64-bit integer*/; const o64 = 0x010203040506/*>32-bit integer*/; if(w.bigIntEnabled){ w.poke64(pi64, o64); //log("pi64 =",pi64, "o64 = 0x",o64.toString(16), o64); const v64 = ()=>w.peek64(pi64) T.assert(v64() == o64); //T.assert(o64 === w.peek64(pi64)); const cf64w = w.xGet('sqlite3_wasm_test_int64ptr'); cf64w(pi64); T.assert(v64() == BigInt(2 * o64)); cf64w(pi64); T.assert(v64() == BigInt(4 * o64)); const biTimes2 = w.xGet('sqlite3_wasm_test_int64_times2'); T.assert(BigInt(2 * o64) === biTimes2(BigInt(o64)/*explicit conv. required to avoid TypeError in the call :/ */)); const pMin = w.scopedAlloc(16); const pMax = pMin + 8; const g64 = (p)=>w.peek64(p); w.poke64([pMin, pMax], 0); const minMaxI64 = [ w.xCall('sqlite3_wasm_test_int64_min'), w.xCall('sqlite3_wasm_test_int64_max') ]; T.assert(minMaxI64[0] < BigInt(Number.MIN_SAFE_INTEGER)). assert(minMaxI64[1] > BigInt(Number.MAX_SAFE_INTEGER)); //log("int64_min/max() =",minMaxI64, typeof minMaxI64[0]); w.xCall('sqlite3_wasm_test_int64_minmax', pMin, pMax); T.assert(g64(pMin) === minMaxI64[0], "int64 mismatch"). assert(g64(pMax) === minMaxI64[1], "int64 mismatch"); //log("pMin",g64(pMin), "pMax",g64(pMax)); w.poke64(pMin, minMaxI64[0]); T.assert(g64(pMin) === minMaxI64[0]). assert(minMaxI64[0] === db.selectValue("select ?",g64(pMin))). assert(minMaxI64[1] === db.selectValue("select ?",g64(pMax))); const rxRange = /too big/; T.mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[0] - BigInt(1))}, rxRange). mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[1] + BigInt(1))}, (e)=>rxRange.test(e.message)); }else{ log("No BigInt support. Skipping related tests."); log("\"The problem\" here is that we can manipulate, at the byte level,", "heap memory to set 64-bit values, but we can't get those values", "back into JS because of the lack of 64-bit integer support."); } }finally{ const x = w.scopedAlloc(1), y = w.scopedAlloc(1), z = w.scopedAlloc(1); //log("x=",x,"y=",y,"z=",z); // just looking at the alignment w.scopedAllocPop(stack); } } }/* jaccwabyt-specific tests */) //////////////////////////////////////////////////////////////////////// .t({ name: 'virtual table #1: eponymous w/ manual exception handling', predicate: ()=>!!capi.sqlite3_index_info, test: function(sqlite3){ const VT = sqlite3.vtab; const tmplCols = Object.assign(Object.create(null),{ A: 0, B: 1 }); /** The vtab demonstrated here is a JS-ification of ext/misc/templatevtab.c. */ const tmplMod = new sqlite3.capi.sqlite3_module(); T.assert(0===tmplMod.$xUpdate); tmplMod.setupModule({ catchExceptions: false, methods: { xConnect: function(pDb, pAux, argc, argv, ppVtab, pzErr){ try{ const args = wasm.cArgvToJs(argc, argv); T.assert(args.length>=3) .assert(args[0] === 'testvtab') .assert(args[1] === 'main') .assert(args[2] === 'testvtab'); //console.debug("xConnect() args =",args); const rc = capi.sqlite3_declare_vtab( pDb, "CREATE TABLE ignored(a,b)" ); if(0===rc){ const t = VT.xVtab.create(ppVtab); T.assert(t === VT.xVtab.get(wasm.peekPtr(ppVtab))); } return rc; }catch(e){ if(!(e instanceof sqlite3.WasmAllocError)){ wasm.dealloc(wasm.peekPtr, pzErr); wasm.pokePtr(pzErr, wasm.allocCString(e.message)); } return VT.xError('xConnect',e); } }, xCreate: true /* just for testing. Will be removed afterwards. */, xDisconnect: function(pVtab){ try { VT.xVtab.unget(pVtab).dispose(); return 0; }catch(e){ return VT.xError('xDisconnect',e); } }, xOpen: function(pVtab, ppCursor){ try{ const t = VT.xVtab.get(pVtab), c = VT.xCursor.create(ppCursor); T.assert(t instanceof capi.sqlite3_vtab) .assert(c instanceof capi.sqlite3_vtab_cursor); c._rowId = 0; return 0; }catch(e){ return VT.xError('xOpen',e); } }, xClose: function(pCursor){ try{ const c = VT.xCursor.unget(pCursor); T.assert(c instanceof capi.sqlite3_vtab_cursor) .assert(!VT.xCursor.get(pCursor)); c.dispose(); return 0; }catch(e){ return VT.xError('xClose',e); } }, xNext: function(pCursor){ try{ const c = VT.xCursor.get(pCursor); ++c._rowId; return 0; }catch(e){ return VT.xError('xNext',e); } }, xColumn: function(pCursor, pCtx, iCol){ try{ const c = VT.xCursor.get(pCursor); switch(iCol){ case tmplCols.A: capi.sqlite3_result_int(pCtx, 1000 + c._rowId); break; case tmplCols.B: capi.sqlite3_result_int(pCtx, 2000 + c._rowId); break; default: sqlite3.SQLite3Error.toss("Invalid column id",iCol); } return 0; }catch(e){ return VT.xError('xColumn',e); } }, xRowid: function(pCursor, ppRowid64){ try{ const c = VT.xCursor.get(pCursor); VT.xRowid(ppRowid64, c._rowId); return 0; }catch(e){ return VT.xError('xRowid',e); } }, xEof: function(pCursor){ const c = VT.xCursor.get(pCursor), rc = c._rowId>=10; return rc; }, xFilter: function(pCursor, idxNum, idxCStr, argc, argv/* [sqlite3_value* ...] */){ try{ const c = VT.xCursor.get(pCursor); c._rowId = 0; const list = capi.sqlite3_values_to_js(argc, argv); T.assert(argc === list.length); //log(argc,"xFilter value(s):",list); return 0; }catch(e){ return VT.xError('xFilter',e); } }, xBestIndex: function(pVtab, pIdxInfo){ try{ //const t = VT.xVtab.get(pVtab); const sii = capi.sqlite3_index_info; const pii = new sii(pIdxInfo); pii.$estimatedRows = 10; pii.$estimatedCost = 10.0; //log("xBestIndex $nConstraint =",pii.$nConstraint); if(pii.$nConstraint>0){ // Validate nthConstraint() and nthConstraintUsage() const max = pii.$nConstraint; for(let i=0; i < max; ++i ){ let v = pii.nthConstraint(i,true); T.assert(wasm.isPtr(v)); v = pii.nthConstraint(i); T.assert(v instanceof sii.sqlite3_index_constraint) .assert(v.pointer >= pii.$aConstraint); v.dispose(); v = pii.nthConstraintUsage(i,true); T.assert(wasm.isPtr(v)); v = pii.nthConstraintUsage(i); T.assert(v instanceof sii.sqlite3_index_constraint_usage) .assert(v.pointer >= pii.$aConstraintUsage); v.$argvIndex = i;//just to get some values into xFilter v.dispose(); } } //log("xBestIndex $nOrderBy =",pii.$nOrderBy); if(pii.$nOrderBy>0){ // Validate nthOrderBy() const max = pii.$nOrderBy; for(let i=0; i < max; ++i ){ let v = pii.nthOrderBy(i,true); T.assert(wasm.isPtr(v)); v = pii.nthOrderBy(i); T.assert(v instanceof sii.sqlite3_index_orderby) .assert(v.pointer >= pii.$aOrderBy); v.dispose(); } } pii.dispose(); return 0; }catch(e){ return VT.xError('xBestIndex',e); } } } }); this.db.onclose.disposeAfter.push(tmplMod); T.assert(0===tmplMod.$xUpdate) .assert(tmplMod.$xCreate) .assert(tmplMod.$xCreate === tmplMod.$xConnect, "setup() must make these equivalent and "+ "installMethods() must avoid re-compiling identical functions"); tmplMod.$xCreate = 0 /* make tmplMod eponymous-only */; let rc = capi.sqlite3_create_module( this.db, "testvtab", tmplMod, 0 ); this.db.checkRc(rc); const list = this.db.selectArrays( "SELECT a,b FROM testvtab where a<9999 and b>1 order by a, b" /* Query is shaped so that it will ensure that some constraints end up in xBestIndex(). */ ); T.assert(10===list.length) .assert(1000===list[0][0]) .assert(2009===list[list.length-1][1]); } })/*custom vtab #1*/ //////////////////////////////////////////////////////////////////////// .t({ name: 'virtual table #2: non-eponymous w/ automated exception wrapping', predicate: ()=>!!capi.sqlite3_index_info, test: function(sqlite3){ const VT = sqlite3.vtab; const tmplCols = Object.assign(Object.create(null),{ A: 0, B: 1 }); /** The vtab demonstrated here is a JS-ification of ext/misc/templatevtab.c. */ let throwOnCreate = 1 ? 0 : capi.SQLITE_CANTOPEN /* ^^^ just for testing exception wrapping. Note that sqlite always translates errors from a vtable to a generic SQLITE_ERROR unless it's from xConnect()/xCreate() and that callback sets an error string. */; const vtabTrace = 1 ? ()=>{} : (methodName,...args)=>console.debug('sqlite3_module::'+methodName+'():',...args); const modConfig = { /* catchExceptions changes how the methods are wrapped */ catchExceptions: true, name: "vtab2test", methods:{ xCreate: function(pDb, pAux, argc, argv, ppVtab, pzErr){ vtabTrace("xCreate",...arguments); if(throwOnCreate){ sqlite3.SQLite3Error.toss( throwOnCreate, "Throwing a test exception." ); } const args = wasm.cArgvToJs(argc, argv); vtabTrace("xCreate","argv:",args); T.assert(args.length>=3); const rc = capi.sqlite3_declare_vtab( pDb, "CREATE TABLE ignored(a,b)" ); if(0===rc){ const t = VT.xVtab.create(ppVtab); T.assert(t === VT.xVtab.get(wasm.peekPtr(ppVtab))); vtabTrace("xCreate",...arguments," ppVtab =",t.pointer); } return rc; }, xConnect: true, xDestroy: function(pVtab){ vtabTrace("xDestroy/xDisconnect",pVtab); VT.xVtab.dispose(pVtab); }, xDisconnect: true, xOpen: function(pVtab, ppCursor){ const t = VT.xVtab.get(pVtab), c = VT.xCursor.create(ppCursor); T.assert(t instanceof capi.sqlite3_vtab) .assert(c instanceof capi.sqlite3_vtab_cursor); vtabTrace("xOpen",...arguments," cursor =",c.pointer); c._rowId = 0; }, xClose: function(pCursor){ vtabTrace("xClose",...arguments); const c = VT.xCursor.unget(pCursor); T.assert(c instanceof capi.sqlite3_vtab_cursor) .assert(!VT.xCursor.get(pCursor)); c.dispose(); }, xNext: function(pCursor){ vtabTrace("xNext",...arguments); const c = VT.xCursor.get(pCursor); ++c._rowId; }, xColumn: function(pCursor, pCtx, iCol){ vtabTrace("xColumn",...arguments); const c = VT.xCursor.get(pCursor); switch(iCol){ case tmplCols.A: capi.sqlite3_result_int(pCtx, 1000 + c._rowId); break; case tmplCols.B: capi.sqlite3_result_int(pCtx, 2000 + c._rowId); break; default: sqlite3.SQLite3Error.toss("Invalid column id",iCol); } }, xRowid: function(pCursor, ppRowid64){ vtabTrace("xRowid",...arguments); const c = VT.xCursor.get(pCursor); VT.xRowid(ppRowid64, c._rowId); }, xEof: function(pCursor){ vtabTrace("xEof",...arguments); return VT.xCursor.get(pCursor)._rowId>=10; }, xFilter: function(pCursor, idxNum, idxCStr, argc, argv/* [sqlite3_value* ...] */){ vtabTrace("xFilter",...arguments); const c = VT.xCursor.get(pCursor); c._rowId = 0; const list = capi.sqlite3_values_to_js(argc, argv); T.assert(argc === list.length); }, xBestIndex: function(pVtab, pIdxInfo){ vtabTrace("xBestIndex",...arguments); //const t = VT.xVtab.get(pVtab); const pii = VT.xIndexInfo(pIdxInfo); pii.$estimatedRows = 10; pii.$estimatedCost = 10.0; pii.dispose(); } }/*methods*/ }; const tmplMod = VT.setupModule(modConfig); T.assert(1===tmplMod.$iVersion); this.db.onclose.disposeAfter.push(tmplMod); this.db.checkRc(capi.sqlite3_create_module( this.db.pointer, modConfig.name, tmplMod.pointer, 0 )); this.db.exec([ "create virtual table testvtab2 using ", modConfig.name, "(arg1 blah, arg2 bloop)" ]); if(0){ /* If we DROP TABLE then xDestroy() is called. If the vtab is instead destroyed when the db is closed, xDisconnect() is called. */ this.db.onclose.disposeBefore.push(function(db){ console.debug("Explicitly dropping testvtab2 via disposeBefore handler..."); db.exec( /** DROP TABLE is the only way to get xDestroy() to be called. */ "DROP TABLE testvtab2" ); }); } let list = this.db.selectArrays( "SELECT a,b FROM testvtab2 where a<9999 and b>1 order by a, b" /* Query is shaped so that it will ensure that some constraints end up in xBestIndex(). */ ); T.assert(10===list.length) .assert(1000===list[0][0]) .assert(2009===list[list.length-1][1]); list = this.db.selectArrays( "SELECT a,b FROM testvtab2 where a<9999 and b>1 order by b, a limit 5" ); T.assert(5===list.length) .assert(1000===list[0][0]) .assert(2004===list[list.length-1][1]); // Call it as a table-valued function... list = this.db.selectArrays([ "SELECT a,b FROM ", modConfig.name, " where a<9999 and b>1 order by b, a limit 1" ]); T.assert(1===list.length) .assert(1000===list[0][0]) .assert(2000===list[0][1]); } })/*custom vtab #2*/ //////////////////////////////////////////////////////////////////////// .t('Custom collation', function(sqlite3){ let collationCounter = 0; let myCmp = function(pArg,n1,p1,n2,p2){ //int (*)(void*,int,const void*,int,const void*) ++collationCounter; const rc = wasm.exports.sqlite3_strnicmp(p1,p2,(n1<n2?n1:n2)); return rc ? rc : (n1 - n2); }; let rc = capi.sqlite3_create_collation_v2(this.db, "mycollation", capi.SQLITE_UTF8, 0, myCmp, 0); this.db.checkRc(rc); rc = this.db.selectValue("select 'hi' = 'HI' collate mycollation"); T.assert(1===rc).assert(1===collationCounter); rc = this.db.selectValue("select 'hii' = 'HI' collate mycollation"); T.assert(0===rc).assert(2===collationCounter); rc = this.db.selectValue("select 'hi' = 'HIi' collate mycollation"); T.assert(0===rc).assert(3===collationCounter); rc = capi.sqlite3_create_collation(this.db,"hi",capi.SQLITE_UTF8/*not enough args*/); T.assert(capi.SQLITE_MISUSE === rc); rc = capi.sqlite3_create_collation_v2(this.db,"hi",capi.SQLITE_UTF8+1/*invalid encoding*/,0,0,0); T.assert(capi.SQLITE_FORMAT === rc) .mustThrowMatching(()=>this.db.checkRc(rc), /SQLITE_UTF8 is the only supported encoding./); /* We need to ensure that replacing that collation function does the right thing. We don't have a handle to the underlying WASM pointer from here, so cannot verify (without digging through internal state) that the old one gets uninstalled, but we can verify that a new one properly replaces it. (That said, console.warn() output has shown that the uninstallation does happen.) */ collationCounter = 0; myCmp = function(pArg,n1,p1,n2,p2){ --collationCounter; return 0; }; rc = capi.sqlite3_create_collation_v2(this.db, "MYCOLLATION", capi.SQLITE_UTF8, 0, myCmp, 0); this.db.checkRc(rc); rc = this.db.selectValue("select 'hi' = 'HI' collate mycollation"); T.assert(rc>0).assert(-1===collationCounter); rc = this.db.selectValue("select 'a' = 'b' collate mycollation"); T.assert(rc>0).assert(-2===collationCounter); rc = capi.sqlite3_create_collation_v2(this.db, "MYCOLLATION", capi.SQLITE_UTF8, 0, null, 0); this.db.checkRc(rc); rc = 0; try { this.db.selectValue("select 'a' = 'b' collate mycollation"); }catch(e){ /* Why is e.resultCode not automatically an extended result code? The DB() class enables those automatically. */ rc = sqlite3.capi.sqlite3_extended_errcode(this.db); } T.assert(capi.SQLITE_ERROR_MISSING_COLLSEQ === rc); })/*custom collation*/ //////////////////////////////////////////////////////////////////////// .t('Close db', function(){ T.assert(this.db).assert(wasm.isPtr(this.db.pointer)); //wasm.sqlite3_wasm_db_reset(this.db); // will leak virtual tables! this.db.close(); T.assert(!this.db.pointer); }) ;/* end of oo1 checks */ //////////////////////////////////////////////////////////////////////// T.g('kvvfs') .t({ name: 'kvvfs is disabled in worker', predicate: ()=>(isWorker() || "test is only valid in a Worker"), test: function(sqlite3){ T.assert( !capi.sqlite3_vfs_find('kvvfs'), "Expecting kvvfs to be unregistered." ); } }) .t({ name: 'kvvfs in main thread', predicate: ()=>(isUIThread() || "local/sessionStorage are unavailable in a Worker"), test: function(sqlite3){ const filename = this.kvvfsDbFile = 'session'; const pVfs = capi.sqlite3_vfs_find('kvvfs'); T.assert(pVfs); const JDb = this.JDb = sqlite3.oo1.JsStorageDb; const unlink = this.kvvfsUnlink = ()=>{JDb.clearStorage(filename)}; unlink(); let db = new JDb(filename); try { db.exec([ 'create table kvvfs(a);', 'insert into kvvfs(a) values(1),(2),(3)' ]); T.assert(3 === db.selectValue('select count(*) from kvvfs')); db.close(); db = new JDb(filename); db.exec('insert into kvvfs(a) values(4),(5),(6)'); T.assert(6 === db.selectValue('select count(*) from kvvfs')); }finally{ db.close(); } } }/*kvvfs sanity checks*/) .t({ name: 'kvvfs sqlite3_js_vfs_create_file()', predicate: ()=>"kvvfs does not currently support this", test: function(sqlite3){ let db; try { db = new this.JDb(this.kvvfsDbFile); const exp = capi.sqlite3_js_db_export(db); db.close(); this.kvvfsUnlink(); capi.sqlite3_js_vfs_create_file("kvvfs", this.kvvfsDbFile, exp); db = new this.JDb(filename); T.assert(6 === db.selectValue('select count(*) from kvvfs')); }finally{ db.close(); this.kvvfsUnlink(); } delete this.kvvfsDbFile; delete this.kvvfsUnlink; delete this.JDb; } }/*kvvfs sqlite3_js_vfs_create_file()*/) ;/* end kvvfs tests */ //////////////////////////////////////////////////////////////////////// T.g('OPFS: Origin-Private File System', (sqlite3)=>(sqlite3.opfs ? true : "requires Worker thread in a compatible browser")) .t({ name: 'OPFS db sanity checks', test: async function(sqlite3){ const filename = this.opfsDbFile = 'sqlite3-tester1.db'; const pVfs = this.opfsVfs = capi.sqlite3_vfs_find('opfs'); T.assert(pVfs); const unlink = this.opfsUnlink = (fn=filename)=>{wasm.sqlite3_wasm_vfs_unlink(pVfs,fn)}; unlink(); let db = new sqlite3.oo1.OpfsDb(filename); try { db.exec([ 'create table p(a);', 'insert into p(a) values(1),(2),(3)' ]); T.assert(3 === db.selectValue('select count(*) from p')); db.close(); db = new sqlite3.oo1.OpfsDb(filename); db.exec('insert into p(a) values(4),(5),(6)'); T.assert(6 === db.selectValue('select count(*) from p')); this.opfsDbExport = capi.sqlite3_js_db_export(db); T.assert(this.opfsDbExport instanceof Uint8Array) .assert(this.opfsDbExport.byteLength>0 && 0===this.opfsDbExport.byteLength % 512); }finally{ db.close(); unlink(); } } }/*OPFS db sanity checks*/) .t({ name: 'OPFS export/import', test: async function(sqlite3){ let db; try { const exp = this.opfsDbExport; delete this.opfsDbExport; capi.sqlite3_js_vfs_create_file("opfs", this.opfsDbFile, exp); const db = new sqlite3.oo1.OpfsDb(this.opfsDbFile); T.assert(6 === db.selectValue('select count(*) from p')); }finally{ if(db) db.close(); } } }/*OPFS export/import*/) .t({ name: 'OPFS utility APIs and sqlite3_js_vfs_create_file()', test: async function(sqlite3){ const filename = this.opfsDbFile; const pVfs = this.opfsVfs; const unlink = this.opfsUnlink; T.assert(filename && pVfs && !!unlink); delete this.opfsDbFile; delete this.opfsVfs; delete this.opfsUnlink; unlink(); // Sanity-test sqlite3_js_vfs_create_file()... /************************************************************** ATTENTION CLIENT-SIDE USERS: sqlite3.opfs is NOT intended for client-side use. It is only for this project's own internal use. Its APIs are subject to change or removal at any time. ***************************************************************/ const opfs = sqlite3.opfs; const fSize = 1379; let sh; try{ T.assert(!(await opfs.entryExists(filename))); capi.sqlite3_js_vfs_create_file( pVfs, filename, null, fSize ); T.assert(await opfs.entryExists(filename)); let fh = await opfs.rootDirectory.getFileHandle(filename); sh = await fh.createSyncAccessHandle(); T.assert(fSize === await sh.getSize()); await sh.close(); sh = undefined; unlink(); T.assert(!(await opfs.entryExists(filename))); const ba = new Uint8Array([1,2,3,4,5]); capi.sqlite3_js_vfs_create_file( "opfs", filename, ba ); T.assert(await opfs.entryExists(filename)); fh = await opfs.rootDirectory.getFileHandle(filename); sh = await fh.createSyncAccessHandle(); T.assert(ba.byteLength === await sh.getSize()); await sh.close(); sh = undefined; unlink(); T.mustThrowMatching(()=>{ capi.sqlite3_js_vfs_create_file( "no-such-vfs", filename, ba ); }, "SQLITE_NOTFOUND: Unknown sqlite3_vfs name: no-such-vfs"); }finally{ if(sh) await sh.close(); unlink(); } // Some sanity checks of the opfs utility functions... const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12); const aDir = testDir+'/test/dir'; T.assert(await opfs.mkdir(aDir), "mkdir failed") .assert(await opfs.mkdir(aDir), "mkdir must pass if the dir exists") .assert(!(await opfs.unlink(testDir+'/test')), "delete 1 should have failed (dir not empty)") .assert((await opfs.unlink(testDir+'/test/dir')), "delete 2 failed") .assert(!(await opfs.unlink(testDir+'/test/dir')), "delete 2b should have failed (dir already deleted)") .assert((await opfs.unlink(testDir, true)), "delete 3 failed") .assert(!(await opfs.entryExists(testDir)), "entryExists(",testDir,") should have failed"); } }/*OPFS util sanity checks*/) ;/* end OPFS tests */ //////////////////////////////////////////////////////////////////////// T.g('Session API') .t({ name: 'Session API sanity checks', predicate: ()=>!!capi.sqlite3changegroup_add, test: function(sqlite3){ warn("The session API tests could use some expansion."); const db1 = new sqlite3.oo1.DB(), db2 = new sqlite3.oo1.DB(); const sqlInit = [ "create table t(rowid INTEGER PRIMARY KEY,a,b); ", "insert into t(rowid,a,b) values", "(1,'a1','b1'),", "(2,'a2','b2'),", "(3,'a3','b3');" ].join(''); db1.exec(sqlInit); db2.exec(sqlInit); T.assert(3 === db1.selectValue("select count(*) from t")) .assert('b3' === db1.selectValue('select b from t where rowid=3')); const stackPtr = wasm.pstack.pointer; try{ let ppOut = wasm.pstack.allocPtr(); let rc = capi.sqlite3session_create(db1, "main", ppOut); T.assert(0===rc); let pSession = wasm.peekPtr(ppOut); T.assert(pSession && wasm.isPtr(pSession)); capi.sqlite3session_table_filter(pSession, (pCtx, tbl)=>{ T.assert('t' === tbl).assert( 99 === pCtx ); return 1; }, 99); db1.exec([ "update t set b='bTwo' where rowid=2;", "update t set a='aThree' where rowid=3;", "delete from t where rowid=1;", "insert into t(rowid,a,b) values(4,'a4','b4')" ]); T.assert('bTwo' === db1.selectValue("select b from t where rowid=2")) .assert(undefined === db1.selectValue('select a from t where rowid=1')) .assert('b4' === db1.selectValue('select b from t where rowid=4')) .assert(3 === db1.selectValue('select count(*) from t')); const testSessionEnable = false; if(testSessionEnable){ rc = capi.sqlite3session_enable(pSession, 0); T.assert( 0 === rc ) .assert( 0 === capi.sqlite3session_enable(pSession, -1) ); db1.exec("delete from t where rowid=2;"); rc = capi.sqlite3session_enable(pSession, 1); T.assert( rc > 0 ) .assert( capi.sqlite3session_enable(pSession, -1) > 0 ) .assert(undefined === db1.selectValue('select a from t where rowid=2')); }else{ warn("sqlite3session_enable() tests disabled due to unexpected results.", "(Possibly a tester misunderstanding, as opposed to a bug.)"); } let db1Count = db1.selectValue("select count(*) from t"); T.assert( db1Count === (testSessionEnable ? 2 : 3) ); /* Capture changeset and destroy session. */ let pnChanges = wasm.pstack.alloc('i32'), ppChanges = wasm.pstack.allocPtr(); rc = capi.sqlite3session_changeset(pSession, pnChanges, ppChanges); T.assert( 0 === rc ); capi.sqlite3session_delete(pSession); pSession = 0; const pChanges = wasm.peekPtr(ppChanges), nChanges = wasm.peek32(pnChanges); T.assert( pChanges && wasm.isPtr( pChanges ) ) .assert( nChanges > 0 ); /* Revert db1 via an inverted changeset, but keep pChanges and nChanges for application to db2. */ rc = capi.sqlite3changeset_invert( nChanges, pChanges, pnChanges, ppChanges ); T.assert( 0 === rc ); rc = capi.sqlite3changeset_apply( db1, wasm.peek32(pnChanges), wasm.peekPtr(ppChanges), 0, (pCtx, eConflict, pIter)=>{ return 1; }, 0 ); T.assert( 0 === rc ); wasm.dealloc( wasm.peekPtr(ppChanges) ); pnChanges = ppChanges = 0; T.assert('b2' === db1.selectValue("select b from t where rowid=2")) .assert('a1' === db1.selectValue('select a from t where rowid=1')) .assert(undefined === db1.selectValue('select b from t where rowid=4')); db1Count = db1.selectValue("select count(*) from t"); T.assert(3 === db1Count); /* Apply pre-reverted changeset (pChanges, nChanges) to db2... */ rc = capi.sqlite3changeset_apply( db2, nChanges, pChanges, 0, (pCtx, eConflict, pIter)=>{ return pCtx ? 1 : 0 }, 1 ); wasm.dealloc( pChanges ); T.assert( 0 === rc ) .assert( 'b4' === db2.selectValue('select b from t where rowid=4') ) .assert( 'aThree' === db2.selectValue('select a from t where rowid=3') ) .assert( undefined === db2.selectValue('select b from t where rowid=1') ); if(testSessionEnable){ T.assert( (undefined === db2.selectValue('select b from t where rowid=2')), "But... the session was disabled when rowid=2 was deleted?" ); log("rowids from db2.t:",db2.selectValues('select rowid from t order by rowid')); T.assert( 3 === db2.selectValue('select count(*) from t') ); }else{ T.assert( 'bTwo' === db2.selectValue('select b from t where rowid=2') ) .assert( 3 === db2.selectValue('select count(*) from t') ); } }finally{ wasm.pstack.restore(stackPtr); db1.close(); db2.close(); } } })/*session API sanity tests*/ ;/*end of session API group*/; //////////////////////////////////////////////////////////////////////// log("Loading and initializing sqlite3 WASM module..."); if(!self.sqlite3InitModule && !isUIThread()){ /* Vanilla worker, as opposed to an ES6 module worker */ /* If sqlite3.js is in a directory other than this script, in order to get sqlite3.js to resolve sqlite3.wasm properly, we have to explicitly tell it where sqlite3.js is being loaded from. We do that by passing the `sqlite3.dir=theDirName` URL argument to _this_ script. That URL argument will be seen by the JS/WASM loader and it will adjust the sqlite3.wasm path accordingly. If sqlite3.js/.wasm are in the same directory as this script then that's not needed. URL arguments passed as part of the filename via importScripts() are simply lost, and such scripts see the self.location of _this_ script. */ let sqlite3Js = 'sqlite3.js'; const urlParams = new URL(self.location.href).searchParams; if(urlParams.has('sqlite3.dir')){ sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js; } importScripts(sqlite3Js); } self.sqlite3InitModule.__isUnderTest = true /* disables certain API-internal cleanup so that we can test internal APIs from here */; self.sqlite3InitModule({ print: log, printErr: error }).then(function(sqlite3){ //console.log('sqlite3 =',sqlite3); log("Done initializing WASM/JS bits. Running tests..."); capi = sqlite3.capi; wasm = sqlite3.wasm; log("sqlite3 version:",capi.sqlite3_libversion(), capi.sqlite3_sourceid()); if(wasm.bigIntEnabled){ log("BigInt/int64 support is enabled."); }else{ logClass('warning',"BigInt/int64 support is disabled."); } if(haveWasmCTests()){ log("sqlite3_wasm_test_...() APIs are available."); }else{ logClass('warning',"sqlite3_wasm_test_...() APIs unavailable."); } TestUtil.runTests(sqlite3); }); })(self); |
Deleted ext/wasm/tester1.html.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted ext/wasm/tester1.js.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added ext/wasm/tests/opfs/concurrency/index.html.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | <!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/testing.css"/> <title>sqlite3 OPFS Worker concurrency tester</title> <style> body { display: revert; } body > * {} #test-output { font-family: monospace; } </style> </head> <body> <h1></h1> <p> OPFS concurrency tester using multiple independent Workers. Disclaimer: concurrency in OPFS is currently a pain point! </p> <p> URL flags: pass a number of workers using the <code>workers=N</code> URL flag. Set the time between each workload with <code>interval=N</code> (milliseconds). Set the number of worker iterations with <code>iterations=N</code>. Enable OPFS VFS verbosity with <code>verbose=1-3</code> (output goes to the dev console). Enable/disable "unlock ASAP" mode (higher concurrency, lower speed) with <code>unlock-asap=0-1</code>. </p> <p>Achtung: if it does not start to do anything within a couple of seconds, check the dev console: Chrome sometimes fails to load the wasm module due to "cannot allocate WasmMemory." Closing and re-opening the tab usually resolves it, but sometimes restarting the browser is required. </p> <div class='input-wrapper'> <input type='checkbox' id='cb-log-reverse'> <label for='cb-log-reverse'>Reverse log order?</label> </div> <div id='test-output'></div> <script>(function(){ document.querySelector('h1').innerHTML = document.querySelector('title').innerHTML; })();</script> <script src="test.js?sqlite3.dir=../../../jswasm"></script> </body> </html> |
Added ext/wasm/tests/opfs/concurrency/test.js.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | (async function(self){ const logCss = (function(){ const mapToString = (v)=>{ switch(typeof v){ case 'number': case 'string': 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 logCss = 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 = 'tester1:cb-log-reverse'; const cbReverseIt = ()=>{ logTarget.classList[cbReverse.checked ? 'add' : 'remove']('reverse'); localStorage.setItem(cbReverseKey, cbReverse.checked ? 1 : 0); }; cbReverse.addEventListener('change', cbReverseIt, true); if(localStorage.getItem(cbReverseKey)){ cbReverse.checked = !!(+localStorage.getItem(cbReverseKey)); } cbReverseIt(); return logCss; })(); const stdout = (...args)=>logCss('',...args); const stderr = (...args)=>logCss('error',...args); const wait = async (ms)=>{ return new Promise((resolve)=>setTimeout(resolve,ms)); }; const urlArgsJs = new URL(document.currentScript.src).searchParams; const urlArgsHtml = new URL(self.location.href).searchParams; const options = Object.create(null); options.sqlite3Dir = urlArgsJs.get('sqlite3.dir'); options.workerCount = ( urlArgsHtml.has('workers') ? +urlArgsHtml.get('workers') : 3 ) || 4; options.opfsVerbose = ( urlArgsHtml.has('verbose') ? +urlArgsHtml.get('verbose') : 1 ) || 1; options.interval = ( urlArgsHtml.has('interval') ? +urlArgsHtml.get('interval') : 1000 ) || 1000; options.iterations = ( urlArgsHtml.has('iterations') ? +urlArgsHtml.get('iterations') : 10 ) || 10; options.unlockAsap = ( urlArgsHtml.has('unlock-asap') ? +urlArgsHtml.get('unlock-asap') : 0 ) || 0; options.noUnlink = !!urlArgsHtml.has('no-unlink'); const workers = []; workers.post = (type,...args)=>{ for(const w of workers) w.postMessage({type, payload:args}); }; workers.counts = {loaded: 0, passed: 0, failed: 0}; const checkFinished = function(){ if(workers.counts.passed + workers.counts.failed !== workers.length){ return; } if(workers.counts.failed>0){ logCss('tests-fail',"Finished with",workers.counts.failed,"failure(s)."); }else{ logCss('tests-pass',"All",workers.length,"workers finished."); } }; workers.onmessage = function(msg){ msg = msg.data; const prefix = 'Worker #'+msg.worker+':'; switch(msg.type){ case 'loaded': stdout(prefix,"loaded"); if(++workers.counts.loaded === workers.length){ stdout("All",workers.length,"workers loaded. Telling them to run..."); workers.post('run'); } break; case 'stdout': stdout(prefix,...msg.payload); break; case 'stderr': stderr(prefix,...msg.payload); break; case 'error': stderr(prefix,"ERROR:",...msg.payload); break; case 'finished': ++workers.counts.passed; logCss('tests-pass',prefix,...msg.payload); checkFinished(); break; case 'failed': ++workers.counts.failed; logCss('tests-fail',prefix,"FAILED:",...msg.payload); checkFinished(); break; default: logCss('error',"Unhandled message type:",msg); break; } }; stdout("Launching",options.workerCount,"workers. Options:",options); workers.uri = ( 'worker.js?' + 'sqlite3.dir='+options.sqlite3Dir + '&interval='+options.interval + '&iterations='+options.iterations + '&opfs-verbose='+options.opfsVerbose + '&opfs-unlock-asap='+options.unlockAsap ); for(let i = 0; i < options.workerCount; ++i){ stdout("Launching worker..."); workers.push(new Worker( workers.uri+'&workerId='+(i+1)+( (i || options.noUnlink) ? '' : '&unlink-db' ) )); } // Have to delay onmessage assignment until after the loop // to avoid that early workers get an undue head start. workers.forEach((w)=>w.onmessage = workers.onmessage); })(self); |
Added ext/wasm/tests/opfs/concurrency/worker.js.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | importScripts( (new URL(self.location.href).searchParams).get('sqlite3.dir') + '/sqlite3.js' ); self.sqlite3InitModule().then(async function(sqlite3){ const urlArgs = new URL(self.location.href).searchParams; const options = { workerName: urlArgs.get('workerId') || Math.round(Math.random()*10000), unlockAsap: urlArgs.get('opfs-unlock-asap') || 0 /*EXPERIMENTAL*/ }; const wPost = (type,...payload)=>{ postMessage({type, worker: options.workerName, payload}); }; const stdout = (...args)=>wPost('stdout',...args); const stderr = (...args)=>wPost('stderr',...args); if(!sqlite3.opfs){ stderr("OPFS support not detected. Aborting."); return; } const wait = async (ms)=>{ return new Promise((resolve)=>setTimeout(resolve,ms)); }; const dbName = 'concurrency-tester.db'; if(urlArgs.has('unlink-db')){ await sqlite3.opfs.unlink(dbName); stdout("Unlinked",dbName); } wPost('loaded'); let db; const interval = Object.assign(Object.create(null),{ delay: urlArgs.has('interval') ? (+urlArgs.get('interval') || 750) : 750, handle: undefined, count: 0 }); const finish = ()=>{ if(db){ if(!db.pointer) return; db.close(); } if(interval.error){ wPost('failed',"Ending work after interval #"+interval.count, "due to error:",interval.error); }else{ wPost('finished',"Ending work after",interval.count,"intervals."); } }; const run = async function(){ db = new sqlite3.oo1.OpfsDb({ filename: 'file:'+dbName+'?opfs-unlock-asap='+options.unlockAsap, flags: 'c' }); sqlite3.capi.sqlite3_busy_timeout(db.pointer, 5000); db.transaction((db)=>{ db.exec([ "create table if not exists t1(w TEXT UNIQUE ON CONFLICT REPLACE,v);", "create table if not exists t2(w TEXT UNIQUE ON CONFLICT REPLACE,v);" ]); }); const maxIterations = urlArgs.has('iterations') ? (+urlArgs.get('iterations') || 10) : 10; stdout("Starting interval-based db updates with delay of",interval.delay,"ms."); const doWork = async ()=>{ const tm = new Date().getTime(); ++interval.count; const prefix = "v(#"+interval.count+")"; stdout("Setting",prefix,"=",tm); try{ db.exec({ sql:"INSERT OR REPLACE INTO t1(w,v) VALUES(?,?)", bind: [options.workerName, new Date().getTime()] }); //stdout("Set",prefix); }catch(e){ interval.error = e; } }; if(1){/*use setInterval()*/ setTimeout(async function timer(){ await doWork(); if(interval.error || maxIterations === interval.count){ finish(); }else{ setTimeout(timer, interval.delay); } }, interval.delay); }else{ /*This approach provides no concurrency whatsoever: each worker is run to completion before any others can work.*/ let i; for(i = 0; i < maxIterations; ++i){ await doWork(); if(interval.error) break; await wait(interval.ms); } finish(); } }/*run()*/; self.onmessage = function({data}){ switch(data.type){ case 'run': run().catch((e)=>{ if(!interval.error) interval.error = e; finish(); }); break; default: stderr("Unhandled message type '"+data.type+"'."); break; } }; }); |
Changes to ext/wasm/wasmfs.make.
︙ | ︙ | |||
11 12 13 14 15 16 17 18 19 20 | # subdirectory because loading of the auxiliary # sqlite3-wasmfs.worker.js file it creates fails if sqlite3-wasmfs.js # is loaded from any directory other than the one in which the # containing HTML lives. Similarly, they cannot be loaded from a # Worker to an Emscripten quirk regarding loading nested Workers. dir.wasmfs := $(dir.wasm) sqlite3-wasmfs.js := $(dir.wasmfs)/sqlite3-wasmfs.js sqlite3-wasmfs.wasm := $(dir.wasmfs)/sqlite3-wasmfs.wasm CLEAN_FILES += $(sqlite3-wasmfs.js) $(sqlite3-wasmfs.wasm) \ | > | > > | | | | | | | | | > | | | > | | | | | < < < < < < < < < | | | > | | | | | > > | | > | > | > > > | | > > > | > | > > > | > > > > > | > > > | | | | | | | | | | < | | > | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | # subdirectory because loading of the auxiliary # sqlite3-wasmfs.worker.js file it creates fails if sqlite3-wasmfs.js # is loaded from any directory other than the one in which the # containing HTML lives. Similarly, they cannot be loaded from a # Worker to an Emscripten quirk regarding loading nested Workers. dir.wasmfs := $(dir.wasm) sqlite3-wasmfs.js := $(dir.wasmfs)/sqlite3-wasmfs.js sqlite3-wasmfs.mjs := $(dir.wasmfs)/sqlite3-wasmfs.mjs sqlite3-wasmfs.wasm := $(dir.wasmfs)/sqlite3-wasmfs.wasm CLEAN_FILES += $(sqlite3-wasmfs.js) $(sqlite3-wasmfs.wasm) \ $(subst .js,.worker.js,$(sqlite3-wasmfs.js)) \ $(sqlite3-wasmfs.mjs) \ $(subst .mjs,.worker.mjs,$(sqlite3-wasmfs.mjs)) ######################################################################## # emcc flags for .c/.o. cflags.sqlite3-wasmfs := cflags.sqlite3-wasmfs += -std=c99 -fPIC cflags.sqlite3-wasmfs += -pthread cflags.sqlite3-wasmfs += $(cflags.speedtest1) cflags.sqlite3-wasmfs += $(SQLITE_OPT) -DSQLITE_ENABLE_WASMFS ######################################################################## # emcc flags specific to building the final .js/.wasm file... emcc.flags.sqlite3-wasmfs := -fPIC emcc.flags.sqlite3-wasmfs += --no-entry emcc.flags.sqlite3-wasmfs += --minify 0 emcc.flags.sqlite3-wasmfs += -sMODULARIZE emcc.flags.sqlite3-wasmfs += -sEXPORT_NAME=$(sqlite3.js.init-func) emcc.flags.sqlite3-wasmfs += -sSTRICT_JS emcc.flags.sqlite3-wasmfs += -sDYNAMIC_EXECUTION=0 emcc.flags.sqlite3-wasmfs += -sNO_POLYFILL emcc.flags.sqlite3-wasmfs += -sWASM_BIGINT=$(emcc.WASM_BIGINT) emcc.flags.sqlite3-wasmfs += -sEXPORTED_FUNCTIONS=@$(abspath $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api) emcc.flags.sqlite3-wasmfs += -sEXPORTED_RUNTIME_METHODS=FS,wasmMemory,allocateUTF8OnStack # wasmMemory ==> for -sIMPORTED_MEMORY # allocateUTF8OnStack ==> wasmfs internals emcc.flags.sqlite3-wasmfs += -sUSE_CLOSURE_COMPILER=0 emcc.flags.sqlite3-wasmfs += -Wno-limited-postlink-optimizations # ^^^^^ it likes to warn when we have "limited optimizations" via the -g3 flag. emcc.flags.sqlite3-wasmfs += -sALLOW_TABLE_GROWTH emcc.flags.sqlite3-wasmfs += -sSTACK_SIZE=512KB emcc.flags.sqlite3-wasmfs += -sGLOBAL_BASE=4096 # HYPOTHETICALLY keep func table indexes from overlapping w/ heap addr. emcc.flags.sqlite3-wasmfs += -sMEMORY64=0 emcc.flags.sqlite3-wasmfs += -sIMPORTED_MEMORY emcc.flags.sqlite3-wasmfs += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.128) # ^^^^ 64MB is not enough for WASMFS/OPFS test runs using batch-runner.js sqlite3-wasmfs.fsflags := -pthread -sWASMFS \ -sPTHREAD_POOL_SIZE=2 -sENVIRONMENT=web,worker \ -sERROR_ON_UNDEFINED_SYMBOLS=0 -sLLD_REPORT_UNDEFINED # ^^^^^ why undefined symbols are necessary for the wasmfs build is anyone's guess. emcc.flags.sqlite3-wasmfs += $(sqlite3-wasmfs.fsflags) #emcc.flags.sqlite3-wasmfs += -sALLOW_MEMORY_GROWTH #^^^ using ALLOW_MEMORY_GROWTH produces a warning from emcc: # USE_PTHREADS + ALLOW_MEMORY_GROWTH may run non-wasm code slowly, # see https://github.com/WebAssembly/design/issues/1271 [-Wpthreads-mem-growth] # And, indeed, it runs slowly if memory is permitted to grow. emcc.flags.sqlite3-wasmfs.vanilla := emcc.flags.sqlite3-wasmfs.esm := -sEXPORT_ES6 -sUSE_ES6_IMPORT_META $(eval $(call call-make-pre-js,sqlite3-wasmfs,vanilla)) $(eval $(call call-make-pre-js,sqlite3-wasmfs,esm)) Xemcc.flags.sqlite3-wasmfs.vanilla += \ $(pre-post-common.flags.vanilla) \ $(pre-post-sqlite3-wasmfs.flags.vanilla) Xemcc.flags.sqlite3-wasmfs.esm += \ $(pre-post-common.flags.esm) \ $(pre-post-sqlite3-wasmfs.flags.esm) $(sqlite3-wasmfs.js) $(sqlite3-wasmfs.mjs): $(sqlite3-wasm.c) \ $(EXPORTED_FUNCTIONS.api) $(MAKEFILE) $(MAKEFILE.wasmfs) $(sqlite3-wasmfs.js): $(pre-post-sqlite3-wasmfs.deps.vanilla) $(sqlite3-wasmfs.mjs): $(pre-post-sqlite3-wasmfs.deps.esm) # SQLITE3-WASMFS.xJS.RECIPE is the wasmfs-specific counterpart # of SQLITE3.xJS.RECIPE from the main makefile. define SQLITE3-WASMFS.xJS.RECIPE @echo "Building $@ ..." $(emcc.bin) -o $@ $(emcc_opt_full) $(emcc.flags) \ $(cflags.sqlite3-wasmfs) \ $(emcc.flags.sqlite3-wasmfs) $(emcc.flags.sqlite3-wasmfs.$(1)) \ $(pre-post-sqlite3-wasmfs.flags.$(1)) \ $(sqlite3-wasm.c) @$(call SQLITE3.xJS.ESM-EXPORT-DEFAULT,$(1)) chmod -x $(sqlite3-wasmfs.wasm) $(maybe-wasm-strip) $(sqlite3-wasmfs.wasm) @ls -la $(sqlite3-wasmfs.wasm) sqlite3-wasmfs*js endef $(sqlite3-wasmfs.js): $(call SQLITE3-WASMFS.xJS.RECIPE,vanilla) $(sqlite3-wasmfs.mjs): $(sqlite3-wasmfs.js) $(call SQLITE3-WASMFS.xJS.RECIPE,esm) $(sqlite3-wasmfs.wasm): $(sqlite3-wasmfs.js) wasmfs: $(sqlite3-wasmfs.js) $(sqlite3-wasmfs.mjs) #all: wasmfs ######################################################################## # speedtest1 for wasmfs. speedtest1-wasmfs.js := $(dir.wasmfs)/speedtest1-wasmfs.js speedtest1-wasmfs.wasm := $(subst .js,.wasm,$(speedtest1-wasmfs.js)) emcc.flags.speedtest1-wasmfs := $(sqlite3-wasmfs.fsflags) emcc.flags.speedtest1-wasmfs += $(SQLITE_OPT) -DSQLITE_ENABLE_WASMFS emcc.flags.speedtest1-wasmfs += -sALLOW_MEMORY_GROWTH=0 emcc.flags.speedtest1-wasmfs += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.128) #$(eval $(call call-make-pre-js,speedtest1-wasmfs,vanilla)) $(speedtest1-wasmfs.js): $(speedtest1.cses) $(sqlite3-wasmfs.js) \ $(MAKEFILE) $(MAKEFILE.wasmfs) \ $(pre-post-sqlite3-wasmfs.deps) \ $(EXPORTED_FUNCTIONS.speedtest1) @echo "Building $@ ..." $(emcc.bin) \ $(emcc.speedtest1.common) $(emcc.flags.speedtest1-wasmfs) \ $(pre-post-sqlite3-wasmfs.flags.vanilla) \ $(cflags.sqlite3-wasmfs) \ -o $@ $(speedtest1.cses) -lm $(maybe-wasm-strip) $(speedtest1-wasmfs.wasm) ls -la $@ $(speedtest1-wasmfs.wasm) #speedtest1: $(speedtest1-wasmfs.js) wasmfs: $(speedtest1-wasmfs.js) CLEAN_FILES += $(speedtest1-wasmfs.js) $(speedtest1-wasmfs.wasm) \ $(subst .js,.worker.js,$(speedtest1-wasmfs.js)) # end speedtest1.js ######################################################################## |
Changes to main.mk.
︙ | ︙ | |||
359 360 361 362 363 364 365 366 367 368 369 370 371 372 | $(TOP)/src/test_wsd.c # Extensions to be statically loaded. # TESTSRC += \ $(TOP)/ext/misc/amatch.c \ $(TOP)/ext/misc/appendvfs.c \ $(TOP)/ext/misc/carray.c \ $(TOP)/ext/misc/cksumvfs.c \ $(TOP)/ext/misc/closure.c \ $(TOP)/ext/misc/csv.c \ $(TOP)/ext/misc/decimal.c \ $(TOP)/ext/misc/eval.c \ $(TOP)/ext/misc/explain.c \ | > | 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 | $(TOP)/src/test_wsd.c # Extensions to be statically loaded. # TESTSRC += \ $(TOP)/ext/misc/amatch.c \ $(TOP)/ext/misc/appendvfs.c \ $(TOP)/ext/misc/basexx.c \ $(TOP)/ext/misc/carray.c \ $(TOP)/ext/misc/cksumvfs.c \ $(TOP)/ext/misc/closure.c \ $(TOP)/ext/misc/csv.c \ $(TOP)/ext/misc/decimal.c \ $(TOP)/ext/misc/eval.c \ $(TOP)/ext/misc/explain.c \ |
︙ | ︙ | |||
753 754 755 756 757 758 759 760 761 762 763 764 765 766 | ./mkkeywordhash >keywordhash.h # Source files that go into making shell.c SHELL_SRC = \ $(TOP)/src/shell.c.in \ $(TOP)/ext/misc/appendvfs.c \ $(TOP)/ext/misc/completion.c \ $(TOP)/ext/misc/decimal.c \ $(TOP)/ext/misc/fileio.c \ $(TOP)/ext/misc/ieee754.c \ $(TOP)/ext/misc/regexp.c \ $(TOP)/ext/misc/series.c \ $(TOP)/ext/misc/shathree.c \ $(TOP)/ext/misc/sqlar.c \ | > > | 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 | ./mkkeywordhash >keywordhash.h # Source files that go into making shell.c SHELL_SRC = \ $(TOP)/src/shell.c.in \ $(TOP)/ext/misc/appendvfs.c \ $(TOP)/ext/misc/completion.c \ $(TOP)/ext/misc/base64.c \ $(TOP)/ext/misc/base85.c \ $(TOP)/ext/misc/decimal.c \ $(TOP)/ext/misc/fileio.c \ $(TOP)/ext/misc/ieee754.c \ $(TOP)/ext/misc/regexp.c \ $(TOP)/ext/misc/series.c \ $(TOP)/ext/misc/shathree.c \ $(TOP)/ext/misc/sqlar.c \ |
︙ | ︙ |
Changes to src/alter.c.
︙ | ︙ | |||
737 738 739 740 741 742 743 | ** following a valid object, it may not be used in comparison operations. */ static void renameTokenCheckAll(Parse *pParse, const void *pPtr){ assert( pParse==pParse->db->pParse ); assert( pParse->db->mallocFailed==0 || pParse->nErr!=0 ); if( pParse->nErr==0 ){ const RenameToken *p; | | | > | 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 | ** following a valid object, it may not be used in comparison operations. */ static void renameTokenCheckAll(Parse *pParse, const void *pPtr){ assert( pParse==pParse->db->pParse ); assert( pParse->db->mallocFailed==0 || pParse->nErr!=0 ); if( pParse->nErr==0 ){ const RenameToken *p; u32 i = 1; for(p=pParse->pRename; p; p=p->pNext){ if( p->p ){ assert( p->p!=pPtr ); i += *(u8*)(p->p) | 1; } } assert( i>0 ); } } #else # define renameTokenCheckAll(x,y) #endif /* |
︙ | ︙ |
Changes to src/btree.c.
︙ | ︙ | |||
1896 1897 1898 1899 1900 1901 1902 | /* ** Decode the flags byte (the first byte of the header) for a page ** and initialize fields of the MemPage structure accordingly. ** ** Only the following combinations are supported. Anything different ** indicates a corrupt database files: ** | | > | | < | > | | | | < | | | | > > | > | > > > > > | | | | > > | > > > > > > > > > > < < < < < < < < < < < | < < | | | < < | | | | | | < > | 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 | /* ** Decode the flags byte (the first byte of the header) for a page ** and initialize fields of the MemPage structure accordingly. ** ** Only the following combinations are supported. Anything different ** indicates a corrupt database files: ** ** PTF_ZERODATA (0x02, 2) ** PTF_LEAFDATA | PTF_INTKEY (0x05, 5) ** PTF_ZERODATA | PTF_LEAF (0x0a, 10) ** PTF_LEAFDATA | PTF_INTKEY | PTF_LEAF (0x0d, 13) */ static int decodeFlags(MemPage *pPage, int flagByte){ BtShared *pBt; /* A copy of pPage->pBt */ assert( pPage->hdrOffset==(pPage->pgno==1 ? 100 : 0) ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); pBt = pPage->pBt; pPage->max1bytePayload = pBt->max1bytePayload; if( flagByte>=(PTF_ZERODATA | PTF_LEAF) ){ pPage->childPtrSize = 0; pPage->leaf = 1; if( flagByte==(PTF_LEAFDATA | PTF_INTKEY | PTF_LEAF) ){ pPage->intKeyLeaf = 1; pPage->xCellSize = cellSizePtrTableLeaf; pPage->xParseCell = btreeParseCellPtr; pPage->intKey = 1; pPage->maxLocal = pBt->maxLeaf; pPage->minLocal = pBt->minLeaf; }else if( flagByte==(PTF_ZERODATA | PTF_LEAF) ){ pPage->intKey = 0; pPage->intKeyLeaf = 0; pPage->xCellSize = cellSizePtr; pPage->xParseCell = btreeParseCellPtrIndex; pPage->maxLocal = pBt->maxLocal; pPage->minLocal = pBt->minLocal; }else{ pPage->intKey = 0; pPage->intKeyLeaf = 0; pPage->xCellSize = cellSizePtr; pPage->xParseCell = btreeParseCellPtrIndex; return SQLITE_CORRUPT_PAGE(pPage); } }else{ pPage->childPtrSize = 4; pPage->leaf = 0; if( flagByte==(PTF_ZERODATA) ){ pPage->intKey = 0; pPage->intKeyLeaf = 0; pPage->xCellSize = cellSizePtr; pPage->xParseCell = btreeParseCellPtrIndex; pPage->maxLocal = pBt->maxLocal; pPage->minLocal = pBt->minLocal; }else if( flagByte==(PTF_LEAFDATA | PTF_INTKEY) ){ pPage->intKeyLeaf = 0; pPage->xCellSize = cellSizePtrNoPayload; pPage->xParseCell = btreeParseCellPtrNoPayload; pPage->intKey = 1; pPage->maxLocal = pBt->maxLeaf; pPage->minLocal = pBt->minLeaf; }else{ pPage->intKey = 0; pPage->intKeyLeaf = 0; pPage->xCellSize = cellSizePtr; pPage->xParseCell = btreeParseCellPtrIndex; return SQLITE_CORRUPT_PAGE(pPage); } } return SQLITE_OK; } /* ** Compute the amount of freespace on the page. In other words, fill ** in the pPage->nFree field. */ |
︙ | ︙ | |||
5491 5492 5493 5494 5495 5496 5497 | return rc; } /* Move the cursor to the last entry in the table. Return SQLITE_OK ** on success. Set *pRes to 0 if the cursor actually points to something ** or set *pRes to 1 if the table is empty. */ | | | > > > > > > > > | > > > > > > > > < < < < < < < < < < < < < < < < | | 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 5514 5515 5516 5517 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534 5535 5536 5537 5538 5539 5540 5541 5542 5543 5544 5545 5546 5547 5548 5549 | return rc; } /* Move the cursor to the last entry in the table. Return SQLITE_OK ** on success. Set *pRes to 0 if the cursor actually points to something ** or set *pRes to 1 if the table is empty. */ static SQLITE_NOINLINE int btreeLast(BtCursor *pCur, int *pRes){ int rc = moveToRoot(pCur); if( rc==SQLITE_OK ){ assert( pCur->eState==CURSOR_VALID ); *pRes = 0; rc = moveToRightmost(pCur); if( rc==SQLITE_OK ){ pCur->curFlags |= BTCF_AtLast; }else{ pCur->curFlags &= ~BTCF_AtLast; } }else if( rc==SQLITE_EMPTY ){ assert( pCur->pgnoRoot==0 || pCur->pPage->nCell==0 ); *pRes = 1; rc = SQLITE_OK; } return rc; } int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ assert( cursorOwnsBtShared(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); /* If the cursor already points to the last entry, this is a no-op. */ if( CURSOR_VALID==pCur->eState && (pCur->curFlags & BTCF_AtLast)!=0 ){ #ifdef SQLITE_DEBUG /* This block serves to assert() that the cursor really does point ** to the last entry in the b-tree. */ int ii; for(ii=0; ii<pCur->iPage; ii++){ assert( pCur->aiIdx[ii]==pCur->apPage[ii]->nCell ); } assert( pCur->ix==pCur->pPage->nCell-1 || CORRUPT_DB ); testcase( pCur->ix!=pCur->pPage->nCell-1 ); /* ^-- dbsqlfuzz b92b72e4de80b5140c30ab71372ca719b8feb618 */ assert( pCur->pPage->leaf ); #endif *pRes = 0; return SQLITE_OK; } return btreeLast(pCur, pRes); } /* Move the cursor so that it points to an entry in a table (a.k.a INTKEY) ** table near the key intKey. Return a success code. ** ** If an exact match is not found, then the cursor is always ** left pointing at a leaf page which would hold the entry if it |
︙ | ︙ | |||
6597 6598 6599 6600 6601 6602 6603 | } memset(pPage->aData, 0, pPage->pBt->pageSize); } /* If the database supports auto-vacuum, write an entry in the pointer-map ** to indicate that the page is free. */ | | | 6602 6603 6604 6605 6606 6607 6608 6609 6610 6611 6612 6613 6614 6615 6616 | } memset(pPage->aData, 0, pPage->pBt->pageSize); } /* If the database supports auto-vacuum, write an entry in the pointer-map ** to indicate that the page is free. */ if( ISAUTOVACUUM(pBt) ){ ptrmapPut(pBt, iPage, PTRMAP_FREEPAGE, 0, &rc); if( rc ) goto freepage_out; } /* Now manipulate the actual database free-list structure. There are two ** possibilities. If the free-list is currently empty, or if the first ** trunk page in the free-list is full, then this page will become a |
︙ | ︙ | |||
7037 7038 7039 7040 7041 7042 7043 | ** If the cell content will fit on the page, then put it there. If it ** will not fit, then make a copy of the cell content into pTemp if ** pTemp is not null. Regardless of pTemp, allocate a new entry ** in pPage->apOvfl[] and make it point to the cell content (either ** in pTemp or the original pCell) and also record its index. ** Allocating a new entry in pPage->aCell[] implies that ** pPage->nOverflow is incremented. | < < | | < < | 7042 7043 7044 7045 7046 7047 7048 7049 7050 7051 7052 7053 7054 7055 7056 7057 7058 7059 7060 7061 7062 7063 7064 7065 7066 7067 7068 7069 | ** If the cell content will fit on the page, then put it there. If it ** will not fit, then make a copy of the cell content into pTemp if ** pTemp is not null. Regardless of pTemp, allocate a new entry ** in pPage->apOvfl[] and make it point to the cell content (either ** in pTemp or the original pCell) and also record its index. ** Allocating a new entry in pPage->aCell[] implies that ** pPage->nOverflow is incremented. */ static int insertCell( MemPage *pPage, /* Page into which we are copying */ int i, /* New cell becomes the i-th cell of the page */ u8 *pCell, /* Content of the new cell */ int sz, /* Bytes of content in pCell */ u8 *pTemp, /* Temp storage space for pCell, if needed */ Pgno iChild /* If non-zero, replace first 4 bytes with this value */ ){ int idx = 0; /* Where to write new cell content in data[] */ int j; /* Loop counter */ u8 *data; /* The content of the whole page */ u8 *pIns; /* The point in pPage->aCellIdx[] where no cell inserted */ assert( i>=0 && i<=pPage->nCell+pPage->nOverflow ); assert( MX_CELL(pPage->pBt)<=10921 ); assert( pPage->nCell<=MX_CELL(pPage->pBt) || CORRUPT_DB ); assert( pPage->nOverflow<=ArraySize(pPage->apOvfl) ); assert( ArraySize(pPage->apOvfl)==ArraySize(pPage->aiOvfl) ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); assert( sz==pPage->xCellSize(pPage, pCell) || CORRUPT_DB ); |
︙ | ︙ | |||
7089 7090 7091 7092 7093 7094 7095 | ** balancing, and the dividers are adjacent and sorted. */ assert( j==0 || pPage->aiOvfl[j-1]<(u16)i ); /* Overflows in sorted order */ assert( j==0 || i==pPage->aiOvfl[j-1]+1 ); /* Overflows are sequential */ }else{ int rc = sqlite3PagerWrite(pPage->pDbPage); if( rc!=SQLITE_OK ){ | < | | | 7090 7091 7092 7093 7094 7095 7096 7097 7098 7099 7100 7101 7102 7103 7104 7105 7106 7107 7108 7109 7110 | ** balancing, and the dividers are adjacent and sorted. */ assert( j==0 || pPage->aiOvfl[j-1]<(u16)i ); /* Overflows in sorted order */ assert( j==0 || i==pPage->aiOvfl[j-1]+1 ); /* Overflows are sequential */ }else{ int rc = sqlite3PagerWrite(pPage->pDbPage); if( rc!=SQLITE_OK ){ return rc; } assert( sqlite3PagerIswriteable(pPage->pDbPage) ); data = pPage->aData; assert( &data[pPage->cellOffset]==pPage->aCellIdx ); rc = allocateSpace(pPage, sz, &idx); if( rc ){ return rc; } /* The allocateSpace() routine guarantees the following properties ** if it returns successfully */ assert( idx >= 0 ); assert( idx >= pPage->cellOffset+2*pPage->nCell+2 || CORRUPT_DB ); assert( idx+sz <= (int)pPage->pBt->usableSize ); pPage->nFree -= (u16)(2 + sz); if( iChild ){ |
︙ | ︙ | |||
7123 7124 7125 7126 7127 7128 7129 7130 7131 7132 | put2byte(pIns, idx); pPage->nCell++; /* increment the cell count */ if( (++data[pPage->hdrOffset+4])==0 ) data[pPage->hdrOffset+3]++; assert( get2byte(&data[pPage->hdrOffset+3])==pPage->nCell || CORRUPT_DB ); #ifndef SQLITE_OMIT_AUTOVACUUM if( pPage->pBt->autoVacuum ){ /* The cell may contain a pointer to an overflow page. If so, write ** the entry for the overflow page into the pointer map. */ | > | > > | 7123 7124 7125 7126 7127 7128 7129 7130 7131 7132 7133 7134 7135 7136 7137 7138 7139 7140 7141 7142 7143 7144 7145 7146 | put2byte(pIns, idx); pPage->nCell++; /* increment the cell count */ if( (++data[pPage->hdrOffset+4])==0 ) data[pPage->hdrOffset+3]++; assert( get2byte(&data[pPage->hdrOffset+3])==pPage->nCell || CORRUPT_DB ); #ifndef SQLITE_OMIT_AUTOVACUUM if( pPage->pBt->autoVacuum ){ int rc2 = SQLITE_OK; /* The cell may contain a pointer to an overflow page. If so, write ** the entry for the overflow page into the pointer map. */ ptrmapPutOvflPtr(pPage, pPage, pCell, &rc2); if( rc2 ) return rc2; } #endif } return SQLITE_OK; } /* ** The following parameters determine how many adjacent pages get involved ** in a balancing operation. NN is the number of neighbors on either side ** of the page that participate in the balancing operation. NB is the ** total number of pages that participate, including the target page and |
︙ | ︙ | |||
7230 7231 7232 7233 7234 7235 7236 7237 7238 7239 | }; /* ** Make sure the cell sizes at idx, idx+1, ..., idx+N-1 have been ** computed. */ static void populateCellCache(CellArray *p, int idx, int N){ assert( idx>=0 && idx+N<=p->nCell ); while( N>0 ){ assert( p->apCell[idx]!=0 ); | > > | | | | 7233 7234 7235 7236 7237 7238 7239 7240 7241 7242 7243 7244 7245 7246 7247 7248 7249 7250 7251 7252 7253 7254 7255 7256 | }; /* ** Make sure the cell sizes at idx, idx+1, ..., idx+N-1 have been ** computed. */ static void populateCellCache(CellArray *p, int idx, int N){ MemPage *pRef = p->pRef; u16 *szCell = p->szCell; assert( idx>=0 && idx+N<=p->nCell ); while( N>0 ){ assert( p->apCell[idx]!=0 ); if( szCell[idx]==0 ){ szCell[idx] = pRef->xCellSize(pRef, p->apCell[idx]); }else{ assert( CORRUPT_DB || szCell[idx]==pRef->xCellSize(pRef, p->apCell[idx]) ); } idx++; N--; } } /* |
︙ | ︙ | |||
7439 7440 7441 7442 7443 7444 7445 | ){ u8 * const aData = pPg->aData; u8 * const pEnd = &aData[pPg->pBt->usableSize]; u8 * const pStart = &aData[pPg->hdrOffset + 8 + pPg->childPtrSize]; int nRet = 0; int i; int iEnd = iFirst + nCell; | | | > > > | 7444 7445 7446 7447 7448 7449 7450 7451 7452 7453 7454 7455 7456 7457 7458 7459 7460 7461 7462 7463 7464 7465 7466 7467 7468 7469 7470 7471 7472 7473 7474 7475 7476 7477 7478 7479 7480 7481 7482 | ){ u8 * const aData = pPg->aData; u8 * const pEnd = &aData[pPg->pBt->usableSize]; u8 * const pStart = &aData[pPg->hdrOffset + 8 + pPg->childPtrSize]; int nRet = 0; int i; int iEnd = iFirst + nCell; u8 *pFree = 0; /* \__ Parameters for pending call to */ int szFree = 0; /* / freeSpace() */ for(i=iFirst; i<iEnd; i++){ u8 *pCell = pCArray->apCell[i]; if( SQLITE_WITHIN(pCell, pStart, pEnd) ){ int sz; /* No need to use cachedCellSize() here. The sizes of all cells that ** are to be freed have already been computing while deciding which ** cells need freeing */ sz = pCArray->szCell[i]; assert( sz>0 ); if( pFree!=(pCell + sz) ){ if( pFree ){ assert( pFree>aData && (pFree - aData)<65536 ); freeSpace(pPg, (u16)(pFree - aData), szFree); } pFree = pCell; szFree = sz; if( pFree+sz>pEnd ){ return 0; } }else{ /* The current cell is adjacent to and before the pFree cell. ** Combine the two regions into one to reduce the number of calls ** to freeSpace(). */ pFree = pCell; szFree += sz; } nRet++; } } if( pFree ){ |
︙ | ︙ | |||
7668 7669 7670 7671 7672 7673 7674 | ** cell on the page to an overflow page. If either of these ** operations fails, the return code is set, but the contents ** of the parent page are still manipulated by thh code below. ** That is Ok, at this point the parent page is guaranteed to ** be marked as dirty. Returning an error code will cause a ** rollback, undoing any changes made to the parent page. */ | | | 7676 7677 7678 7679 7680 7681 7682 7683 7684 7685 7686 7687 7688 7689 7690 | ** cell on the page to an overflow page. If either of these ** operations fails, the return code is set, but the contents ** of the parent page are still manipulated by thh code below. ** That is Ok, at this point the parent page is guaranteed to ** be marked as dirty. Returning an error code will cause a ** rollback, undoing any changes made to the parent page. */ if( ISAUTOVACUUM(pBt) ){ ptrmapPut(pBt, pgnoNew, PTRMAP_BTREE, pParent->pgno, &rc); if( szCell>pNew->minLocal ){ ptrmapPutOvflPtr(pNew, pNew, pCell, &rc); } } /* Create a divider cell to insert into pParent. The divider cell |
︙ | ︙ | |||
7696 7697 7698 7699 7700 7701 7702 | pStop = &pCell[9]; while( (*(pCell++)&0x80) && pCell<pStop ); pStop = &pCell[9]; while( ((*(pOut++) = *(pCell++))&0x80) && pCell<pStop ); /* Insert the new divider cell into pParent. */ if( rc==SQLITE_OK ){ | | | | 7704 7705 7706 7707 7708 7709 7710 7711 7712 7713 7714 7715 7716 7717 7718 7719 | pStop = &pCell[9]; while( (*(pCell++)&0x80) && pCell<pStop ); pStop = &pCell[9]; while( ((*(pOut++) = *(pCell++))&0x80) && pCell<pStop ); /* Insert the new divider cell into pParent. */ if( rc==SQLITE_OK ){ rc = insertCell(pParent, pParent->nCell, pSpace, (int)(pOut-pSpace), 0, pPage->pgno); } /* Set the right-child pointer of pParent to point to the new page. */ put4byte(&pParent->aData[pParent->hdrOffset+8], pgnoNew); /* Release the reference to the new page. */ releasePage(pNew); |
︙ | ︙ | |||
7806 7807 7808 7809 7810 7811 7812 | *pRC = rc; return; } /* If this is an auto-vacuum database, update the pointer-map entries ** for any b-tree or overflow pages that pTo now contains the pointers to. */ | | | 7814 7815 7816 7817 7818 7819 7820 7821 7822 7823 7824 7825 7826 7827 7828 | *pRC = rc; return; } /* If this is an auto-vacuum database, update the pointer-map entries ** for any b-tree or overflow pages that pTo now contains the pointers to. */ if( ISAUTOVACUUM(pBt) ){ *pRC = setChildPtrmaps(pTo); } } } /* ** This routine redistributes cells on the iParentIdx'th child of pParent |
︙ | ︙ | |||
8230 8231 8232 8233 8234 8235 8236 8237 8238 | int r; /* Index of right-most cell in left sibling */ int d; /* Index of first cell to the left of right sibling */ r = cntNew[i-1] - 1; d = r + 1 - leafData; (void)cachedCellSize(&b, d); do{ assert( d<nMaxCells ); assert( r<nMaxCells ); | > | > | | | | 8238 8239 8240 8241 8242 8243 8244 8245 8246 8247 8248 8249 8250 8251 8252 8253 8254 8255 8256 8257 8258 8259 8260 8261 8262 | int r; /* Index of right-most cell in left sibling */ int d; /* Index of first cell to the left of right sibling */ r = cntNew[i-1] - 1; d = r + 1 - leafData; (void)cachedCellSize(&b, d); do{ int szR, szD; assert( d<nMaxCells ); assert( r<nMaxCells ); szR = cachedCellSize(&b, r); szD = b.szCell[d]; if( szRight!=0 && (bBulk || szRight+szD+2 > szLeft-(szR+(i==k-1?0:2)))){ break; } szRight += szD + 2; szLeft -= szR + 2; cntNew[i-1] = r; r--; d--; }while( r>=0 ); szNew[i] = szRight; szNew[i-1] = szLeft; if( cntNew[i-1] <= (i>1 ? cntNew[i-2] : 0) ){ |
︙ | ︙ | |||
8292 8293 8294 8295 8296 8297 8298 | if( rc ) goto balance_cleanup; zeroPage(pNew, pageFlags); apNew[i] = pNew; nNew++; cntOld[i] = b.nCell; /* Set the pointer-map entry for the new sibling page. */ | | | 8302 8303 8304 8305 8306 8307 8308 8309 8310 8311 8312 8313 8314 8315 8316 | if( rc ) goto balance_cleanup; zeroPage(pNew, pageFlags); apNew[i] = pNew; nNew++; cntOld[i] = b.nCell; /* Set the pointer-map entry for the new sibling page. */ if( ISAUTOVACUUM(pBt) ){ ptrmapPut(pBt, pNew->pgno, PTRMAP_BTREE, pParent->pgno, &rc); if( rc!=SQLITE_OK ){ goto balance_cleanup; } } } } |
︙ | ︙ | |||
8385 8386 8387 8388 8389 8390 8391 | ** with the cell. ** ** If the sibling pages are not leaves, then the pointer map entry ** associated with the right-child of each sibling may also need to be ** updated. This happens below, after the sibling pages have been ** populated, not here. */ | | | 8395 8396 8397 8398 8399 8400 8401 8402 8403 8404 8405 8406 8407 8408 8409 | ** with the cell. ** ** If the sibling pages are not leaves, then the pointer map entry ** associated with the right-child of each sibling may also need to be ** updated. This happens below, after the sibling pages have been ** populated, not here. */ if( ISAUTOVACUUM(pBt) ){ MemPage *pOld; MemPage *pNew = pOld = apNew[0]; int cntOldNext = pNew->nCell + pNew->nOverflow; int iNew = 0; int iOld = 0; for(i=0; i<b.nCell; i++){ |
︙ | ︙ | |||
8482 8483 8484 8485 8486 8487 8488 | assert( iOvflSpace <= (int)pBt->pageSize ); for(k=0; b.ixNx[k]<=j && ALWAYS(k<NB*2); k++){} pSrcEnd = b.apEnd[k]; if( SQLITE_WITHIN(pSrcEnd, pCell, pCell+sz) ){ rc = SQLITE_CORRUPT_BKPT; goto balance_cleanup; } | | | 8492 8493 8494 8495 8496 8497 8498 8499 8500 8501 8502 8503 8504 8505 8506 | assert( iOvflSpace <= (int)pBt->pageSize ); for(k=0; b.ixNx[k]<=j && ALWAYS(k<NB*2); k++){} pSrcEnd = b.apEnd[k]; if( SQLITE_WITHIN(pSrcEnd, pCell, pCell+sz) ){ rc = SQLITE_CORRUPT_BKPT; goto balance_cleanup; } rc = insertCell(pParent, nxDiv+i, pCell, sz, pTemp, pNew->pgno); if( rc!=SQLITE_OK ) goto balance_cleanup; assert( sqlite3PagerIswriteable(pParent->pDbPage) ); } /* Now update the actual sibling pages. The order in which they are updated ** is important, as this code needs to avoid disrupting any page from which ** cells may still to be read. In practice, this means: |
︙ | ︙ | |||
8578 8579 8580 8581 8582 8583 8584 | assert( apNew[0]->nFree == (get2byteNotZero(&apNew[0]->aData[5]) - apNew[0]->cellOffset - apNew[0]->nCell*2) || rc!=SQLITE_OK ); copyNodeContent(apNew[0], pParent, &rc); freePage(apNew[0], &rc); | | | | 8588 8589 8590 8591 8592 8593 8594 8595 8596 8597 8598 8599 8600 8601 8602 8603 8604 8605 8606 8607 8608 8609 8610 8611 8612 8613 8614 8615 8616 8617 8618 8619 8620 8621 8622 8623 | assert( apNew[0]->nFree == (get2byteNotZero(&apNew[0]->aData[5]) - apNew[0]->cellOffset - apNew[0]->nCell*2) || rc!=SQLITE_OK ); copyNodeContent(apNew[0], pParent, &rc); freePage(apNew[0], &rc); }else if( ISAUTOVACUUM(pBt) && !leafCorrection ){ /* Fix the pointer map entries associated with the right-child of each ** sibling page. All other pointer map entries have already been taken ** care of. */ for(i=0; i<nNew; i++){ u32 key = get4byte(&apNew[i]->aData[8]); ptrmapPut(pBt, key, PTRMAP_BTREE, apNew[i]->pgno, &rc); } } assert( pParent->isInit ); TRACE(("BALANCE: finished: old=%d new=%d cells=%d\n", nOld, nNew, b.nCell)); /* Free any old pages that were not reused as new pages. */ for(i=nNew; i<nOld; i++){ freePage(apOld[i], &rc); } #if 0 if( ISAUTOVACUUM(pBt) && rc==SQLITE_OK && apNew[0]->isInit ){ /* The ptrmapCheckPages() contains assert() statements that verify that ** all pointer map pages are set correctly. This is helpful while ** debugging. This is usually disabled because a corrupt database may ** cause an assert() statement to fail. */ ptrmapCheckPages(apNew, nNew); ptrmapCheckPages(&pParent, 1); } |
︙ | ︙ | |||
8661 8662 8663 8664 8665 8666 8667 | ** page that will become the new right-child of pPage. Copy the contents ** of the node stored on pRoot into the new child page. */ rc = sqlite3PagerWrite(pRoot->pDbPage); if( rc==SQLITE_OK ){ rc = allocateBtreePage(pBt,&pChild,&pgnoChild,pRoot->pgno,0); copyNodeContent(pRoot, pChild, &rc); | | | 8671 8672 8673 8674 8675 8676 8677 8678 8679 8680 8681 8682 8683 8684 8685 | ** page that will become the new right-child of pPage. Copy the contents ** of the node stored on pRoot into the new child page. */ rc = sqlite3PagerWrite(pRoot->pDbPage); if( rc==SQLITE_OK ){ rc = allocateBtreePage(pBt,&pChild,&pgnoChild,pRoot->pgno,0); copyNodeContent(pRoot, pChild, &rc); if( ISAUTOVACUUM(pBt) ){ ptrmapPut(pBt, pgnoChild, PTRMAP_BTREE, pRoot->pgno, &rc); } } if( rc ){ *ppChild = 0; releasePage(pChild); return rc; |
︙ | ︙ | |||
8993 8994 8995 8996 8997 8998 8999 | ){ int rc; int loc = seekResult; /* -1: before desired location +1: after */ int szNew = 0; int idx; MemPage *pPage; Btree *p = pCur->pBtree; | < | | 9003 9004 9005 9006 9007 9008 9009 9010 9011 9012 9013 9014 9015 9016 9017 9018 9019 9020 9021 9022 9023 9024 9025 9026 9027 9028 9029 9030 9031 9032 9033 9034 9035 | ){ int rc; int loc = seekResult; /* -1: before desired location +1: after */ int szNew = 0; int idx; MemPage *pPage; Btree *p = pCur->pBtree; unsigned char *oldCell; unsigned char *newCell = 0; assert( (flags & (BTREE_SAVEPOSITION|BTREE_APPEND|BTREE_PREFORMAT))==flags ); assert( (flags & BTREE_PREFORMAT)==0 || seekResult || pCur->pKeyInfo==0 ); /* Save the positions of any other cursors open on this table. ** ** In some cases, the call to btreeMoveto() below is a no-op. For ** example, when inserting data into a table with auto-generated integer ** keys, the VDBE layer invokes sqlite3BtreeLast() to figure out the ** integer key to use. It then calls this function to actually insert the ** data into the intkey B-Tree. In this case btreeMoveto() recognizes ** that the cursor is already where it needs to be and returns without ** doing any work. To avoid thwarting these optimizations, it is important ** not to clear the cursor here. */ if( pCur->curFlags & BTCF_Multiple ){ rc = saveAllCursors(p->pBt, pCur->pgnoRoot, pCur); if( rc ) return rc; if( loc && pCur->iPage<0 ){ /* This can only happen if the schema is corrupt such that there is more ** than one table or index with the same root page as used by the cursor. ** Which can only happen if the SQLITE_NoSchemaError flag was set when ** the schema was loaded. This cannot be asserted though, as a user might ** set the flag, load the schema, and then unset the flag. */ |
︙ | ︙ | |||
9036 9037 9038 9039 9040 9041 9042 | testcase( pCur->eState==CURSOR_FAULT ); rc = moveToRoot(pCur); if( rc && rc!=SQLITE_EMPTY ) return rc; } assert( cursorOwnsBtShared(pCur) ); assert( (pCur->curFlags & BTCF_WriteFlag)!=0 | | | | 9045 9046 9047 9048 9049 9050 9051 9052 9053 9054 9055 9056 9057 9058 9059 9060 | testcase( pCur->eState==CURSOR_FAULT ); rc = moveToRoot(pCur); if( rc && rc!=SQLITE_EMPTY ) return rc; } assert( cursorOwnsBtShared(pCur) ); assert( (pCur->curFlags & BTCF_WriteFlag)!=0 && p->pBt->inTransaction==TRANS_WRITE && (p->pBt->btsFlags & BTS_READ_ONLY)==0 ); assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) ); /* Assert that the caller has been consistent. If this cursor was opened ** expecting an index b-tree, then the caller should be inserting blob ** keys with no associated data. If the cursor was opened expecting an ** intkey table, the caller should be inserting integer keys with a ** blob of associated data. */ |
︙ | ︙ | |||
9154 9155 9156 9157 9158 9159 9160 | if( rc ) return rc; } TRACE(("INSERT: table=%d nkey=%lld ndata=%d page=%d %s\n", pCur->pgnoRoot, pX->nKey, pX->nData, pPage->pgno, loc==0 ? "overwrite" : "new entry")); assert( pPage->isInit || CORRUPT_DB ); | | > | | | > < | > | | | 9163 9164 9165 9166 9167 9168 9169 9170 9171 9172 9173 9174 9175 9176 9177 9178 9179 9180 9181 9182 9183 9184 9185 9186 9187 9188 9189 9190 9191 9192 9193 9194 9195 9196 9197 9198 9199 9200 9201 9202 9203 9204 9205 9206 9207 9208 9209 9210 9211 9212 9213 9214 9215 9216 9217 9218 | if( rc ) return rc; } TRACE(("INSERT: table=%d nkey=%lld ndata=%d page=%d %s\n", pCur->pgnoRoot, pX->nKey, pX->nData, pPage->pgno, loc==0 ? "overwrite" : "new entry")); assert( pPage->isInit || CORRUPT_DB ); newCell = p->pBt->pTmpSpace; assert( newCell!=0 ); assert( BTREE_PREFORMAT==OPFLAG_PREFORMAT ); if( flags & BTREE_PREFORMAT ){ rc = SQLITE_OK; szNew = p->pBt->nPreformatSize; if( szNew<4 ) szNew = 4; if( ISAUTOVACUUM(p->pBt) && szNew>pPage->maxLocal ){ CellInfo info; pPage->xParseCell(pPage, newCell, &info); if( info.nPayload!=info.nLocal ){ Pgno ovfl = get4byte(&newCell[szNew-4]); ptrmapPut(p->pBt, ovfl, PTRMAP_OVERFLOW1, pPage->pgno, &rc); if( NEVER(rc) ) goto end_insert; } } }else{ rc = fillInCell(pPage, newCell, pX, &szNew); if( rc ) goto end_insert; } assert( szNew==pPage->xCellSize(pPage, newCell) ); assert( szNew <= MX_CELL_SIZE(p->pBt) ); idx = pCur->ix; if( loc==0 ){ CellInfo info; assert( idx>=0 ); if( idx>=pPage->nCell ){ return SQLITE_CORRUPT_BKPT; } rc = sqlite3PagerWrite(pPage->pDbPage); if( rc ){ goto end_insert; } oldCell = findCell(pPage, idx); if( !pPage->leaf ){ memcpy(newCell, oldCell, 4); } BTREE_CLEAR_CELL(rc, pPage, oldCell, info); testcase( pCur->curFlags & BTCF_ValidOvfl ); invalidateOverflowCache(pCur); if( info.nSize==szNew && info.nLocal==info.nPayload && (!ISAUTOVACUUM(p->pBt) || szNew<pPage->minLocal) ){ /* Overwrite the old cell with the new if they are the same size. ** We could also try to do this if the old cell is smaller, then add ** the leftover space to the free list. But experiments show that ** doing that is no faster then skipping this optimization and just ** calling dropCell() and insertCell(). ** |
︙ | ︙ | |||
9223 9224 9225 9226 9227 9228 9229 | }else if( loc<0 && pPage->nCell>0 ){ assert( pPage->leaf ); idx = ++pCur->ix; pCur->curFlags &= ~BTCF_ValidNKey; }else{ assert( pPage->leaf ); } | | | 9234 9235 9236 9237 9238 9239 9240 9241 9242 9243 9244 9245 9246 9247 9248 | }else if( loc<0 && pPage->nCell>0 ){ assert( pPage->leaf ); idx = ++pCur->ix; pCur->curFlags &= ~BTCF_ValidNKey; }else{ assert( pPage->leaf ); } rc = insertCell(pPage, idx, newCell, szNew, 0, 0); assert( pPage->nOverflow==0 || rc==SQLITE_OK ); assert( rc!=SQLITE_OK || pPage->nCell>0 || pPage->nOverflow>0 ); /* If no error has occurred and pPage has an overflow cell, call balance() ** to redistribute the cells within the tree. Since balance() may move ** the cursor, zero the BtCursor.info.nSize and BTCF_ValidNKey ** variables. |
︙ | ︙ | |||
9296 9297 9298 9299 9300 9301 9302 | ** for the destination database. The size of the cell, in bytes, is left ** in BtShared.nPreformatSize. The caller completes the insertion by ** calling sqlite3BtreeInsert() with the BTREE_PREFORMAT flag specified. ** ** SQLITE_OK is returned if successful, or an SQLite error code otherwise. */ int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ | < | 9307 9308 9309 9310 9311 9312 9313 9314 9315 9316 9317 9318 9319 9320 | ** for the destination database. The size of the cell, in bytes, is left ** in BtShared.nPreformatSize. The caller completes the insertion by ** calling sqlite3BtreeInsert() with the BTREE_PREFORMAT flag specified. ** ** SQLITE_OK is returned if successful, or an SQLite error code otherwise. */ int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ BtShared *pBt = pDest->pBt; u8 *aOut = pBt->pTmpSpace; /* Pointer to next output buffer */ const u8 *aIn; /* Pointer to next input buffer */ u32 nIn; /* Size of input buffer aIn[] */ u32 nRem; /* Bytes of data still to copy */ getCellInfo(pSrc); |
︙ | ︙ | |||
9319 9320 9321 9322 9323 9324 9325 9326 9327 9328 9329 9330 9331 9332 9333 | if( aIn+nIn>pSrc->pPage->aDataEnd ){ return SQLITE_CORRUPT_BKPT; } nRem = pSrc->info.nPayload; if( nIn==nRem && nIn<pDest->pPage->maxLocal ){ memcpy(aOut, aIn, nIn); pBt->nPreformatSize = nIn + (aOut - pBt->pTmpSpace); }else{ Pager *pSrcPager = pSrc->pBt->pPager; u8 *pPgnoOut = 0; Pgno ovflIn = 0; DbPage *pPageIn = 0; MemPage *pPageOut = 0; u32 nOut; /* Size of output buffer aOut[] */ | > > | 9329 9330 9331 9332 9333 9334 9335 9336 9337 9338 9339 9340 9341 9342 9343 9344 9345 | if( aIn+nIn>pSrc->pPage->aDataEnd ){ return SQLITE_CORRUPT_BKPT; } nRem = pSrc->info.nPayload; if( nIn==nRem && nIn<pDest->pPage->maxLocal ){ memcpy(aOut, aIn, nIn); pBt->nPreformatSize = nIn + (aOut - pBt->pTmpSpace); return SQLITE_OK; }else{ int rc = SQLITE_OK; Pager *pSrcPager = pSrc->pBt->pPager; u8 *pPgnoOut = 0; Pgno ovflIn = 0; DbPage *pPageIn = 0; MemPage *pPageOut = 0; u32 nOut; /* Size of output buffer aOut[] */ |
︙ | ︙ | |||
9371 9372 9373 9374 9375 9376 9377 | }while( rc==SQLITE_OK && nOut>0 ); if( rc==SQLITE_OK && nRem>0 && ALWAYS(pPgnoOut) ){ Pgno pgnoNew; MemPage *pNew = 0; rc = allocateBtreePage(pBt, &pNew, &pgnoNew, 0, 0); put4byte(pPgnoOut, pgnoNew); | | < < | > | 9383 9384 9385 9386 9387 9388 9389 9390 9391 9392 9393 9394 9395 9396 9397 9398 9399 9400 9401 9402 9403 9404 9405 9406 9407 9408 9409 9410 9411 9412 9413 9414 | }while( rc==SQLITE_OK && nOut>0 ); if( rc==SQLITE_OK && nRem>0 && ALWAYS(pPgnoOut) ){ Pgno pgnoNew; MemPage *pNew = 0; rc = allocateBtreePage(pBt, &pNew, &pgnoNew, 0, 0); put4byte(pPgnoOut, pgnoNew); if( ISAUTOVACUUM(pBt) && pPageOut ){ ptrmapPut(pBt, pgnoNew, PTRMAP_OVERFLOW2, pPageOut->pgno, &rc); } releasePage(pPageOut); pPageOut = pNew; if( pPageOut ){ pPgnoOut = pPageOut->aData; put4byte(pPgnoOut, 0); aOut = &pPgnoOut[4]; nOut = MIN(pBt->usableSize - 4, nRem); } } }while( nRem>0 && rc==SQLITE_OK ); releasePage(pPageOut); sqlite3PagerUnref(pPageIn); return rc; } } /* ** Delete the entry that the cursor is pointing to. ** ** If the BTREE_SAVEPOSITION bit of the flags parameter is zero, then ** the cursor is left pointing at an arbitrary location after the delete. |
︙ | ︙ | |||
9544 9545 9546 9547 9548 9549 9550 | if( pCell<&pLeaf->aData[4] ) return SQLITE_CORRUPT_BKPT; nCell = pLeaf->xCellSize(pLeaf, pCell); assert( MX_CELL_SIZE(pBt) >= nCell ); pTmp = pBt->pTmpSpace; assert( pTmp!=0 ); rc = sqlite3PagerWrite(pLeaf->pDbPage); if( rc==SQLITE_OK ){ | | | 9555 9556 9557 9558 9559 9560 9561 9562 9563 9564 9565 9566 9567 9568 9569 | if( pCell<&pLeaf->aData[4] ) return SQLITE_CORRUPT_BKPT; nCell = pLeaf->xCellSize(pLeaf, pCell); assert( MX_CELL_SIZE(pBt) >= nCell ); pTmp = pBt->pTmpSpace; assert( pTmp!=0 ); rc = sqlite3PagerWrite(pLeaf->pDbPage); if( rc==SQLITE_OK ){ rc = insertCell(pPage, iCellIdx, pCell-4, nCell+4, pTmp, n); } dropCell(pLeaf, pLeaf->nCell-1, nCell, &rc); if( rc ) return rc; } /* Balance the tree. If the entry deleted was located on a leaf page, ** then the cursor still points to that page. In this case the first |
︙ | ︙ |
Changes to src/btree.h.
︙ | ︙ | |||
179 180 181 182 183 184 185 | ** ** The design of the _RANGE hint is aid b-tree implementations that try ** to prefetch content from remote machines - to provide those ** implementations with limits on what needs to be prefetched and thereby ** reduce network bandwidth. ** ** Note that BTREE_HINT_FLAGS with BTREE_BULKLOAD is the only hint used by | | | 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | ** ** The design of the _RANGE hint is aid b-tree implementations that try ** to prefetch content from remote machines - to provide those ** implementations with limits on what needs to be prefetched and thereby ** reduce network bandwidth. ** ** Note that BTREE_HINT_FLAGS with BTREE_BULKLOAD is the only hint used by ** standard SQLite. The other hints are provided for extensions that use ** the SQLite parser and code generator but substitute their own storage ** engine. */ #define BTREE_HINT_RANGE 0 /* Range constraints on queries */ /* ** Values that may be OR'd together to form the argument to the |
︙ | ︙ |
Changes to src/btreeInt.h.
︙ | ︙ | |||
670 671 672 673 674 675 676 | ** The ISAUTOVACUUM macro is used within balance_nonroot() to determine ** if the database supports auto-vacuum or not. Because it is used ** within an expression that is an argument to another macro ** (sqliteMallocRaw), it is not possible to use conditional compilation. ** So, this macro is defined instead. */ #ifndef SQLITE_OMIT_AUTOVACUUM | | | | 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 | ** The ISAUTOVACUUM macro is used within balance_nonroot() to determine ** if the database supports auto-vacuum or not. Because it is used ** within an expression that is an argument to another macro ** (sqliteMallocRaw), it is not possible to use conditional compilation. ** So, this macro is defined instead. */ #ifndef SQLITE_OMIT_AUTOVACUUM #define ISAUTOVACUUM(pBt) (pBt->autoVacuum) #else #define ISAUTOVACUUM(pBt) 0 #endif /* ** This structure is passed around through all the sanity checking routines ** in order to keep track of some global state information. ** |
︙ | ︙ |
Changes to src/build.c.
︙ | ︙ | |||
2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 | pCol->colFlags |= eType; assert( TF_HasVirtual==COLFLAG_VIRTUAL ); assert( TF_HasStored==COLFLAG_STORED ); pTab->tabFlags |= eType; if( pCol->colFlags & COLFLAG_PRIMKEY ){ makeColumnPartOfPrimaryKey(pParse, pCol); /* For the error message */ } sqlite3ColumnSetExpr(pParse, pTab, pCol, pExpr); pExpr = 0; goto generated_done; generated_error: sqlite3ErrorMsg(pParse, "error in generated column \"%s\"", pCol->zCnName); | > > > > > > > | 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 | pCol->colFlags |= eType; assert( TF_HasVirtual==COLFLAG_VIRTUAL ); assert( TF_HasStored==COLFLAG_STORED ); pTab->tabFlags |= eType; if( pCol->colFlags & COLFLAG_PRIMKEY ){ makeColumnPartOfPrimaryKey(pParse, pCol); /* For the error message */ } if( ALWAYS(pExpr) && pExpr->op==TK_ID ){ /* The value of a generated column needs to be a real expression, not ** just a reference to another column, in order for covering index ** optimizations to work correctly. So if the value is not an expression, ** turn it into one by adding a unary "+" operator. */ pExpr = sqlite3PExpr(pParse, TK_UPLUS, pExpr, 0); } sqlite3ColumnSetExpr(pParse, pTab, pCol, pExpr); pExpr = 0; goto generated_done; generated_error: sqlite3ErrorMsg(pParse, "error in generated column \"%s\"", pCol->zCnName); |
︙ | ︙ | |||
2136 2137 2138 2139 2140 2141 2142 | zStmt[k++] = '('; for(pCol=p->aCol, i=0; i<p->nCol; i++, pCol++){ static const char * const azType[] = { /* SQLITE_AFF_BLOB */ "", /* SQLITE_AFF_TEXT */ " TEXT", /* SQLITE_AFF_NUMERIC */ " NUM", /* SQLITE_AFF_INTEGER */ " INT", | | > > | > | 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 | zStmt[k++] = '('; for(pCol=p->aCol, i=0; i<p->nCol; i++, pCol++){ static const char * const azType[] = { /* SQLITE_AFF_BLOB */ "", /* SQLITE_AFF_TEXT */ " TEXT", /* SQLITE_AFF_NUMERIC */ " NUM", /* SQLITE_AFF_INTEGER */ " INT", /* SQLITE_AFF_REAL */ " REAL", /* SQLITE_AFF_FLEXNUM */ " NUM", }; int len; const char *zType; sqlite3_snprintf(n-k, &zStmt[k], zSep); k += sqlite3Strlen30(&zStmt[k]); zSep = zSep2; identPut(zStmt, &k, pCol->zCnName); assert( pCol->affinity-SQLITE_AFF_BLOB >= 0 ); assert( pCol->affinity-SQLITE_AFF_BLOB < ArraySize(azType) ); testcase( pCol->affinity==SQLITE_AFF_BLOB ); testcase( pCol->affinity==SQLITE_AFF_TEXT ); testcase( pCol->affinity==SQLITE_AFF_NUMERIC ); testcase( pCol->affinity==SQLITE_AFF_INTEGER ); testcase( pCol->affinity==SQLITE_AFF_REAL ); testcase( pCol->affinity==SQLITE_AFF_FLEXNUM ); zType = azType[pCol->affinity - SQLITE_AFF_BLOB]; len = sqlite3Strlen30(zType); assert( pCol->affinity==SQLITE_AFF_BLOB || pCol->affinity==SQLITE_AFF_FLEXNUM || pCol->affinity==sqlite3AffinityType(zType, 0) ); memcpy(&zStmt[k], zType, len); k += len; assert( k<=n ); } sqlite3_snprintf(n-k, &zStmt[k], "%s", zEnd); return zStmt; |
︙ | ︙ | |||
2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 | /* ** Mark all nodes of an expression as EP_Immutable, indicating that ** they should not be changed. Expressions attached to a table or ** index definition are tagged this way to help ensure that we do ** not pass them into code generator routines by mistake. */ static int markImmutableExprStep(Walker *pWalker, Expr *pExpr){ ExprSetVVAProperty(pExpr, EP_Immutable); return WRC_Continue; } static void markExprListImmutable(ExprList *pList){ if( pList ){ Walker w; memset(&w, 0, sizeof(w)); | > | 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 | /* ** Mark all nodes of an expression as EP_Immutable, indicating that ** they should not be changed. Expressions attached to a table or ** index definition are tagged this way to help ensure that we do ** not pass them into code generator routines by mistake. */ static int markImmutableExprStep(Walker *pWalker, Expr *pExpr){ (void)pWalker; ExprSetVVAProperty(pExpr, EP_Immutable); return WRC_Continue; } static void markExprListImmutable(ExprList *pList){ if( pList ){ Walker w; memset(&w, 0, sizeof(w)); |
︙ | ︙ | |||
3136 3137 3138 3139 3140 3141 3142 | */ sqlite3ColumnsFromExprList(pParse, pTable->pCheck, &pTable->nCol, &pTable->aCol); if( pParse->nErr==0 && pTable->nCol==pSel->pEList->nExpr ){ assert( db->mallocFailed==0 ); | < | | 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 | */ sqlite3ColumnsFromExprList(pParse, pTable->pCheck, &pTable->nCol, &pTable->aCol); if( pParse->nErr==0 && pTable->nCol==pSel->pEList->nExpr ){ assert( db->mallocFailed==0 ); sqlite3SubqueryColumnTypes(pParse, pTable, pSel, SQLITE_AFF_NONE); } }else{ /* CREATE VIEW name AS... without an argument list. Construct ** the column names from the SELECT statement that defines the view. */ assert( pTable->aCol==0 ); pTable->nCol = pSelTab->nCol; |
︙ | ︙ |
Changes to src/dbpage.c.
︙ | ︙ | |||
68 69 70 71 72 73 74 75 76 77 78 79 80 81 | void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVtab, char **pzErr ){ DbpageTable *pTab = 0; int rc = SQLITE_OK; sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); rc = sqlite3_declare_vtab(db, "CREATE TABLE x(pgno INTEGER PRIMARY KEY, data BLOB, schema HIDDEN)"); if( rc==SQLITE_OK ){ pTab = (DbpageTable *)sqlite3_malloc64(sizeof(DbpageTable)); if( pTab==0 ) rc = SQLITE_NOMEM_BKPT; | > > > > | 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVtab, char **pzErr ){ DbpageTable *pTab = 0; int rc = SQLITE_OK; (void)pAux; (void)argc; (void)argv; (void)pzErr; sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); rc = sqlite3_declare_vtab(db, "CREATE TABLE x(pgno INTEGER PRIMARY KEY, data BLOB, schema HIDDEN)"); if( rc==SQLITE_OK ){ pTab = (DbpageTable *)sqlite3_malloc64(sizeof(DbpageTable)); if( pTab==0 ) rc = SQLITE_NOMEM_BKPT; |
︙ | ︙ | |||
106 107 108 109 110 111 112 113 114 115 116 117 118 119 | ** 1 schema=main, pgno=?1 ** 2 schema=?1, full table scan ** 3 schema=?1, pgno=?2 */ static int dbpageBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ int i; int iPlan = 0; /* If there is a schema= constraint, it must be honored. Report a ** ridiculously large estimated cost if the schema= constraint is ** unavailable */ for(i=0; i<pIdxInfo->nConstraint; i++){ struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[i]; | > | 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | ** 1 schema=main, pgno=?1 ** 2 schema=?1, full table scan ** 3 schema=?1, pgno=?2 */ static int dbpageBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ int i; int iPlan = 0; (void)tab; /* If there is a schema= constraint, it must be honored. Report a ** ridiculously large estimated cost if the schema= constraint is ** unavailable */ for(i=0; i<pIdxInfo->nConstraint; i++){ struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[i]; |
︙ | ︙ | |||
221 222 223 224 225 226 227 228 229 230 231 232 233 234 | ){ DbpageCursor *pCsr = (DbpageCursor *)pCursor; DbpageTable *pTab = (DbpageTable *)pCursor->pVtab; int rc; sqlite3 *db = pTab->db; Btree *pBt; /* Default setting is no rows of result */ pCsr->pgno = 1; pCsr->mxPgno = 0; if( idxNum & 2 ){ const char *zSchema; assert( argc>=1 ); | > > | 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | ){ DbpageCursor *pCsr = (DbpageCursor *)pCursor; DbpageTable *pTab = (DbpageTable *)pCursor->pVtab; int rc; sqlite3 *db = pTab->db; Btree *pBt; (void)idxStr; /* Default setting is no rows of result */ pCsr->pgno = 1; pCsr->mxPgno = 0; if( idxNum & 2 ){ const char *zSchema; assert( argc>=1 ); |
︙ | ︙ | |||
316 317 318 319 320 321 322 323 324 325 326 327 328 329 | char *zErr = 0; const char *zSchema; int iDb; Btree *pBt; Pager *pPager; int szPage; if( pTab->db->flags & SQLITE_Defensive ){ zErr = "read-only"; goto update_fail; } if( argc==1 ){ zErr = "cannot delete"; goto update_fail; | > | 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 | char *zErr = 0; const char *zSchema; int iDb; Btree *pBt; Pager *pPager; int szPage; (void)pRowid; if( pTab->db->flags & SQLITE_Defensive ){ zErr = "read-only"; goto update_fail; } if( argc==1 ){ zErr = "cannot delete"; goto update_fail; |
︙ | ︙ |
Changes to src/dbstat.c.
︙ | ︙ | |||
159 160 161 162 163 164 165 166 167 168 169 170 171 172 | int argc, const char *const*argv, sqlite3_vtab **ppVtab, char **pzErr ){ StatTable *pTab = 0; int rc = SQLITE_OK; int iDb; if( argc>=4 ){ Token nm; sqlite3TokenInit(&nm, (char*)argv[3]); iDb = sqlite3FindDb(db, &nm); if( iDb<0 ){ *pzErr = sqlite3_mprintf("no such database: %s", argv[3]); | > | 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | int argc, const char *const*argv, sqlite3_vtab **ppVtab, char **pzErr ){ StatTable *pTab = 0; int rc = SQLITE_OK; int iDb; (void)pAux; if( argc>=4 ){ Token nm; sqlite3TokenInit(&nm, (char*)argv[3]); iDb = sqlite3FindDb(db, &nm); if( iDb<0 ){ *pzErr = sqlite3_mprintf("no such database: %s", argv[3]); |
︙ | ︙ | |||
212 213 214 215 216 217 218 219 220 221 222 223 224 225 | ** 0x08 Output should be ordered by name and path */ static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ int i; int iSchema = -1; int iName = -1; int iAgg = -1; /* Look for a valid schema=? constraint. If found, change the idxNum to ** 1 and request the value of that constraint be sent to xFilter. And ** lower the cost estimate to encourage the constrained version to be ** used. */ for(i=0; i<pIdxInfo->nConstraint; i++){ | > | 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | ** 0x08 Output should be ordered by name and path */ static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ int i; int iSchema = -1; int iName = -1; int iAgg = -1; (void)tab; /* Look for a valid schema=? constraint. If found, change the idxNum to ** 1 and request the value of that constraint be sent to xFilter. And ** lower the cost estimate to encourage the constrained version to be ** used. */ for(i=0; i<pIdxInfo->nConstraint; i++){ |
︙ | ︙ | |||
737 738 739 740 741 742 743 744 745 746 747 748 749 750 | StatCursor *pCsr = (StatCursor *)pCursor; StatTable *pTab = (StatTable*)(pCursor->pVtab); sqlite3_str *pSql; /* Query of btrees to analyze */ char *zSql; /* String value of pSql */ int iArg = 0; /* Count of argv[] parameters used so far */ int rc = SQLITE_OK; /* Result of this operation */ const char *zName = 0; /* Only provide analysis of this table */ statResetCsr(pCsr); sqlite3_finalize(pCsr->pStmt); pCsr->pStmt = 0; if( idxNum & 0x01 ){ /* schema=? constraint is present. Get its value */ const char *zDbase = (const char*)sqlite3_value_text(argv[iArg++]); | > > | 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 | StatCursor *pCsr = (StatCursor *)pCursor; StatTable *pTab = (StatTable*)(pCursor->pVtab); sqlite3_str *pSql; /* Query of btrees to analyze */ char *zSql; /* String value of pSql */ int iArg = 0; /* Count of argv[] parameters used so far */ int rc = SQLITE_OK; /* Result of this operation */ const char *zName = 0; /* Only provide analysis of this table */ (void)argc; (void)idxStr; statResetCsr(pCsr); sqlite3_finalize(pCsr->pStmt); pCsr->pStmt = 0; if( idxNum & 0x01 ){ /* schema=? constraint is present. Get its value */ const char *zDbase = (const char*)sqlite3_value_text(argv[iArg++]); |
︙ | ︙ | |||
820 821 822 823 824 825 826 | break; case 3: /* pagetype */ if( !pCsr->isAgg ){ sqlite3_result_text(ctx, pCsr->zPagetype, -1, SQLITE_STATIC); } break; case 4: /* ncell */ | | | | | | | 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 | break; case 3: /* pagetype */ if( !pCsr->isAgg ){ sqlite3_result_text(ctx, pCsr->zPagetype, -1, SQLITE_STATIC); } break; case 4: /* ncell */ sqlite3_result_int64(ctx, pCsr->nCell); break; case 5: /* payload */ sqlite3_result_int64(ctx, pCsr->nPayload); break; case 6: /* unused */ sqlite3_result_int64(ctx, pCsr->nUnused); break; case 7: /* mx_payload */ sqlite3_result_int64(ctx, pCsr->nMxPayload); break; case 8: /* pgoffset */ if( !pCsr->isAgg ){ sqlite3_result_int64(ctx, pCsr->iOffset); } break; case 9: /* pgsize */ sqlite3_result_int64(ctx, pCsr->szPage); break; case 10: { /* schema */ sqlite3 *db = sqlite3_context_db_handle(ctx); int iDb = pCsr->iDb; sqlite3_result_text(ctx, db->aDb[iDb].zDbSName, -1, SQLITE_STATIC); break; } |
︙ | ︙ |
Changes to src/expr.c.
︙ | ︙ | |||
40 41 42 43 44 45 46 | ** CREATE TABLE t1(a); ** SELECT * FROM t1 WHERE a; ** SELECT a AS b FROM t1 WHERE b; ** SELECT * FROM t1 WHERE (select a from t1); */ char sqlite3ExprAffinity(const Expr *pExpr){ int op; | < < < < < < < < > | | | | | | | | | | | | | | | | | | | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | ** CREATE TABLE t1(a); ** SELECT * FROM t1 WHERE a; ** SELECT a AS b FROM t1 WHERE b; ** SELECT * FROM t1 WHERE (select a from t1); */ char sqlite3ExprAffinity(const Expr *pExpr){ int op; op = pExpr->op; while( 1 /* exit-by-break */ ){ if( op==TK_COLUMN || (op==TK_AGG_COLUMN && pExpr->y.pTab!=0) ){ assert( ExprUseYTab(pExpr) ); assert( pExpr->y.pTab!=0 ); return sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); } if( op==TK_SELECT ){ assert( ExprUseXSelect(pExpr) ); assert( pExpr->x.pSelect!=0 ); assert( pExpr->x.pSelect->pEList!=0 ); assert( pExpr->x.pSelect->pEList->a[0].pExpr!=0 ); return sqlite3ExprAffinity(pExpr->x.pSelect->pEList->a[0].pExpr); } #ifndef SQLITE_OMIT_CAST if( op==TK_CAST ){ assert( !ExprHasProperty(pExpr, EP_IntValue) ); return sqlite3AffinityType(pExpr->u.zToken, 0); } #endif if( op==TK_SELECT_COLUMN ){ assert( pExpr->pLeft!=0 && ExprUseXSelect(pExpr->pLeft) ); assert( pExpr->iColumn < pExpr->iTable ); assert( pExpr->iTable==pExpr->pLeft->x.pSelect->pEList->nExpr ); return sqlite3ExprAffinity( pExpr->pLeft->x.pSelect->pEList->a[pExpr->iColumn].pExpr ); } if( op==TK_VECTOR ){ assert( ExprUseXList(pExpr) ); return sqlite3ExprAffinity(pExpr->x.pList->a[0].pExpr); } if( ExprHasProperty(pExpr, EP_Skip|EP_IfNullRow) ){ assert( pExpr->op==TK_COLLATE || pExpr->op==TK_IF_NULL_ROW || (pExpr->op==TK_REGISTER && pExpr->op2==TK_IF_NULL_ROW) ); pExpr = pExpr->pLeft; op = pExpr->op; continue; } if( op!=TK_REGISTER || (op = pExpr->op2)==TK_REGISTER ) break; } return pExpr->affExpr; } /* ** Make a guess at all the possible datatypes of the result that could ** be returned by an expression. Return a bitmask indicating the answer: ** ** 0x01 Numeric ** 0x02 Text ** 0x04 Blob ** ** If the expression must return NULL, then 0x00 is returned. */ int sqlite3ExprDataType(const Expr *pExpr){ while( pExpr ){ switch( pExpr->op ){ case TK_COLLATE: case TK_IF_NULL_ROW: case TK_UPLUS: { pExpr = pExpr->pLeft; break; } case TK_NULL: { pExpr = 0; break; } case TK_STRING: { return 0x02; } case TK_BLOB: { return 0x04; } case TK_CONCAT: { return 0x06; } case TK_VARIABLE: case TK_AGG_FUNCTION: case TK_FUNCTION: { return 0x07; } case TK_COLUMN: case TK_AGG_COLUMN: case TK_SELECT: case TK_CAST: case TK_SELECT_COLUMN: case TK_VECTOR: { int aff = sqlite3ExprAffinity(pExpr); if( aff>=SQLITE_AFF_NUMERIC ) return 0x05; if( aff==SQLITE_AFF_TEXT ) return 0x06; return 0x07; } case TK_CASE: { int res = 0; int ii; ExprList *pList = pExpr->x.pList; assert( ExprUseXList(pExpr) && pList!=0 ); assert( pList->nExpr > 0); for(ii=1; ii<pList->nExpr; ii+=2){ res |= sqlite3ExprDataType(pList->a[ii].pExpr); } if( pList->nExpr % 2 ){ res |= sqlite3ExprDataType(pList->a[pList->nExpr-1].pExpr); } return res; } default: { return 0x01; } } /* End of switch(op) */ } /* End of while(pExpr) */ return 0x00; } /* ** Set the collating sequence for expression pExpr to be the collating ** sequence named by pToken. Return a pointer to a new Expr node that ** implements the COLLATE operator. ** ** If a memory allocation error occurs, that fact is recorded in pParse->db |
︙ | ︙ | |||
169 170 171 172 173 174 175 | CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){ sqlite3 *db = pParse->db; CollSeq *pColl = 0; const Expr *p = pExpr; while( p ){ int op = p->op; if( op==TK_REGISTER ) op = p->op2; | > | > | 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 | CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){ sqlite3 *db = pParse->db; CollSeq *pColl = 0; const Expr *p = pExpr; while( p ){ int op = p->op; if( op==TK_REGISTER ) op = p->op2; if( (op==TK_AGG_COLUMN && p->y.pTab!=0) || op==TK_COLUMN || op==TK_TRIGGER ){ int j; assert( ExprUseYTab(p) ); assert( p->y.pTab!=0 ); if( (j = p->iColumn)>=0 ){ const char *zColl = sqlite3ColumnColl(&p->y.pTab->aCol[j]); pColl = sqlite3FindCollSeq(db, ENC(db), zColl, 0); } |
︙ | ︙ | |||
3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 | int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ int addrOnce = 0; /* Address of OP_Once at top of subroutine */ int rReg = 0; /* Register storing resulting */ Select *pSel; /* SELECT statement to encode */ SelectDest dest; /* How to deal with SELECT result */ int nReg; /* Registers to allocate */ Expr *pLimit; /* New limit expression */ Vdbe *v = pParse->pVdbe; assert( v!=0 ); if( pParse->nErr ) return 0; testcase( pExpr->op==TK_EXISTS ); testcase( pExpr->op==TK_SELECT ); assert( pExpr->op==TK_EXISTS || pExpr->op==TK_SELECT ); | > > > | 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 | int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ int addrOnce = 0; /* Address of OP_Once at top of subroutine */ int rReg = 0; /* Register storing resulting */ Select *pSel; /* SELECT statement to encode */ SelectDest dest; /* How to deal with SELECT result */ int nReg; /* Registers to allocate */ Expr *pLimit; /* New limit expression */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS int addrExplain; /* Address of OP_Explain instruction */ #endif Vdbe *v = pParse->pVdbe; assert( v!=0 ); if( pParse->nErr ) return 0; testcase( pExpr->op==TK_EXISTS ); testcase( pExpr->op==TK_SELECT ); assert( pExpr->op==TK_EXISTS || pExpr->op==TK_SELECT ); |
︙ | ︙ | |||
3303 3304 3305 3306 3307 3308 3309 | ** ** If this is an EXISTS, write an integer 0 (not exists) or 1 (exists) ** into a register and return that register number. ** ** In both cases, the query is augmented with "LIMIT 1". Any ** preexisting limit is discarded in place of the new LIMIT 1. */ | | > | 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 | ** ** If this is an EXISTS, write an integer 0 (not exists) or 1 (exists) ** into a register and return that register number. ** ** In both cases, the query is augmented with "LIMIT 1". Any ** preexisting limit is discarded in place of the new LIMIT 1. */ ExplainQueryPlan2(addrExplain, (pParse, 1, "%sSCALAR SUBQUERY %d", addrOnce?"":"CORRELATED ", pSel->selId)); sqlite3VdbeScanStatusCounters(v, addrExplain, addrExplain, -1); nReg = pExpr->op==TK_SELECT ? pSel->pEList->nExpr : 1; sqlite3SelectDestInit(&dest, 0, pParse->nMem+1); pParse->nMem += nReg; if( pExpr->op==TK_SELECT ){ dest.eDest = SRT_Mem; dest.iSdst = dest.iSDParm; dest.nSdst = nReg; |
︙ | ︙ | |||
3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 | return 0; } pExpr->iTable = rReg = dest.iSDParm; ExprSetVVAProperty(pExpr, EP_NoReduce); if( addrOnce ){ sqlite3VdbeJumpHere(v, addrOnce); } /* Subroutine return */ assert( ExprUseYSub(pExpr) ); assert( sqlite3VdbeGetOp(v,pExpr->y.sub.iAddr-1)->opcode==OP_BeginSubrtn || pParse->nErr ); sqlite3VdbeAddOp3(v, OP_Return, pExpr->y.sub.regReturn, pExpr->y.sub.iAddr, 1); | > | 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 | return 0; } pExpr->iTable = rReg = dest.iSDParm; ExprSetVVAProperty(pExpr, EP_NoReduce); if( addrOnce ){ sqlite3VdbeJumpHere(v, addrOnce); } sqlite3VdbeScanStatusRange(v, addrExplain, addrExplain, -1); /* Subroutine return */ assert( ExprUseYSub(pExpr) ); assert( sqlite3VdbeGetOp(v,pExpr->y.sub.iAddr-1)->opcode==OP_BeginSubrtn || pParse->nErr ); sqlite3VdbeAddOp3(v, OP_Return, pExpr->y.sub.regReturn, pExpr->y.sub.iAddr, 1); |
︙ | ︙ | |||
4033 4034 4035 4036 4037 4038 4039 | } #endif /* !defined(SQLITE_UNTESTABLE) */ } return target; } /* | | | | | | | 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 | } #endif /* !defined(SQLITE_UNTESTABLE) */ } return target; } /* ** Check to see if pExpr is one of the indexed expressions on pParse->pIdxEpr. ** If it is, then resolve the expression by reading from the index and ** return the register into which the value has been read. If pExpr is ** not an indexed expression, then return negative. */ static SQLITE_NOINLINE int sqlite3IndexedExprLookup( Parse *pParse, /* The parsing context */ Expr *pExpr, /* The expression to potentially bypass */ int target /* Where to store the result of the expression */ ){ IndexedExpr *p; Vdbe *v; for(p=pParse->pIdxEpr; p; p=p->pIENext){ int iDataCur = p->iDataCur; if( iDataCur<0 ) continue; if( pParse->iSelfTab ){ if( p->iDataCur!=pParse->iSelfTab-1 ) continue; iDataCur = -1; } if( sqlite3ExprCompare(0, pExpr, p->pExpr, iDataCur)!=0 ) continue; v = pParse->pVdbe; assert( v!=0 ); if( p->bMaybeNullRow ){ /* If the index is on a NULL row due to an outer join, then we ** cannot extract the value from the index. The value must be ** computed using the original expression. */ int addr = sqlite3VdbeCurrentAddr(v); sqlite3VdbeAddOp3(v, OP_IfNullRow, p->iIdxCur, addr+3, target); VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_Column, p->iIdxCur, p->iIdxCol, target); VdbeComment((v, "%s expr-column %d", p->zIdxName, p->iIdxCol)); sqlite3VdbeGoto(v, 0); p = pParse->pIdxEpr; pParse->pIdxEpr = 0; sqlite3ExprCode(pParse, pExpr, target); pParse->pIdxEpr = p; sqlite3VdbeJumpHere(v, addr+2); }else{ sqlite3VdbeAddOp3(v, OP_Column, p->iIdxCur, p->iIdxCol, target); VdbeComment((v, "%s expr-column %d", p->zIdxName, p->iIdxCol)); } return target; } |
︙ | ︙ | |||
4107 4108 4109 4110 4111 4112 4113 | assert( target>0 && target<=pParse->nMem ); assert( v!=0 ); expr_code_doover: if( pExpr==0 ){ op = TK_NULL; | | < | > > | | > > > > > | | 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 | assert( target>0 && target<=pParse->nMem ); assert( v!=0 ); expr_code_doover: if( pExpr==0 ){ op = TK_NULL; }else if( pParse->pIdxEpr!=0 && !ExprHasProperty(pExpr, EP_Leaf) && (r1 = sqlite3IndexedExprLookup(pParse, pExpr, target))>=0 ){ return r1; }else{ assert( !ExprHasVVAProperty(pExpr,EP_Immutable) ); op = pExpr->op; } switch( op ){ case TK_AGG_COLUMN: { AggInfo *pAggInfo = pExpr->pAggInfo; struct AggInfo_col *pCol; assert( pAggInfo!=0 ); assert( pExpr->iAgg>=0 && pExpr->iAgg<pAggInfo->nColumn ); pCol = &pAggInfo->aCol[pExpr->iAgg]; if( !pAggInfo->directMode ){ return AggInfoColumnReg(pAggInfo, pExpr->iAgg); }else if( pAggInfo->useSortingIdx ){ Table *pTab = pCol->pTab; sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab, pCol->iSorterColumn, target); if( pTab==0 ){ /* No comment added */ }else if( pCol->iColumn<0 ){ VdbeComment((v,"%s.rowid",pTab->zName)); }else{ VdbeComment((v,"%s.%s", pTab->zName, pTab->aCol[pCol->iColumn].zCnName)); if( pTab->aCol[pCol->iColumn].affinity==SQLITE_AFF_REAL ){ sqlite3VdbeAddOp1(v, OP_RealAffinity, target); } } return target; }else if( pExpr->y.pTab==0 ){ /* This case happens when the argument to an aggregate function ** is rewritten by aggregateConvertIndexedExprRefToColumn() */ sqlite3VdbeAddOp3(v, OP_Column, pExpr->iTable, pExpr->iColumn, target); return target; } /* Otherwise, fall thru into the TK_COLUMN case */ /* no break */ deliberate_fall_through } case TK_COLUMN: { int iTab = pExpr->iTable; int iReg; if( ExprHasProperty(pExpr, EP_FixedCol) ){ /* This COLUMN expression is really a constant due to WHERE clause ** constraints, and that constant is coded by the pExpr->pLeft ** expresssion. However, make sure the constant has the correct ** datatype by applying the Affinity of the table column to the ** constant. */ int aff; iReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft,target); assert( ExprUseYTab(pExpr) ); assert( pExpr->y.pTab!=0 ); aff = sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); if( aff>SQLITE_AFF_BLOB ){ static const char zAff[] = "B\000C\000D\000E\000F"; assert( SQLITE_AFF_BLOB=='A' ); assert( SQLITE_AFF_TEXT=='B' ); sqlite3VdbeAddOp4(v, OP_Affinity, iReg, 1, 0, &zAff[(aff-'B')*2], P4_STATIC); } return iReg; } |
︙ | ︙ | |||
4437 4438 4439 4440 4441 4442 4443 | if( pInfo==0 || NEVER(pExpr->iAgg<0) || NEVER(pExpr->iAgg>=pInfo->nFunc) ){ assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3ErrorMsg(pParse, "misuse of aggregate: %#T()", pExpr); }else{ | | | 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 | if( pInfo==0 || NEVER(pExpr->iAgg<0) || NEVER(pExpr->iAgg>=pInfo->nFunc) ){ assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3ErrorMsg(pParse, "misuse of aggregate: %#T()", pExpr); }else{ return AggInfoFuncReg(pInfo, pExpr->iAgg); } break; } case TK_FUNCTION: { ExprList *pFarg; /* List of function arguments */ int nFarg; /* Number of function arguments */ FuncDef *pDef; /* The function definition object */ |
︙ | ︙ | |||
4726 4727 4728 4729 4730 4731 4732 | case TK_IF_NULL_ROW: { int addrINR; u8 okConstFactor = pParse->okConstFactor; AggInfo *pAggInfo = pExpr->pAggInfo; if( pAggInfo ){ assert( pExpr->iAgg>=0 && pExpr->iAgg<pAggInfo->nColumn ); if( !pAggInfo->directMode ){ | | | 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 | case TK_IF_NULL_ROW: { int addrINR; u8 okConstFactor = pParse->okConstFactor; AggInfo *pAggInfo = pExpr->pAggInfo; if( pAggInfo ){ assert( pExpr->iAgg>=0 && pExpr->iAgg<pAggInfo->nColumn ); if( !pAggInfo->directMode ){ inReg = AggInfoColumnReg(pAggInfo, pExpr->iAgg); break; } if( pExpr->pAggInfo->useSortingIdx ){ sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab, pAggInfo->aCol[pExpr->iAgg].iSorterColumn, target); inReg = target; |
︙ | ︙ | |||
6157 6158 6159 6160 6161 6162 6163 | ** This is a Walker expression node callback. ** ** For Expr nodes that contain pAggInfo pointers, make sure the AggInfo ** object that is referenced does not refer directly to the Expr. If ** it does, make a copy. This is done because the pExpr argument is ** subject to change. ** | | < < | < | 6243 6244 6245 6246 6247 6248 6249 6250 6251 6252 6253 6254 6255 6256 6257 6258 6259 6260 6261 6262 6263 6264 6265 6266 6267 6268 | ** This is a Walker expression node callback. ** ** For Expr nodes that contain pAggInfo pointers, make sure the AggInfo ** object that is referenced does not refer directly to the Expr. If ** it does, make a copy. This is done because the pExpr argument is ** subject to change. ** ** The copy is scheduled for deletion using the sqlite3ExprDeferredDelete() ** which builds on the sqlite3ParserAddCleanup() mechanism. */ static int agginfoPersistExprCb(Walker *pWalker, Expr *pExpr){ if( ALWAYS(!ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced)) && pExpr->pAggInfo!=0 ){ AggInfo *pAggInfo = pExpr->pAggInfo; int iAgg = pExpr->iAgg; Parse *pParse = pWalker->pParse; sqlite3 *db = pParse->db; if( pExpr->op!=TK_AGG_FUNCTION ){ assert( iAgg>=0 && iAgg<pAggInfo->nColumn ); if( pAggInfo->aCol[iAgg].pCExpr==pExpr ){ pExpr = sqlite3ExprDup(db, pExpr, 0); if( pExpr ){ pAggInfo->aCol[iAgg].pCExpr = pExpr; sqlite3ExprDeferredDelete(pParse, pExpr); } |
︙ | ︙ | |||
6237 6238 6239 6240 6241 6242 6243 6244 6245 6246 6247 6248 6249 6250 6251 6252 6253 6254 6255 6256 6257 6258 6259 6260 6261 6262 6263 6264 6265 6266 6267 6268 6269 | pInfo->aFunc, sizeof(pInfo->aFunc[0]), &pInfo->nFunc, &i ); return i; } /* ** This is the xExprCallback for a tree walker. It is used to ** implement sqlite3ExprAnalyzeAggregates(). See sqlite3ExprAnalyzeAggregates ** for additional information. */ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ int i; NameContext *pNC = pWalker->u.pNC; Parse *pParse = pNC->pParse; SrcList *pSrcList = pNC->pSrcList; AggInfo *pAggInfo = pNC->uNC.pAggInfo; assert( pNC->ncFlags & NC_UAggInfo ); switch( pExpr->op ){ case TK_IF_NULL_ROW: case TK_AGG_COLUMN: case TK_COLUMN: { testcase( pExpr->op==TK_AGG_COLUMN ); testcase( pExpr->op==TK_COLUMN ); testcase( pExpr->op==TK_IF_NULL_ROW ); /* Check to see if the column is in one of the tables in the FROM ** clause of the aggregate query */ if( ALWAYS(pSrcList!=0) ){ SrcItem *pItem = pSrcList->a; for(i=0; i<pSrcList->nSrc; i++, pItem++){ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < < < < < < < < < < < < < < < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 6320 6321 6322 6323 6324 6325 6326 6327 6328 6329 6330 6331 6332 6333 6334 6335 6336 6337 6338 6339 6340 6341 6342 6343 6344 6345 6346 6347 6348 6349 6350 6351 6352 6353 6354 6355 6356 6357 6358 6359 6360 6361 6362 6363 6364 6365 6366 6367 6368 6369 6370 6371 6372 6373 6374 6375 6376 6377 6378 6379 6380 6381 6382 6383 6384 6385 6386 6387 6388 6389 6390 6391 6392 6393 6394 6395 6396 6397 6398 6399 6400 6401 6402 6403 6404 6405 6406 6407 6408 6409 6410 6411 6412 6413 6414 6415 6416 6417 6418 6419 6420 6421 6422 6423 6424 6425 6426 6427 6428 6429 6430 6431 6432 6433 6434 6435 6436 6437 6438 6439 6440 6441 6442 6443 6444 6445 6446 6447 6448 6449 6450 6451 6452 6453 6454 6455 6456 6457 6458 6459 | pInfo->aFunc, sizeof(pInfo->aFunc[0]), &pInfo->nFunc, &i ); return i; } /* ** Search the AggInfo object for an aCol[] entry that has iTable and iColumn. ** Return the index in aCol[] of the entry that describes that column. ** ** If no prior entry is found, create a new one and return -1. The ** new column will have an idex of pAggInfo->nColumn-1. */ static void findOrCreateAggInfoColumn( Parse *pParse, /* Parsing context */ AggInfo *pAggInfo, /* The AggInfo object to search and/or modify */ Expr *pExpr /* Expr describing the column to find or insert */ ){ struct AggInfo_col *pCol; int k; assert( pAggInfo->iFirstReg==0 ); pCol = pAggInfo->aCol; for(k=0; k<pAggInfo->nColumn; k++, pCol++){ if( pCol->iTable==pExpr->iTable && pCol->iColumn==pExpr->iColumn && pExpr->op!=TK_IF_NULL_ROW ){ goto fix_up_expr; } } k = addAggInfoColumn(pParse->db, pAggInfo); if( k<0 ){ /* OOM on resize */ assert( pParse->db->mallocFailed ); return; } pCol = &pAggInfo->aCol[k]; assert( ExprUseYTab(pExpr) ); pCol->pTab = pExpr->y.pTab; pCol->iTable = pExpr->iTable; pCol->iColumn = pExpr->iColumn; pCol->iSorterColumn = -1; pCol->pCExpr = pExpr; if( pAggInfo->pGroupBy && pExpr->op!=TK_IF_NULL_ROW ){ int j, n; ExprList *pGB = pAggInfo->pGroupBy; struct ExprList_item *pTerm = pGB->a; n = pGB->nExpr; for(j=0; j<n; j++, pTerm++){ Expr *pE = pTerm->pExpr; if( pE->op==TK_COLUMN && pE->iTable==pExpr->iTable && pE->iColumn==pExpr->iColumn ){ pCol->iSorterColumn = j; break; } } } if( pCol->iSorterColumn<0 ){ pCol->iSorterColumn = pAggInfo->nSortingColumn++; } fix_up_expr: ExprSetVVAProperty(pExpr, EP_NoReduce); assert( pExpr->pAggInfo==0 || pExpr->pAggInfo==pAggInfo ); pExpr->pAggInfo = pAggInfo; if( pExpr->op==TK_COLUMN ){ pExpr->op = TK_AGG_COLUMN; } pExpr->iAgg = (i16)k; } /* ** This is the xExprCallback for a tree walker. It is used to ** implement sqlite3ExprAnalyzeAggregates(). See sqlite3ExprAnalyzeAggregates ** for additional information. */ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ int i; NameContext *pNC = pWalker->u.pNC; Parse *pParse = pNC->pParse; SrcList *pSrcList = pNC->pSrcList; AggInfo *pAggInfo = pNC->uNC.pAggInfo; assert( pNC->ncFlags & NC_UAggInfo ); assert( pAggInfo->iFirstReg==0 ); switch( pExpr->op ){ default: { IndexedExpr *pIEpr; Expr tmp; assert( pParse->iSelfTab==0 ); if( (pNC->ncFlags & NC_InAggFunc)==0 ) break; if( pParse->pIdxEpr==0 ) break; for(pIEpr=pParse->pIdxEpr; pIEpr; pIEpr=pIEpr->pIENext){ int iDataCur = pIEpr->iDataCur; if( iDataCur<0 ) continue; if( sqlite3ExprCompare(0, pExpr, pIEpr->pExpr, iDataCur)==0 ) break; } if( pIEpr==0 ) break; if( NEVER(!ExprUseYTab(pExpr)) ) break; if( pExpr->pAggInfo!=0 ) break; /* Already resolved by outer context */ /* If we reach this point, it means that expression pExpr can be ** translated into a reference to an index column as described by ** pIEpr. */ memset(&tmp, 0, sizeof(tmp)); tmp.op = TK_AGG_COLUMN; tmp.iTable = pIEpr->iIdxCur; tmp.iColumn = pIEpr->iIdxCol; findOrCreateAggInfoColumn(pParse, pAggInfo, &tmp); pAggInfo->aCol[tmp.iAgg].pCExpr = pExpr; pExpr->pAggInfo = pAggInfo; pExpr->iAgg = tmp.iAgg; return WRC_Prune; } case TK_IF_NULL_ROW: case TK_AGG_COLUMN: case TK_COLUMN: { testcase( pExpr->op==TK_AGG_COLUMN ); testcase( pExpr->op==TK_COLUMN ); testcase( pExpr->op==TK_IF_NULL_ROW ); /* Check to see if the column is in one of the tables in the FROM ** clause of the aggregate query */ if( ALWAYS(pSrcList!=0) ){ SrcItem *pItem = pSrcList->a; for(i=0; i<pSrcList->nSrc; i++, pItem++){ assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); if( pExpr->iTable==pItem->iCursor ){ findOrCreateAggInfoColumn(pParse, pAggInfo, pExpr); break; } /* endif pExpr->iTable==pItem->iCursor */ } /* end loop over pSrcList */ } return WRC_Prune; } case TK_AGG_FUNCTION: { |
︙ | ︙ | |||
6353 6354 6355 6356 6357 6358 6359 | */ u8 enc = ENC(pParse->db); i = addAggInfoFunc(pParse->db, pAggInfo); if( i>=0 ){ assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); pItem = &pAggInfo->aFunc[i]; pItem->pFExpr = pExpr; | < | 6475 6476 6477 6478 6479 6480 6481 6482 6483 6484 6485 6486 6487 6488 | */ u8 enc = ENC(pParse->db); i = addAggInfoFunc(pParse->db, pAggInfo); if( i>=0 ){ assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); pItem = &pAggInfo->aFunc[i]; pItem->pFExpr = pExpr; assert( ExprUseUToken(pExpr) ); pItem->pFunc = sqlite3FindFunction(pParse->db, pExpr->u.zToken, pExpr->x.pList ? pExpr->x.pList->nExpr : 0, enc, 0); if( pExpr->flags & EP_Distinct ){ pItem->iDistinct = pParse->nTab++; }else{ |
︙ | ︙ |
Changes to src/func.c.
︙ | ︙ | |||
1078 1079 1080 1081 1082 1083 1084 | } case SQLITE_INTEGER: { sqlite3_str_appendf(pStr, "%lld", sqlite3_value_int64(pValue)); break; } case SQLITE_BLOB: { char const *zBlob = sqlite3_value_blob(pValue); | | | 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 | } case SQLITE_INTEGER: { sqlite3_str_appendf(pStr, "%lld", sqlite3_value_int64(pValue)); break; } case SQLITE_BLOB: { char const *zBlob = sqlite3_value_blob(pValue); i64 nBlob = sqlite3_value_bytes(pValue); assert( zBlob==sqlite3_value_blob(pValue) ); /* No encoding change */ sqlite3StrAccumEnlarge(pStr, nBlob*2 + 4); if( pStr->accError==0 ){ char *zText = pStr->zText; int i; for(i=0; i<nBlob; i++){ zText[(i*2)+2] = hexdigits[(zBlob[i]>>4)&0x0F]; |
︙ | ︙ | |||
1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 | */ static void unknownFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ /* no-op */ } #endif /*SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION*/ /* IMP: R-25361-16150 This function is omitted from SQLite by default. It ** is only available if the SQLITE_SOUNDEX compile-time option is used ** when SQLite is built. | > > > | 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 | */ static void unknownFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ /* no-op */ (void)context; (void)argc; (void)argv; } #endif /*SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION*/ /* IMP: R-25361-16150 This function is omitted from SQLite by default. It ** is only available if the SQLITE_SOUNDEX compile-time option is used ** when SQLite is built. |
︙ | ︙ | |||
2102 2103 2104 2105 2106 2107 2108 | if( x<=0.0 ) return; break; default: return; } ans = log(x)/b; }else{ | < < | < | > | 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 | if( x<=0.0 ) return; break; default: return; } ans = log(x)/b; }else{ switch( SQLITE_PTR_TO_INT(sqlite3_user_data(context)) ){ case 1: ans = log10(x); break; case 2: ans = log2(x); break; default: ans = log(x); break; } } sqlite3_result_double(context, ans); } /* |
︙ | ︙ | |||
2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 | */ static void piFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ assert( argc==0 ); sqlite3_result_double(context, M_PI); } #endif /* SQLITE_ENABLE_MATH_FUNCTIONS */ /* ** Implementation of sign(X) function. | > | 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 | */ static void piFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ assert( argc==0 ); (void)argv; sqlite3_result_double(context, M_PI); } #endif /* SQLITE_ENABLE_MATH_FUNCTIONS */ /* ** Implementation of sign(X) function. |
︙ | ︙ |
Changes to src/hwtime.h.
︙ | ︙ | |||
44 45 46 47 48 49 50 | } #endif #elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__x86_64__)) __inline__ sqlite_uint64 sqlite3Hwtime(void){ | | | | | 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | } #endif #elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__x86_64__)) __inline__ sqlite_uint64 sqlite3Hwtime(void){ unsigned int lo, hi; __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); return (sqlite_uint64)hi << 32 | lo; } #elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__ppc__)) __inline__ sqlite_uint64 sqlite3Hwtime(void){ unsigned long long retval; unsigned long junk; |
︙ | ︙ |
Changes to src/insert.c.
︙ | ︙ | |||
1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 | case OE_Abort: sqlite3MayAbort(pParse); /* no break */ deliberate_fall_through case OE_Rollback: case OE_Fail: { char *zMsg = sqlite3MPrintf(db, "%s.%s", pTab->zName, pCol->zCnName); sqlite3VdbeAddOp3(v, OP_HaltIfNull, SQLITE_CONSTRAINT_NOTNULL, onError, iReg); sqlite3VdbeAppendP4(v, zMsg, P4_DYNAMIC); sqlite3VdbeChangeP5(v, P5_ConstraintNotNull); VdbeCoverage(v); break; } | > | 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 | case OE_Abort: sqlite3MayAbort(pParse); /* no break */ deliberate_fall_through case OE_Rollback: case OE_Fail: { char *zMsg = sqlite3MPrintf(db, "%s.%s", pTab->zName, pCol->zCnName); testcase( zMsg==0 && db->mallocFailed==0 ); sqlite3VdbeAddOp3(v, OP_HaltIfNull, SQLITE_CONSTRAINT_NOTNULL, onError, iReg); sqlite3VdbeAppendP4(v, zMsg, P4_DYNAMIC); sqlite3VdbeChangeP5(v, P5_ConstraintNotNull); VdbeCoverage(v); break; } |
︙ | ︙ |
Changes to src/main.c.
︙ | ︙ | |||
1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 | case SQLITE_RANGE: zName = "SQLITE_RANGE"; break; case SQLITE_NOTADB: zName = "SQLITE_NOTADB"; break; case SQLITE_ROW: zName = "SQLITE_ROW"; break; case SQLITE_NOTICE: zName = "SQLITE_NOTICE"; break; case SQLITE_NOTICE_RECOVER_WAL: zName = "SQLITE_NOTICE_RECOVER_WAL";break; case SQLITE_NOTICE_RECOVER_ROLLBACK: zName = "SQLITE_NOTICE_RECOVER_ROLLBACK"; break; case SQLITE_WARNING: zName = "SQLITE_WARNING"; break; case SQLITE_WARNING_AUTOINDEX: zName = "SQLITE_WARNING_AUTOINDEX"; break; case SQLITE_DONE: zName = "SQLITE_DONE"; break; } } if( zName==0 ){ static char zBuf[50]; | > | 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 | case SQLITE_RANGE: zName = "SQLITE_RANGE"; break; case SQLITE_NOTADB: zName = "SQLITE_NOTADB"; break; case SQLITE_ROW: zName = "SQLITE_ROW"; break; case SQLITE_NOTICE: zName = "SQLITE_NOTICE"; break; case SQLITE_NOTICE_RECOVER_WAL: zName = "SQLITE_NOTICE_RECOVER_WAL";break; case SQLITE_NOTICE_RECOVER_ROLLBACK: zName = "SQLITE_NOTICE_RECOVER_ROLLBACK"; break; case SQLITE_NOTICE_RBU: zName = "SQLITE_NOTICE_RBU"; break; case SQLITE_WARNING: zName = "SQLITE_WARNING"; break; case SQLITE_WARNING_AUTOINDEX: zName = "SQLITE_WARNING_AUTOINDEX"; break; case SQLITE_DONE: zName = "SQLITE_DONE"; break; } } if( zName==0 ){ static char zBuf[50]; |
︙ | ︙ |
Changes to src/malloc.c.
︙ | ︙ | |||
275 276 277 278 279 280 281 | ** ** This is not a limit on the total amount of memory used. This is ** a limit on the size parameter to sqlite3_malloc() and sqlite3_realloc(). ** ** The upper bound is slightly less than 2GiB: 0x7ffffeff == 2,147,483,391 ** This provides a 256-byte safety margin for defense against 32-bit ** signed integer overflow bugs when computing memory allocation sizes. | | | 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 | ** ** This is not a limit on the total amount of memory used. This is ** a limit on the size parameter to sqlite3_malloc() and sqlite3_realloc(). ** ** The upper bound is slightly less than 2GiB: 0x7ffffeff == 2,147,483,391 ** This provides a 256-byte safety margin for defense against 32-bit ** signed integer overflow bugs when computing memory allocation sizes. ** Paranoid applications might want to reduce the maximum allocation size ** further for an even larger safety margin. 0x3fffffff or 0x0fffffff ** or even smaller would be reasonable upper bounds on the size of a memory ** allocations for most applications. */ #ifndef SQLITE_MAX_ALLOCATION_SIZE # define SQLITE_MAX_ALLOCATION_SIZE 2147483391 #endif |
︙ | ︙ | |||
789 790 791 792 793 794 795 796 797 | /* ** The text between zStart and zEnd represents a phrase within a larger ** SQL statement. Make a copy of this phrase in space obtained form ** sqlite3DbMalloc(). Omit leading and trailing whitespace. */ char *sqlite3DbSpanDup(sqlite3 *db, const char *zStart, const char *zEnd){ int n; while( sqlite3Isspace(zStart[0]) ) zStart++; n = (int)(zEnd - zStart); | > > > > > | | 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 | /* ** The text between zStart and zEnd represents a phrase within a larger ** SQL statement. Make a copy of this phrase in space obtained form ** sqlite3DbMalloc(). Omit leading and trailing whitespace. */ char *sqlite3DbSpanDup(sqlite3 *db, const char *zStart, const char *zEnd){ int n; #ifdef SQLITE_DEBUG /* Because of the way the parser works, the span is guaranteed to contain ** at least one non-space character */ for(n=0; sqlite3Isspace(zStart[n]); n++){ assert( &zStart[n]<zEnd ); } #endif while( sqlite3Isspace(zStart[0]) ) zStart++; n = (int)(zEnd - zStart); while( sqlite3Isspace(zStart[n-1]) ) n--; return sqlite3DbStrNDup(db, zStart, n); } /* ** Free any prior content in *pz and replace it with a copy of zNew. */ void sqlite3SetString(char **pz, sqlite3 *db, const char *zNew){ |
︙ | ︙ |
Changes to src/mem5.c.
︙ | ︙ | |||
420 421 422 423 424 425 426 | */ static int memsys5Roundup(int n){ int iFullSz; if( n<=mem5.szAtom*2 ){ if( n<=mem5.szAtom ) return mem5.szAtom; return mem5.szAtom*2; } | < | < < < | | 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 | */ static int memsys5Roundup(int n){ int iFullSz; if( n<=mem5.szAtom*2 ){ if( n<=mem5.szAtom ) return mem5.szAtom; return mem5.szAtom*2; } if( n>0x40000000 ) return 0; for(iFullSz=mem5.szAtom*8; iFullSz<n; iFullSz *= 4); if( (iFullSz/2)>=n ) return iFullSz/2; return iFullSz; } /* ** Return the ceiling of the logarithm base 2 of iValue. ** ** Examples: memsys5Log(1) -> 0 |
︙ | ︙ |
Changes to src/memdb.c.
︙ | ︙ | |||
895 896 897 898 899 900 901 902 903 904 905 906 907 908 | sqlite3_finalize(pStmt); if( pData && (mFlags & SQLITE_DESERIALIZE_FREEONCLOSE)!=0 ){ sqlite3_free(pData); } sqlite3_mutex_leave(db->mutex); return rc; } /* ** This routine is called when the extension is loaded. ** Register the new VFS. */ int sqlite3MemdbInit(void){ sqlite3_vfs *pLower = sqlite3_vfs_find(0); | > > > > > > > | 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 | sqlite3_finalize(pStmt); if( pData && (mFlags & SQLITE_DESERIALIZE_FREEONCLOSE)!=0 ){ sqlite3_free(pData); } sqlite3_mutex_leave(db->mutex); return rc; } /* ** Return true if the VFS is the memvfs. */ int sqlite3IsMemdb(const sqlite3_vfs *pVfs){ return pVfs==&memdb_vfs; } /* ** This routine is called when the extension is loaded. ** Register the new VFS. */ int sqlite3MemdbInit(void){ sqlite3_vfs *pLower = sqlite3_vfs_find(0); |
︙ | ︙ |
Changes to src/os_common.h.
︙ | ︙ | |||
31 32 33 34 35 36 37 | /* ** Macros for performance tracing. Normally turned off. Only works ** on i486 hardware. */ #ifdef SQLITE_PERFORMANCE_TRACE | < < < < < < | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | /* ** Macros for performance tracing. Normally turned off. Only works ** on i486 hardware. */ #ifdef SQLITE_PERFORMANCE_TRACE static sqlite_uint64 g_start; static sqlite_uint64 g_elapsed; #define TIMER_START g_start=sqlite3Hwtime() #define TIMER_END g_elapsed=sqlite3Hwtime()-g_start #define TIMER_ELAPSED g_elapsed #else #define TIMER_START |
︙ | ︙ |
Changes to src/os_kv.c.
︙ | ︙ | |||
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | sqlite3_file base; /* IO methods */ const char *zClass; /* Storage class */ int isJournal; /* True if this is a journal file */ unsigned int nJrnl; /* Space allocated for aJrnl[] */ char *aJrnl; /* Journal content */ int szPage; /* Last known page size */ sqlite3_int64 szDb; /* Database file size. -1 means unknown */ }; /* ** Methods for KVVfsFile */ static int kvvfsClose(sqlite3_file*); static int kvvfsReadDb(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); static int kvvfsReadJrnl(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); | > > | 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | sqlite3_file base; /* IO methods */ const char *zClass; /* Storage class */ int isJournal; /* True if this is a journal file */ unsigned int nJrnl; /* Space allocated for aJrnl[] */ char *aJrnl; /* Journal content */ int szPage; /* Last known page size */ sqlite3_int64 szDb; /* Database file size. -1 means unknown */ char *aData; /* Buffer to hold page data */ }; #define SQLITE_KVOS_SZ 133073 /* ** Methods for KVVfsFile */ static int kvvfsClose(sqlite3_file*); static int kvvfsReadDb(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); static int kvvfsReadJrnl(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); |
︙ | ︙ | |||
489 490 491 492 493 494 495 496 497 498 499 500 501 502 | */ static int kvvfsClose(sqlite3_file *pProtoFile){ KVVfsFile *pFile = (KVVfsFile *)pProtoFile; SQLITE_KV_LOG(("xClose %s %s\n", pFile->zClass, pFile->isJournal ? "journal" : "db")); sqlite3_free(pFile->aJrnl); return SQLITE_OK; } /* ** Read from the -journal file. */ static int kvvfsReadJrnl( | > | 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 | */ static int kvvfsClose(sqlite3_file *pProtoFile){ KVVfsFile *pFile = (KVVfsFile *)pProtoFile; SQLITE_KV_LOG(("xClose %s %s\n", pFile->zClass, pFile->isJournal ? "journal" : "db")); sqlite3_free(pFile->aJrnl); sqlite3_free(pFile->aData); return SQLITE_OK; } /* ** Read from the -journal file. */ static int kvvfsReadJrnl( |
︙ | ︙ | |||
537 538 539 540 541 542 543 | int iAmt, sqlite_int64 iOfst ){ KVVfsFile *pFile = (KVVfsFile*)pProtoFile; unsigned int pgno; int got, n; char zKey[30]; | | | > | | 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 | int iAmt, sqlite_int64 iOfst ){ KVVfsFile *pFile = (KVVfsFile*)pProtoFile; unsigned int pgno; int got, n; char zKey[30]; char *aData = pFile->aData; assert( iOfst>=0 ); assert( iAmt>=0 ); SQLITE_KV_LOG(("xRead('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); if( iOfst+iAmt>=512 ){ if( (iOfst % iAmt)!=0 ){ return SQLITE_IOERR_READ; } if( (iAmt & (iAmt-1))!=0 || iAmt<512 || iAmt>65536 ){ return SQLITE_IOERR_READ; } pFile->szPage = iAmt; pgno = 1 + iOfst/iAmt; }else{ pgno = 1; } sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); got = sqlite3KvvfsMethods.xRead(pFile->zClass, zKey, aData, SQLITE_KVOS_SZ-1); if( got<0 ){ n = 0; }else{ aData[got] = 0; if( iOfst+iAmt<512 ){ int k = iOfst+iAmt; aData[k*2] = 0; n = kvvfsDecode(aData, &aData[2000], SQLITE_KVOS_SZ-2000); if( n>=iOfst+iAmt ){ memcpy(zBuf, &aData[2000+iOfst], iAmt); n = iAmt; }else{ n = 0; } }else{ |
︙ | ︙ | |||
621 622 623 624 625 626 627 | const void *zBuf, int iAmt, sqlite_int64 iOfst ){ KVVfsFile *pFile = (KVVfsFile*)pProtoFile; unsigned int pgno; char zKey[30]; | | | 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 | const void *zBuf, int iAmt, sqlite_int64 iOfst ){ KVVfsFile *pFile = (KVVfsFile*)pProtoFile; unsigned int pgno; char zKey[30]; char *aData = pFile->aData; SQLITE_KV_LOG(("xWrite('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); assert( iAmt>=512 && iAmt<=65536 ); assert( (iAmt & (iAmt-1))==0 ); assert( pFile->szPage<0 || pFile->szPage==iAmt ); pFile->szPage = iAmt; pgno = 1 + iOfst/iAmt; sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); |
︙ | ︙ | |||
829 830 831 832 833 834 835 836 837 838 839 840 841 842 | }else{ return SQLITE_CANTOPEN; } if( zName[0]=='s' ){ pFile->zClass = "session"; }else{ pFile->zClass = "local"; } pFile->aJrnl = 0; pFile->nJrnl = 0; pFile->szPage = -1; pFile->szDb = -1; return SQLITE_OK; } | > > > > | 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 | }else{ return SQLITE_CANTOPEN; } if( zName[0]=='s' ){ pFile->zClass = "session"; }else{ pFile->zClass = "local"; } pFile->aData = sqlite3_malloc64(SQLITE_KVOS_SZ); if( pFile->aData==0 ){ return SQLITE_NOMEM; } pFile->aJrnl = 0; pFile->nJrnl = 0; pFile->szPage = -1; pFile->szDb = -1; return SQLITE_OK; } |
︙ | ︙ |
Changes to src/os_unix.c.
︙ | ︙ | |||
6675 6676 6677 6678 6679 6680 6681 | ** The argument is the number of microseconds we want to sleep. ** The return value is the number of microseconds of sleep actually ** requested from the underlying operating system, a number which ** might be greater than or equal to the argument, but not less ** than the argument. */ static int unixSleep(sqlite3_vfs *NotUsed, int microseconds){ | | | 6675 6676 6677 6678 6679 6680 6681 6682 6683 6684 6685 6686 6687 6688 6689 | ** The argument is the number of microseconds we want to sleep. ** The return value is the number of microseconds of sleep actually ** requested from the underlying operating system, a number which ** might be greater than or equal to the argument, but not less ** than the argument. */ static int unixSleep(sqlite3_vfs *NotUsed, int microseconds){ #if OS_VXWORKS || _POSIX_C_SOURCE >= 199309L struct timespec sp; sp.tv_sec = microseconds / 1000000; sp.tv_nsec = (microseconds % 1000000) * 1000; nanosleep(&sp, NULL); UNUSED_PARAMETER(NotUsed); return microseconds; |
︙ | ︙ |
Changes to src/pager.c.
︙ | ︙ | |||
7005 7006 7007 7008 7009 7010 7011 | ** participate in shared-cache. ** ** The return value to this routine is always safe to use with ** sqlite3_uri_parameter() and sqlite3_filename_database() and friends. */ const char *sqlite3PagerFilename(const Pager *pPager, int nullIfMemDb){ static const char zFake[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; | > > > | > | 7005 7006 7007 7008 7009 7010 7011 7012 7013 7014 7015 7016 7017 7018 7019 7020 7021 7022 7023 | ** participate in shared-cache. ** ** The return value to this routine is always safe to use with ** sqlite3_uri_parameter() and sqlite3_filename_database() and friends. */ const char *sqlite3PagerFilename(const Pager *pPager, int nullIfMemDb){ static const char zFake[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; if( nullIfMemDb && (pPager->memDb || sqlite3IsMemdb(pPager->pVfs)) ){ return &zFake[4]; }else{ return pPager->zFilename; } } /* ** Return the VFS structure for the pager. */ sqlite3_vfs *sqlite3PagerVfs(Pager *pPager){ return pPager->pVfs; |
︙ | ︙ |
Changes to src/prepare.c.
︙ | ︙ | |||
516 517 518 519 520 521 522 523 | /* Read the schema cookie from the database. If it does not match the ** value stored as part of the in-memory schema representation, ** set Parse.rc to SQLITE_SCHEMA. */ sqlite3BtreeGetMeta(pBt, BTREE_SCHEMA_VERSION, (u32 *)&cookie); assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( cookie!=db->aDb[iDb].pSchema->schema_cookie ){ sqlite3ResetOneSchema(db, iDb); | > < | 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 | /* Read the schema cookie from the database. If it does not match the ** value stored as part of the in-memory schema representation, ** set Parse.rc to SQLITE_SCHEMA. */ sqlite3BtreeGetMeta(pBt, BTREE_SCHEMA_VERSION, (u32 *)&cookie); assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( cookie!=db->aDb[iDb].pSchema->schema_cookie ){ if( DbHasProperty(db, iDb, DB_SchemaLoaded) ) pParse->rc = SQLITE_SCHEMA; sqlite3ResetOneSchema(db, iDb); } /* Close the transaction, if one was opened. */ if( openedTransaction ){ sqlite3BtreeCommit(pBt); } } |
︙ | ︙ |
Changes to src/printf.c.
︙ | ︙ | |||
732 733 734 735 736 737 738 739 740 741 742 743 | buf[1] = 0x80 + (u8)((ch>>12) & 0x3f); buf[2] = 0x80 + (u8)((ch>>6) & 0x3f); buf[3] = 0x80 + (u8)(ch & 0x3f); length = 4; } } if( precision>1 ){ width -= precision-1; if( width>1 && !flag_leftjustify ){ sqlite3_str_appendchar(pAccum, width-1, ' '); width = 0; } | > > > | > > > > > > > | > > > | 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 | buf[1] = 0x80 + (u8)((ch>>12) & 0x3f); buf[2] = 0x80 + (u8)((ch>>6) & 0x3f); buf[3] = 0x80 + (u8)(ch & 0x3f); length = 4; } } if( precision>1 ){ i64 nPrior = 1; width -= precision-1; if( width>1 && !flag_leftjustify ){ sqlite3_str_appendchar(pAccum, width-1, ' '); width = 0; } sqlite3_str_append(pAccum, buf, length); precision--; while( precision > 1 ){ i64 nCopyBytes; if( nPrior > precision-1 ) nPrior = precision - 1; nCopyBytes = length*nPrior; if( nCopyBytes + pAccum->nChar >= pAccum->nAlloc ){ sqlite3StrAccumEnlarge(pAccum, nCopyBytes); } if( pAccum->accError ) break; sqlite3_str_append(pAccum, &pAccum->zText[pAccum->nChar-nCopyBytes], nCopyBytes); precision -= nPrior; nPrior *= 2; } } bufpt = buf; flag_altform2 = 1; goto adjust_width_for_utf8; case etSTRING: case etDYNSTRING: |
︙ | ︙ | |||
966 967 968 969 970 971 972 | /* ** Enlarge the memory allocation on a StrAccum object so that it is ** able to accept at least N more bytes of text. ** ** Return the number of bytes of text that StrAccum is able to accept ** after the attempted enlargement. The value returned might be zero. */ | | | | < | 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 | /* ** Enlarge the memory allocation on a StrAccum object so that it is ** able to accept at least N more bytes of text. ** ** Return the number of bytes of text that StrAccum is able to accept ** after the attempted enlargement. The value returned might be zero. */ int sqlite3StrAccumEnlarge(StrAccum *p, i64 N){ char *zNew; assert( p->nChar+N >= p->nAlloc ); /* Only called if really needed */ if( p->accError ){ testcase(p->accError==SQLITE_TOOBIG); testcase(p->accError==SQLITE_NOMEM); return 0; } if( p->mxAlloc==0 ){ sqlite3StrAccumSetError(p, SQLITE_TOOBIG); return p->nAlloc - p->nChar - 1; }else{ char *zOld = isMalloced(p) ? p->zText : 0; i64 szNew = p->nChar + N + 1; if( szNew+p->nChar<=p->mxAlloc ){ /* Force exponential buffer size growth as long as it does not overflow, ** to avoid having to call this routine too often */ szNew += p->nChar; } if( szNew > p->mxAlloc ){ sqlite3_str_reset(p); |
︙ | ︙ | |||
1010 1011 1012 1013 1014 1015 1016 | p->printfFlags |= SQLITE_PRINTF_MALLOCED; }else{ sqlite3_str_reset(p); sqlite3StrAccumSetError(p, SQLITE_NOMEM); return 0; } } | > | | 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 | p->printfFlags |= SQLITE_PRINTF_MALLOCED; }else{ sqlite3_str_reset(p); sqlite3StrAccumSetError(p, SQLITE_NOMEM); return 0; } } assert( N>=0 && N<=0x7fffffff ); return (int)N; } /* ** Append N copies of character c to the given string buffer. */ void sqlite3_str_appendchar(sqlite3_str *p, int N, char c){ testcase( p->nChar + (i64)N > 0x7fffffff ); |
︙ | ︙ |
Changes to src/select.c.
︙ | ︙ | |||
61 62 63 64 65 66 67 68 69 70 71 72 73 74 | struct DeferredCsr { Table *pTab; /* Table definition */ int iCsr; /* Cursor number for table */ int nKey; /* Number of PK columns for table pTab (>=1) */ } aDefer[4]; #endif struct RowLoadInfo *pDeferredRowLoad; /* Deferred row loading info or NULL */ }; #define SORTFLAG_UseSorter 0x01 /* Use SorterOpen instead of OpenEphemeral */ /* ** Delete all the content of a Select structure. Deallocate the structure ** itself depending on the value of bFree ** | > > > > | 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | struct DeferredCsr { Table *pTab; /* Table definition */ int iCsr; /* Cursor number for table */ int nKey; /* Number of PK columns for table pTab (>=1) */ } aDefer[4]; #endif struct RowLoadInfo *pDeferredRowLoad; /* Deferred row loading info or NULL */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS int addrPush; /* First instruction to push data into sorter */ int addrPushEnd; /* Last instruction that pushes data into sorter */ #endif }; #define SORTFLAG_UseSorter 0x01 /* Use SorterOpen instead of OpenEphemeral */ /* ** Delete all the content of a Select structure. Deallocate the structure ** itself depending on the value of bFree ** |
︙ | ︙ | |||
716 717 718 719 720 721 722 723 724 725 726 727 728 729 | ** the SQLITE_ENABLE_SORTER_REFERENCE optimization, or due to the ** SQLITE_ECEL_OMITREF optimization, or due to the ** SortCtx.pDeferredRowLoad optimiation. In any of these cases ** regOrigData is 0 to prevent this routine from trying to copy ** values that might not yet exist. */ assert( nData==1 || regData==regOrigData || regOrigData==0 ); if( nPrefixReg ){ assert( nPrefixReg==nExpr+bSeq ); regBase = regData - nPrefixReg; }else{ regBase = pParse->nMem + 1; pParse->nMem += nBase; | > > > > | 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 | ** the SQLITE_ENABLE_SORTER_REFERENCE optimization, or due to the ** SQLITE_ECEL_OMITREF optimization, or due to the ** SortCtx.pDeferredRowLoad optimiation. In any of these cases ** regOrigData is 0 to prevent this routine from trying to copy ** values that might not yet exist. */ assert( nData==1 || regData==regOrigData || regOrigData==0 ); #ifdef SQLITE_ENABLE_STMT_SCANSTATUS pSort->addrPush = sqlite3VdbeCurrentAddr(v); #endif if( nPrefixReg ){ assert( nPrefixReg==nExpr+bSeq ); regBase = regData - nPrefixReg; }else{ regBase = pParse->nMem + 1; pParse->nMem += nBase; |
︙ | ︙ | |||
817 818 819 820 821 822 823 824 825 826 827 828 829 830 | } sqlite3VdbeAddOp4Int(v, op, pSort->iECursor, regRecord, regBase+nOBSat, nBase-nOBSat); if( iSkip ){ sqlite3VdbeChangeP2(v, iSkip, pSort->labelOBLopt ? pSort->labelOBLopt : sqlite3VdbeCurrentAddr(v)); } } /* ** Add code to implement the OFFSET */ static void codeOffset( Vdbe *v, /* Generate code into this VM */ | > > > | 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 | } sqlite3VdbeAddOp4Int(v, op, pSort->iECursor, regRecord, regBase+nOBSat, nBase-nOBSat); if( iSkip ){ sqlite3VdbeChangeP2(v, iSkip, pSort->labelOBLopt ? pSort->labelOBLopt : sqlite3VdbeCurrentAddr(v)); } #ifdef SQLITE_ENABLE_STMT_SCANSTATUS pSort->addrPushEnd = sqlite3VdbeCurrentAddr(v)-1; #endif } /* ** Add code to implement the OFFSET */ static void codeOffset( Vdbe *v, /* Generate code into this VM */ |
︙ | ︙ | |||
1283 1284 1285 1286 1287 1288 1289 | case SRT_EphemTab: { int r1 = sqlite3GetTempRange(pParse, nPrefixReg+1); testcase( eDest==SRT_Table ); testcase( eDest==SRT_EphemTab ); testcase( eDest==SRT_Fifo ); testcase( eDest==SRT_DistFifo ); sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1+nPrefixReg); | < < < | 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 | case SRT_EphemTab: { int r1 = sqlite3GetTempRange(pParse, nPrefixReg+1); testcase( eDest==SRT_Table ); testcase( eDest==SRT_EphemTab ); testcase( eDest==SRT_Fifo ); testcase( eDest==SRT_DistFifo ); sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1+nPrefixReg); #ifndef SQLITE_OMIT_CTE if( eDest==SRT_DistFifo ){ /* If the destination is DistFifo, then cursor (iParm+1) is open ** on an ephemeral index. If the current row is already present ** in the index, do not write it to the output. If not, add the ** current row to the index and proceed with writing it to the ** output table as well. */ |
︙ | ︙ | |||
1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 | int iCol; int nKey; /* Number of key columns in sorter record */ int iSortTab; /* Sorter cursor to read from */ int i; int bSeq; /* True if sorter record includes seq. no. */ int nRefKey = 0; struct ExprList_item *aOutEx = p->pEList->a; assert( addrBreak<0 ); if( pSort->labelBkOut ){ sqlite3VdbeAddOp2(v, OP_Gosub, pSort->regReturn, pSort->labelBkOut); sqlite3VdbeGoto(v, addrBreak); sqlite3VdbeResolveLabel(v, pSort->labelBkOut); } | > > > > > > > > > > | 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 | int iCol; int nKey; /* Number of key columns in sorter record */ int iSortTab; /* Sorter cursor to read from */ int i; int bSeq; /* True if sorter record includes seq. no. */ int nRefKey = 0; struct ExprList_item *aOutEx = p->pEList->a; #ifdef SQLITE_ENABLE_STMT_SCANSTATUS int addrExplain; /* Address of OP_Explain instruction */ #endif ExplainQueryPlan2(addrExplain, (pParse, 0, "USE TEMP B-TREE FOR %sORDER BY", pSort->nOBSat>0?"RIGHT PART OF ":"") ); sqlite3VdbeScanStatusRange(v, addrExplain,pSort->addrPush,pSort->addrPushEnd); sqlite3VdbeScanStatusCounters(v, addrExplain, addrExplain, pSort->addrPush); assert( addrBreak<0 ); if( pSort->labelBkOut ){ sqlite3VdbeAddOp2(v, OP_Gosub, pSort->regReturn, pSort->labelBkOut); sqlite3VdbeGoto(v, addrBreak); sqlite3VdbeResolveLabel(v, pSort->labelBkOut); } |
︙ | ︙ | |||
1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 | }else{ iRead = iCol--; } sqlite3VdbeAddOp3(v, OP_Column, iSortTab, iRead, regRow+i); VdbeComment((v, "%s", aOutEx[i].zEName)); } } switch( eDest ){ case SRT_Table: case SRT_EphemTab: { sqlite3VdbeAddOp3(v, OP_Column, iSortTab, nKey+bSeq, regRow); sqlite3VdbeAddOp2(v, OP_NewRowid, iParm, regRowid); sqlite3VdbeAddOp3(v, OP_Insert, iParm, regRow, regRowid); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); | > | 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 | }else{ iRead = iCol--; } sqlite3VdbeAddOp3(v, OP_Column, iSortTab, iRead, regRow+i); VdbeComment((v, "%s", aOutEx[i].zEName)); } } sqlite3VdbeScanStatusRange(v, addrExplain, addrExplain, -1); switch( eDest ){ case SRT_Table: case SRT_EphemTab: { sqlite3VdbeAddOp3(v, OP_Column, iSortTab, nKey+bSeq, regRow); sqlite3VdbeAddOp2(v, OP_NewRowid, iParm, regRowid); sqlite3VdbeAddOp3(v, OP_Insert, iParm, regRow, regRowid); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); |
︙ | ︙ | |||
1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 | */ sqlite3VdbeResolveLabel(v, addrContinue); if( pSort->sortFlags & SORTFLAG_UseSorter ){ sqlite3VdbeAddOp2(v, OP_SorterNext, iTab, addr); VdbeCoverage(v); }else{ sqlite3VdbeAddOp2(v, OP_Next, iTab, addr); VdbeCoverage(v); } if( pSort->regReturn ) sqlite3VdbeAddOp1(v, OP_Return, pSort->regReturn); sqlite3VdbeResolveLabel(v, addrBreak); } /* ** Return a pointer to a string containing the 'declaration type' of the ** expression pExpr. The string may be treated as static by the caller. | > | 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 | */ sqlite3VdbeResolveLabel(v, addrContinue); if( pSort->sortFlags & SORTFLAG_UseSorter ){ sqlite3VdbeAddOp2(v, OP_SorterNext, iTab, addr); VdbeCoverage(v); }else{ sqlite3VdbeAddOp2(v, OP_Next, iTab, addr); VdbeCoverage(v); } sqlite3VdbeScanStatusRange(v, addrExplain, sqlite3VdbeCurrentAddr(v)-1, -1); if( pSort->regReturn ) sqlite3VdbeAddOp1(v, OP_Return, pSort->regReturn); sqlite3VdbeResolveLabel(v, addrBreak); } /* ** Return a pointer to a string containing the 'declaration type' of the ** expression pExpr. The string may be treated as static by the caller. |
︙ | ︙ | |||
1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 | ** the SQLITE_ENABLE_COLUMN_METADATA compile-time option is used. */ #ifdef SQLITE_ENABLE_COLUMN_METADATA # define columnType(A,B,C,D,E) columnTypeImpl(A,B,C,D,E) #else /* if !defined(SQLITE_ENABLE_COLUMN_METADATA) */ # define columnType(A,B,C,D,E) columnTypeImpl(A,B) #endif static const char *columnTypeImpl( NameContext *pNC, #ifndef SQLITE_ENABLE_COLUMN_METADATA Expr *pExpr #else Expr *pExpr, const char **pzOrigDb, | > | 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 | ** the SQLITE_ENABLE_COLUMN_METADATA compile-time option is used. */ #ifdef SQLITE_ENABLE_COLUMN_METADATA # define columnType(A,B,C,D,E) columnTypeImpl(A,B,C,D,E) #else /* if !defined(SQLITE_ENABLE_COLUMN_METADATA) */ # define columnType(A,B,C,D,E) columnTypeImpl(A,B) #endif #ifndef SQLITE_OMIT_DECLTYPE static const char *columnTypeImpl( NameContext *pNC, #ifndef SQLITE_ENABLE_COLUMN_METADATA Expr *pExpr #else Expr *pExpr, const char **pzOrigDb, |
︙ | ︙ | |||
1876 1877 1878 1879 1880 1881 1882 | /* The expression is a column. Locate the table the column is being ** extracted from in NameContext.pSrcList. This table may be real ** database table or a subquery. */ Table *pTab = 0; /* Table structure column is extracted from */ Select *pS = 0; /* Select the column is extracted from */ int iCol = pExpr->iColumn; /* Index of column in pTab */ | | | | 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 | /* The expression is a column. Locate the table the column is being ** extracted from in NameContext.pSrcList. This table may be real ** database table or a subquery. */ Table *pTab = 0; /* Table structure column is extracted from */ Select *pS = 0; /* Select the column is extracted from */ int iCol = pExpr->iColumn; /* Index of column in pTab */ while( ALWAYS(pNC) && !pTab ){ SrcList *pTabList = pNC->pSrcList; for(j=0;j<pTabList->nSrc && pTabList->a[j].iCursor!=pExpr->iTable;j++); if( j<pTabList->nSrc ){ pTab = pTabList->a[j].pTab; pS = pTabList->a[j].pSelect; }else{ pNC = pNC->pNext; } } if( NEVER(pTab==0) ){ /* At one time, code such as "SELECT new.x" within a trigger would ** cause this condition to run. Since then, we have restructured how ** trigger code is generated and so this condition is no longer ** possible. However, it can still be true for statements like ** the following: ** ** CREATE TABLE t1(col INTEGER); |
︙ | ︙ | |||
1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 | *pzOrigDb = zOrigDb; *pzOrigTab = zOrigTab; *pzOrigCol = zOrigCol; } #endif return zType; } /* ** Generate code that will tell the VDBE the declaration types of columns ** in the result set. */ static void generateColumnTypes( Parse *pParse, /* Parser context */ | > | 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 | *pzOrigDb = zOrigDb; *pzOrigTab = zOrigTab; *pzOrigCol = zOrigCol; } #endif return zType; } #endif /* !defined(SQLITE_OMIT_DECLTYPE) */ /* ** Generate code that will tell the VDBE the declaration types of columns ** in the result set. */ static void generateColumnTypes( Parse *pParse, /* Parser context */ |
︙ | ︙ | |||
2087 2088 2089 2090 2091 2092 2093 | return; } #endif if( pParse->colNamesSet ) return; /* Column names are determined by the left-most term of a compound select */ while( pSelect->pPrior ) pSelect = pSelect->pPrior; | | | 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 | return; } #endif if( pParse->colNamesSet ) return; /* Column names are determined by the left-most term of a compound select */ while( pSelect->pPrior ) pSelect = pSelect->pPrior; TREETRACE(0x80,pParse,pSelect,("generating column names\n")); pTabList = pSelect->pSrc; pEList = pSelect->pEList; assert( v!=0 ); assert( pTabList!=0 ); pParse->colNamesSet = 1; fullName = (db->flags & SQLITE_FullColNames)!=0; srcName = (db->flags & SQLITE_ShortColNames)!=0 || fullName; |
︙ | ︙ | |||
2263 2264 2265 2266 2267 2268 2269 | *pnCol = 0; return SQLITE_NOMEM_BKPT; } return SQLITE_OK; } /* | | | | | | < > | | | | | | | < | > < | | < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < | 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 | *pnCol = 0; return SQLITE_NOMEM_BKPT; } return SQLITE_OK; } /* ** pTab is a transient Table object that represents a subquery of some ** kind (maybe a parenthesized subquery in the FROM clause of a larger ** query, or a VIEW, or a CTE). This routine computes type information ** for that Table object based on the Select object that implements the ** subquery. For the purposes of this routine, "type infomation" means: ** ** * The datatype name, as it might appear in a CREATE TABLE statement ** * Which collating sequence to use for the column ** * The affinity of the column */ void sqlite3SubqueryColumnTypes( Parse *pParse, /* Parsing contexts */ Table *pTab, /* Add column type information to this table */ Select *pSelect, /* SELECT used to determine types and collations */ char aff /* Default affinity. */ ){ sqlite3 *db = pParse->db; Column *pCol; CollSeq *pColl; int i,j; Expr *p; struct ExprList_item *a; assert( pSelect!=0 ); assert( (pSelect->selFlags & SF_Resolved)!=0 ); assert( pTab->nCol==pSelect->pEList->nExpr || db->mallocFailed ); assert( aff==SQLITE_AFF_NONE || aff==SQLITE_AFF_BLOB ); if( db->mallocFailed ) return; while( pSelect->pPrior ) pSelect = pSelect->pPrior; a = pSelect->pEList->a; for(i=0, pCol=pTab->aCol; i<pTab->nCol; i++, pCol++){ const char *zType; i64 n; pTab->tabFlags |= (pCol->colFlags & COLFLAG_NOINSERT); p = a[i].pExpr; /* pCol->szEst = ... // Column size est for SELECT tables never used */ pCol->affinity = sqlite3ExprAffinity(p); if( pCol->affinity<=SQLITE_AFF_NONE ){ pCol->affinity = aff; }else if( pCol->affinity>=SQLITE_AFF_NUMERIC && p->op==TK_CAST ){ pCol->affinity = SQLITE_AFF_FLEXNUM; } if( pCol->affinity>=SQLITE_AFF_TEXT && pSelect->pNext ){ int m = 0; Select *pS2; for(m=0, pS2=pSelect->pNext; pS2; pS2=pS2->pNext){ m |= sqlite3ExprDataType(pS2->pEList->a[i].pExpr); } if( pCol->affinity==SQLITE_AFF_TEXT && (m&0x01)!=0 ){ pCol->affinity = SQLITE_AFF_BLOB; }else if( pCol->affinity>=SQLITE_AFF_NUMERIC && (m&0x02)!=0 ){ pCol->affinity = SQLITE_AFF_BLOB; } } if( pCol->affinity==SQLITE_AFF_NUMERIC || pCol->affinity==SQLITE_AFF_FLEXNUM ){ zType = "NUM"; }else{ zType = 0; for(j=1; j<SQLITE_N_STDTYPE; j++){ if( sqlite3StdTypeAffinity[j]==pCol->affinity ){ zType = sqlite3StdType[j]; break; } } } if( zType ){ i64 m = sqlite3Strlen30(zType); n = sqlite3Strlen30(pCol->zCnName); pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+m+2); if( pCol->zCnName ){ memcpy(&pCol->zCnName[n+1], zType, m+1); pCol->colFlags |= COLFLAG_HASTYPE; }else{ testcase( pCol->colFlags & COLFLAG_HASTYPE ); pCol->colFlags &= ~(COLFLAG_HASTYPE|COLFLAG_HASCOLL); } } pColl = sqlite3ExprCollSeq(pParse, p); if( pColl ){ assert( pTab->pIndex==0 ); sqlite3ColumnSetColl(db, pCol, pColl->zName); } } pTab->szTabRow = 1; /* Any non-zero value works */ |
︙ | ︙ | |||
2348 2349 2350 2351 2352 2353 2354 | if( pTab==0 ){ return 0; } pTab->nTabRef = 1; pTab->zName = 0; pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); sqlite3ColumnsFromExprList(pParse, pSelect->pEList, &pTab->nCol, &pTab->aCol); | | | 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 | if( pTab==0 ){ return 0; } pTab->nTabRef = 1; pTab->zName = 0; pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); sqlite3ColumnsFromExprList(pParse, pSelect->pEList, &pTab->nCol, &pTab->aCol); sqlite3SubqueryColumnTypes(pParse, pTab, pSelect, aff); pTab->iPKey = -1; if( db->mallocFailed ){ sqlite3DeleteTable(db, pTab); return 0; } return pTab; } |
︙ | ︙ | |||
2873 2874 2875 2876 2877 2878 2879 | case TK_ALL: { int addr = 0; int nLimit = 0; /* Initialize to suppress harmless compiler warning */ assert( !pPrior->pLimit ); pPrior->iLimit = p->iLimit; pPrior->iOffset = p->iOffset; pPrior->pLimit = p->pLimit; | | | | 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 | case TK_ALL: { int addr = 0; int nLimit = 0; /* Initialize to suppress harmless compiler warning */ assert( !pPrior->pLimit ); pPrior->iLimit = p->iLimit; pPrior->iOffset = p->iOffset; pPrior->pLimit = p->pLimit; TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL left...\n")); rc = sqlite3Select(pParse, pPrior, &dest); pPrior->pLimit = 0; if( rc ){ goto multi_select_end; } p->pPrior = 0; p->iLimit = pPrior->iLimit; p->iOffset = pPrior->iOffset; if( p->iLimit ){ addr = sqlite3VdbeAddOp1(v, OP_IfNot, p->iLimit); VdbeCoverage(v); VdbeComment((v, "Jump ahead if LIMIT reached")); if( p->iOffset ){ sqlite3VdbeAddOp3(v, OP_OffsetLimit, p->iLimit, p->iOffset+1, p->iOffset); } } ExplainQueryPlan((pParse, 1, "UNION ALL")); TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL right...\n")); rc = sqlite3Select(pParse, p, &dest); testcase( rc!=SQLITE_OK ); pDelete = p->pPrior; p->pPrior = pPrior; p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); if( p->pLimit && sqlite3ExprIsInteger(p->pLimit->pLeft, &nLimit) |
︙ | ︙ | |||
2944 2945 2946 2947 2948 2949 2950 | } /* Code the SELECT statements to our left */ assert( !pPrior->pOrderBy ); sqlite3SelectDestInit(&uniondest, priorOp, unionTab); | | | | 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 | } /* Code the SELECT statements to our left */ assert( !pPrior->pOrderBy ); sqlite3SelectDestInit(&uniondest, priorOp, unionTab); TREETRACE(0x200, pParse, p, ("multiSelect EXCEPT/UNION left...\n")); rc = sqlite3Select(pParse, pPrior, &uniondest); if( rc ){ goto multi_select_end; } /* Code the current SELECT statement */ if( p->op==TK_EXCEPT ){ op = SRT_Except; }else{ assert( p->op==TK_UNION ); op = SRT_Union; } p->pPrior = 0; pLimit = p->pLimit; p->pLimit = 0; uniondest.eDest = op; ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE", sqlite3SelectOpName(p->op))); TREETRACE(0x200, pParse, p, ("multiSelect EXCEPT/UNION right...\n")); rc = sqlite3Select(pParse, p, &uniondest); testcase( rc!=SQLITE_OK ); assert( p->pOrderBy==0 ); pDelete = p->pPrior; p->pPrior = pPrior; p->pOrderBy = 0; if( p->op==TK_UNION ){ |
︙ | ︙ | |||
3025 3026 3027 3028 3029 3030 3031 | p->addrOpenEphm[0] = addr; findRightmost(p)->selFlags |= SF_UsesEphemeral; assert( p->pEList ); /* Code the SELECTs to our left into temporary table "tab1". */ sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1); | | | | 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 | p->addrOpenEphm[0] = addr; findRightmost(p)->selFlags |= SF_UsesEphemeral; assert( p->pEList ); /* Code the SELECTs to our left into temporary table "tab1". */ sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1); TREETRACE(0x400, pParse, p, ("multiSelect INTERSECT left...\n")); rc = sqlite3Select(pParse, pPrior, &intersectdest); if( rc ){ goto multi_select_end; } /* Code the current SELECT into temporary table "tab2" */ addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab2, 0); assert( p->addrOpenEphm[1] == -1 ); p->addrOpenEphm[1] = addr; p->pPrior = 0; pLimit = p->pLimit; p->pLimit = 0; intersectdest.iSDParm = tab2; ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE", sqlite3SelectOpName(p->op))); TREETRACE(0x400, pParse, p, ("multiSelect INTERSECT right...\n")); rc = sqlite3Select(pParse, p, &intersectdest); testcase( rc!=SQLITE_OK ); pDelete = p->pPrior; p->pPrior = pPrior; if( p->nSelectRow>pPrior->nSelectRow ){ p->nSelectRow = pPrior->nSelectRow; } |
︙ | ︙ | |||
4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 | */ static ExprList *findLeftmostExprlist(Select *pSel){ while( pSel->pPrior ){ pSel = pSel->pPrior; } return pSel->pEList; } #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) /* ** This routine attempts to flatten subqueries as a performance optimization. ** This routine returns 1 if it makes changes and 0 if no flattening occurs. ** ** To understand the concept of flattening, consider the following | > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 | */ static ExprList *findLeftmostExprlist(Select *pSel){ while( pSel->pPrior ){ pSel = pSel->pPrior; } return pSel->pEList; } /* ** Return true if any of the result-set columns in the compound query ** have incompatible affinities on one or more arms of the compound. */ static int compoundHasDifferentAffinities(Select *p){ int ii; ExprList *pList; assert( p!=0 ); assert( p->pEList!=0 ); assert( p->pPrior!=0 ); pList = p->pEList; for(ii=0; ii<pList->nExpr; ii++){ char aff; Select *pSub1; assert( pList->a[ii].pExpr!=0 ); aff = sqlite3ExprAffinity(pList->a[ii].pExpr); for(pSub1=p->pPrior; pSub1; pSub1=pSub1->pPrior){ assert( pSub1->pEList!=0 ); assert( pSub1->pEList->nExpr>ii ); assert( pSub1->pEList->a[ii].pExpr!=0 ); if( sqlite3ExprAffinity(pSub1->pEList->a[ii].pExpr)!=aff ){ return 1; } } } return 0; } #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) /* ** This routine attempts to flatten subqueries as a performance optimization. ** This routine returns 1 if it makes changes and 0 if no flattening occurs. ** ** To understand the concept of flattening, consider the following |
︙ | ︙ | |||
4149 4150 4151 4152 4153 4154 4155 | ** (17d2) DISTINCT ** (17e) the subquery may not contain window functions, and ** (17f) the subquery must not be the RHS of a LEFT JOIN. ** (17g) either the subquery is the first element of the outer ** query or there are no RIGHT or FULL JOINs in any arm ** of the subquery. (This is a duplicate of condition (27b).) ** (17h) The corresponding result set expressions in all arms of the | | > | 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 | ** (17d2) DISTINCT ** (17e) the subquery may not contain window functions, and ** (17f) the subquery must not be the RHS of a LEFT JOIN. ** (17g) either the subquery is the first element of the outer ** query or there are no RIGHT or FULL JOINs in any arm ** of the subquery. (This is a duplicate of condition (27b).) ** (17h) The corresponding result set expressions in all arms of the ** compound must have the same affinity. (See restriction (9) ** on the push-down optimization.) ** ** The parent and sub-query may contain WHERE clauses. Subject to ** rules (11), (13) and (14), they may also contain ORDER BY, ** LIMIT and OFFSET clauses. The subquery cannot use any compound ** operator other than UNION ALL because all the other compound ** operators have an implied DISTINCT which is disallowed by ** restriction (4). |
︙ | ︙ | |||
4368 4369 4370 4371 4372 4373 4374 | } } /* Restriction (23) */ if( (p->selFlags & SF_Recursive) ) return 0; /* Restriction (17h) */ | < < < < < < < < < | < < < | | 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 | } } /* Restriction (23) */ if( (p->selFlags & SF_Recursive) ) return 0; /* Restriction (17h) */ if( compoundHasDifferentAffinities(pSub) ) return 0; if( pSrc->nSrc>1 ){ if( pParse->nSelect>500 ) return 0; if( OptimizationDisabled(db, SQLITE_FlttnUnionAll) ) return 0; aCsrMap = sqlite3DbMallocZero(db, ((i64)pParse->nTab+1)*sizeof(int)); if( aCsrMap ) aCsrMap[0] = pParse->nTab; } } /***** If we reach this point, flattening is permitted. *****/ TREETRACE(0x4,pParse,p,("flatten %u.%p from term %d\n", pSub->selId, pSub, iFrom)); /* Authorize the subquery */ pParse->zAuthContext = pSubitem->zName; TESTONLY(i =) sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0); testcase( i==SQLITE_DENY ); pParse->zAuthContext = zSavedAuthContext; |
︙ | ︙ | |||
4470 4471 4472 4473 4474 4475 4476 | if( aCsrMap && ALWAYS(db->mallocFailed==0) ){ renumberCursors(pParse, pNew, iFrom, aCsrMap); } pNew->pPrior = pPrior; if( pPrior ) pPrior->pNext = pNew; pNew->pNext = p; p->pPrior = pNew; | | | 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 | if( aCsrMap && ALWAYS(db->mallocFailed==0) ){ renumberCursors(pParse, pNew, iFrom, aCsrMap); } pNew->pPrior = pPrior; if( pPrior ) pPrior->pNext = pNew; pNew->pNext = p; p->pPrior = pNew; TREETRACE(0x4,pParse,p,("compound-subquery flattener" " creates %u as peer\n",pNew->selId)); } assert( pSubitem->pSelect==0 ); } sqlite3DbFree(db, aCsrMap); if( db->mallocFailed ){ pSubitem->pSelect = pSub1; |
︙ | ︙ | |||
4650 4651 4652 4653 4654 4655 4656 | ** success. */ sqlite3AggInfoPersistWalkerInit(&w, pParse); sqlite3WalkSelect(&w,pSub1); sqlite3SelectDelete(db, pSub1); #if TREETRACE_ENABLED | | | | 4717 4718 4719 4720 4721 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 | ** success. */ sqlite3AggInfoPersistWalkerInit(&w, pParse); sqlite3WalkSelect(&w,pSub1); sqlite3SelectDelete(db, pSub1); #if TREETRACE_ENABLED if( sqlite3TreeTrace & 0x4 ){ TREETRACE(0x4,pParse,p,("After flattening:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif return 1; } #endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ |
︙ | ︙ | |||
5025 5026 5027 5028 5029 5030 5031 | ** filter out entire partitions, as this does not change the ** window over which any window-function is calculated. ** ** (7) The inner query is a Common Table Expression (CTE) that should ** be materialized. (This restriction is implemented in the calling ** routine.) ** | | | | > | | | > < > | > > > > > > > > > > > > > > > > > > > > > > > > < > | 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 5167 | ** filter out entire partitions, as this does not change the ** window over which any window-function is calculated. ** ** (7) The inner query is a Common Table Expression (CTE) that should ** be materialized. (This restriction is implemented in the calling ** routine.) ** ** (8) If the subquery is a compound that uses UNION, INTERSECT, ** or EXCEPT, then all of the result set columns for all arms of ** the compound must use the BINARY collating sequence. ** ** (9) If the subquery is a compound, then all arms of the compound must ** have the same affinity. (This is the same as restriction (17h) ** for query flattening.) ** ** ** Return 0 if no changes are made and non-zero if one or more WHERE clause ** terms are duplicated into the subquery. */ static int pushDownWhereTerms( Parse *pParse, /* Parse context (for malloc() and error reporting) */ Select *pSubq, /* The subquery whose WHERE clause is to be augmented */ Expr *pWhere, /* The WHERE clause of the outer query */ SrcItem *pSrc /* The subquery term of the outer FROM clause */ ){ Expr *pNew; int nChng = 0; if( pWhere==0 ) return 0; if( pSubq->selFlags & (SF_Recursive|SF_MultiPart) ) return 0; if( pSrc->fg.jointype & (JT_LTORJ|JT_RIGHT) ) return 0; if( pSubq->pPrior ){ Select *pSel; int notUnionAll = 0; for(pSel=pSubq; pSel; pSel=pSel->pPrior){ u8 op = pSel->op; assert( op==TK_ALL || op==TK_SELECT || op==TK_UNION || op==TK_INTERSECT || op==TK_EXCEPT ); if( op!=TK_ALL && op!=TK_SELECT ){ notUnionAll = 1; } #ifndef SQLITE_OMIT_WINDOWFUNC if( pSel->pWin ) return 0; /* restriction (6b) */ #endif } if( compoundHasDifferentAffinities(pSubq) ){ return 0; /* restriction (9) */ } if( notUnionAll ){ /* If any of the compound arms are connected using UNION, INTERSECT, ** or EXCEPT, then we must ensure that none of the columns use a ** non-BINARY collating sequence. */ for(pSel=pSubq; pSel; pSel=pSel->pPrior){ int ii; const ExprList *pList = pSel->pEList; assert( pList!=0 ); for(ii=0; ii<pList->nExpr; ii++){ CollSeq *pColl = sqlite3ExprCollSeq(pParse, pList->a[ii].pExpr); if( !sqlite3IsBinary(pColl) ){ return 0; /* Restriction (8) */ } } } } }else{ #ifndef SQLITE_OMIT_WINDOWFUNC if( pSubq->pWin && pSubq->pWin->pPartition==0 ) return 0; #endif } #ifdef SQLITE_DEBUG /* Only the first term of a compound can have a WITH clause. But make ** sure no other terms are marked SF_Recursive in case something changes ** in the future. */ { |
︙ | ︙ | |||
6064 6065 6066 6067 6068 6069 6070 | return WRC_Abort; } if( (elistFlags & (EP_HasFunc|EP_Subquery))!=0 ){ p->selFlags |= SF_ComplexResult; } } #if TREETRACE_ENABLED | | | | 6157 6158 6159 6160 6161 6162 6163 6164 6165 6166 6167 6168 6169 6170 6171 6172 | return WRC_Abort; } if( (elistFlags & (EP_HasFunc|EP_Subquery))!=0 ){ p->selFlags |= SF_ComplexResult; } } #if TREETRACE_ENABLED if( sqlite3TreeTrace & 0x8 ){ TREETRACE(0x8,pParse,p,("After result-set wildcard expansion:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif return WRC_Continue; } #if SQLITE_DEBUG |
︙ | ︙ | |||
6116 6117 6118 6119 6120 6121 6122 | #ifndef SQLITE_OMIT_SUBQUERY /* ** This is a Walker.xSelectCallback callback for the sqlite3SelectTypeInfo() ** interface. ** | | | | | | | < < | | 6209 6210 6211 6212 6213 6214 6215 6216 6217 6218 6219 6220 6221 6222 6223 6224 6225 6226 6227 6228 6229 6230 6231 6232 6233 6234 6235 6236 6237 6238 6239 6240 6241 6242 6243 6244 6245 6246 6247 6248 6249 6250 | #ifndef SQLITE_OMIT_SUBQUERY /* ** This is a Walker.xSelectCallback callback for the sqlite3SelectTypeInfo() ** interface. ** ** For each FROM-clause subquery, add Column.zType, Column.zColl, and ** Column.affinity information to the Table structure that represents ** the result set of that subquery. ** ** The Table structure that represents the result set was constructed ** by selectExpander() but the type and collation and affinity information ** was omitted at that point because identifiers had not yet been resolved. ** This routine is called after identifier resolution. */ static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){ Parse *pParse; int i; SrcList *pTabList; SrcItem *pFrom; assert( p->selFlags & SF_Resolved ); if( p->selFlags & SF_HasTypeInfo ) return; p->selFlags |= SF_HasTypeInfo; pParse = pWalker->pParse; pTabList = p->pSrc; for(i=0, pFrom=pTabList->a; i<pTabList->nSrc; i++, pFrom++){ Table *pTab = pFrom->pTab; assert( pTab!=0 ); if( (pTab->tabFlags & TF_Ephemeral)!=0 ){ /* A sub-query in the FROM clause of a SELECT */ Select *pSel = pFrom->pSelect; if( pSel ){ sqlite3SubqueryColumnTypes(pParse, pTab, pSel, SQLITE_AFF_NONE); } } } } #endif |
︙ | ︙ | |||
6199 6200 6201 6202 6203 6204 6205 6206 6207 6208 6209 6210 6211 6212 6213 6214 6215 6216 6217 6218 6219 6220 6221 6222 6223 | if( p->selFlags & SF_HasTypeInfo ) return; sqlite3SelectExpand(pParse, p); if( pParse->nErr ) return; sqlite3ResolveSelectNames(pParse, p, pOuterNC); if( pParse->nErr ) return; sqlite3SelectAddTypeInfo(pParse, p); } /* ** Reset the aggregate accumulator. ** ** The aggregate accumulator is a set of memory cells that hold ** intermediate results while calculating an aggregate. This ** routine generates code that stores NULLs in all of those memory ** cells. */ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){ Vdbe *v = pParse->pVdbe; int i; struct AggInfo_func *pFunc; int nReg = pAggInfo->nFunc + pAggInfo->nColumn; assert( pParse->db->pParse==pParse ); assert( pParse->db->mallocFailed==0 || pParse->nErr!=0 ); if( nReg==0 ) return; if( pParse->nErr ) return; | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < < < < < < < < < < < < < | > | 6290 6291 6292 6293 6294 6295 6296 6297 6298 6299 6300 6301 6302 6303 6304 6305 6306 6307 6308 6309 6310 6311 6312 6313 6314 6315 6316 6317 6318 6319 6320 6321 6322 6323 6324 6325 6326 6327 6328 6329 6330 6331 6332 6333 6334 6335 6336 6337 6338 6339 6340 6341 6342 6343 6344 6345 6346 6347 6348 6349 6350 6351 6352 6353 6354 6355 6356 6357 6358 6359 6360 6361 6362 6363 6364 6365 6366 6367 6368 6369 6370 6371 6372 6373 6374 6375 6376 6377 6378 6379 6380 6381 6382 6383 6384 6385 6386 6387 6388 6389 6390 6391 6392 6393 6394 6395 6396 6397 6398 6399 6400 6401 6402 6403 6404 6405 6406 6407 6408 6409 6410 6411 6412 6413 6414 6415 6416 6417 6418 6419 6420 6421 6422 6423 6424 6425 6426 6427 6428 6429 6430 6431 6432 6433 6434 6435 6436 6437 6438 6439 6440 6441 6442 6443 6444 6445 6446 6447 6448 6449 6450 6451 6452 6453 6454 6455 6456 6457 6458 6459 6460 6461 6462 6463 6464 6465 6466 6467 6468 6469 6470 6471 6472 6473 6474 6475 6476 6477 6478 6479 6480 6481 6482 6483 6484 6485 6486 6487 6488 6489 6490 6491 | if( p->selFlags & SF_HasTypeInfo ) return; sqlite3SelectExpand(pParse, p); if( pParse->nErr ) return; sqlite3ResolveSelectNames(pParse, p, pOuterNC); if( pParse->nErr ) return; sqlite3SelectAddTypeInfo(pParse, p); } #if TREETRACE_ENABLED /* ** Display all information about an AggInfo object */ static void printAggInfo(AggInfo *pAggInfo){ int ii; for(ii=0; ii<pAggInfo->nColumn; ii++){ struct AggInfo_col *pCol = &pAggInfo->aCol[ii]; sqlite3DebugPrintf( "agg-column[%d] pTab=%s iTable=%d iColumn=%d iMem=%d" " iSorterColumn=%d %s\n", ii, pCol->pTab ? pCol->pTab->zName : "NULL", pCol->iTable, pCol->iColumn, pAggInfo->iFirstReg+ii, pCol->iSorterColumn, ii>=pAggInfo->nAccumulator ? "" : " Accumulator"); sqlite3TreeViewExpr(0, pAggInfo->aCol[ii].pCExpr, 0); } for(ii=0; ii<pAggInfo->nFunc; ii++){ sqlite3DebugPrintf("agg-func[%d]: iMem=%d\n", ii, pAggInfo->iFirstReg+pAggInfo->nColumn+ii); sqlite3TreeViewExpr(0, pAggInfo->aFunc[ii].pFExpr, 0); } } #endif /* TREETRACE_ENABLED */ /* ** Analyze the arguments to aggregate functions. Create new pAggInfo->aCol[] ** entries for columns that are arguments to aggregate functions but which ** are not otherwise used. ** ** The aCol[] entries in AggInfo prior to nAccumulator are columns that ** are referenced outside of aggregate functions. These might be columns ** that are part of the GROUP by clause, for example. Other database engines ** would throw an error if there is a column reference that is not in the ** GROUP BY clause and that is not part of an aggregate function argument. ** But SQLite allows this. ** ** The aCol[] entries beginning with the aCol[nAccumulator] and following ** are column references that are used exclusively as arguments to ** aggregate functions. This routine is responsible for computing ** (or recomputing) those aCol[] entries. */ static void analyzeAggFuncArgs( AggInfo *pAggInfo, NameContext *pNC ){ int i; assert( pAggInfo!=0 ); assert( pAggInfo->iFirstReg==0 ); pNC->ncFlags |= NC_InAggFunc; for(i=0; i<pAggInfo->nFunc; i++){ Expr *pExpr = pAggInfo->aFunc[i].pFExpr; assert( ExprUseXList(pExpr) ); sqlite3ExprAnalyzeAggList(pNC, pExpr->x.pList); #ifndef SQLITE_OMIT_WINDOWFUNC assert( !IsWindowFunc(pExpr) ); if( ExprHasProperty(pExpr, EP_WinFunc) ){ sqlite3ExprAnalyzeAggregates(pNC, pExpr->y.pWin->pFilter); } #endif } pNC->ncFlags &= ~NC_InAggFunc; } /* ** An index on expressions is being used in the inner loop of an ** aggregate query with a GROUP BY clause. This routine attempts ** to adjust the AggInfo object to take advantage of index and to ** perhaps use the index as a covering index. ** */ static void optimizeAggregateUseOfIndexedExpr( Parse *pParse, /* Parsing context */ Select *pSelect, /* The SELECT statement being processed */ AggInfo *pAggInfo, /* The aggregate info */ NameContext *pNC /* Name context used to resolve agg-func args */ ){ assert( pAggInfo->iFirstReg==0 ); pAggInfo->nColumn = pAggInfo->nAccumulator; if( ALWAYS(pAggInfo->nSortingColumn>0) ){ if( pAggInfo->nColumn==0 ){ pAggInfo->nSortingColumn = 0; }else{ pAggInfo->nSortingColumn = pAggInfo->aCol[pAggInfo->nColumn-1].iSorterColumn+1; } } analyzeAggFuncArgs(pAggInfo, pNC); #if TREETRACE_ENABLED if( sqlite3TreeTrace & 0x20 ){ IndexedExpr *pIEpr; TREETRACE(0x20, pParse, pSelect, ("AggInfo (possibly) adjusted for Indexed Exprs\n")); sqlite3TreeViewSelect(0, pSelect, 0); for(pIEpr=pParse->pIdxEpr; pIEpr; pIEpr=pIEpr->pIENext){ printf("data-cursor=%d index={%d,%d}\n", pIEpr->iDataCur, pIEpr->iIdxCur, pIEpr->iIdxCol); sqlite3TreeViewExpr(0, pIEpr->pExpr, 0); } printAggInfo(pAggInfo); } #else UNUSED_PARAMETER(pSelect); UNUSED_PARAMETER(pParse); #endif } /* ** Walker callback for aggregateConvertIndexedExprRefToColumn(). */ static int aggregateIdxEprRefToColCallback(Walker *pWalker, Expr *pExpr){ AggInfo *pAggInfo; struct AggInfo_col *pCol; UNUSED_PARAMETER(pWalker); if( pExpr->pAggInfo==0 ) return WRC_Continue; if( pExpr->op==TK_AGG_COLUMN ) return WRC_Continue; if( pExpr->op==TK_AGG_FUNCTION ) return WRC_Continue; if( pExpr->op==TK_IF_NULL_ROW ) return WRC_Continue; pAggInfo = pExpr->pAggInfo; assert( pExpr->iAgg>=0 && pExpr->iAgg<pAggInfo->nColumn ); pCol = &pAggInfo->aCol[pExpr->iAgg]; pExpr->op = TK_AGG_COLUMN; pExpr->iTable = pCol->iTable; pExpr->iColumn = pCol->iColumn; return WRC_Prune; } /* ** Convert every pAggInfo->aFunc[].pExpr such that any node within ** those expressions that has pAppInfo set is changed into a TK_AGG_COLUMN ** opcode. */ static void aggregateConvertIndexedExprRefToColumn(AggInfo *pAggInfo){ int i; Walker w; memset(&w, 0, sizeof(w)); w.xExprCallback = aggregateIdxEprRefToColCallback; for(i=0; i<pAggInfo->nFunc; i++){ sqlite3WalkExpr(&w, pAggInfo->aFunc[i].pFExpr); } } /* ** Allocate a block of registers so that there is one register for each ** pAggInfo->aCol[] and pAggInfo->aFunc[] entry in pAggInfo. The first ** register in this block is stored in pAggInfo->iFirstReg. ** ** This routine may only be called once for each AggInfo object. Prior ** to calling this routine: ** ** * The aCol[] and aFunc[] arrays may be modified ** * The AggInfoColumnReg() and AggInfoFuncReg() macros may not be used ** ** After clling this routine: ** ** * The aCol[] and aFunc[] arrays are fixed ** * The AggInfoColumnReg() and AggInfoFuncReg() macros may be used ** */ static void assignAggregateRegisters(Parse *pParse, AggInfo *pAggInfo){ assert( pAggInfo!=0 ); assert( pAggInfo->iFirstReg==0 ); pAggInfo->iFirstReg = pParse->nMem + 1; pParse->nMem += pAggInfo->nColumn + pAggInfo->nFunc; } /* ** Reset the aggregate accumulator. ** ** The aggregate accumulator is a set of memory cells that hold ** intermediate results while calculating an aggregate. This ** routine generates code that stores NULLs in all of those memory ** cells. */ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){ Vdbe *v = pParse->pVdbe; int i; struct AggInfo_func *pFunc; int nReg = pAggInfo->nFunc + pAggInfo->nColumn; assert( pAggInfo->iFirstReg>0 ); assert( pParse->db->pParse==pParse ); assert( pParse->db->mallocFailed==0 || pParse->nErr!=0 ); if( nReg==0 ) return; if( pParse->nErr ) return; sqlite3VdbeAddOp3(v, OP_Null, 0, pAggInfo->iFirstReg, pAggInfo->iFirstReg+nReg-1); for(pFunc=pAggInfo->aFunc, i=0; i<pAggInfo->nFunc; i++, pFunc++){ if( pFunc->iDistinct>=0 ){ Expr *pE = pFunc->pFExpr; assert( ExprUseXList(pE) ); if( pE->x.pList==0 || pE->x.pList->nExpr!=1 ){ sqlite3ErrorMsg(pParse, "DISTINCT aggregates must have exactly one " "argument"); |
︙ | ︙ | |||
6262 6263 6264 6265 6266 6267 6268 | Vdbe *v = pParse->pVdbe; int i; struct AggInfo_func *pF; for(i=0, pF=pAggInfo->aFunc; i<pAggInfo->nFunc; i++, pF++){ ExprList *pList; assert( ExprUseXList(pF->pFExpr) ); pList = pF->pFExpr->x.pList; | | > | | > > | 6509 6510 6511 6512 6513 6514 6515 6516 6517 6518 6519 6520 6521 6522 6523 6524 6525 6526 6527 6528 6529 6530 6531 6532 6533 6534 6535 6536 6537 6538 6539 6540 6541 6542 6543 6544 6545 6546 6547 6548 6549 6550 6551 6552 6553 | Vdbe *v = pParse->pVdbe; int i; struct AggInfo_func *pF; for(i=0, pF=pAggInfo->aFunc; i<pAggInfo->nFunc; i++, pF++){ ExprList *pList; assert( ExprUseXList(pF->pFExpr) ); pList = pF->pFExpr->x.pList; sqlite3VdbeAddOp2(v, OP_AggFinal, AggInfoFuncReg(pAggInfo,i), pList ? pList->nExpr : 0); sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); } } /* ** Generate code that will update the accumulator memory cells for an ** aggregate based on the current cursor position. ** ** If regAcc is non-zero and there are no min() or max() aggregates ** in pAggInfo, then only populate the pAggInfo->nAccumulator accumulator ** registers if register regAcc contains 0. The caller will take care ** of setting and clearing regAcc. */ static void updateAccumulator( Parse *pParse, int regAcc, AggInfo *pAggInfo, int eDistinctType ){ Vdbe *v = pParse->pVdbe; int i; int regHit = 0; int addrHitTest = 0; struct AggInfo_func *pF; struct AggInfo_col *pC; assert( pAggInfo->iFirstReg>0 ); if( pParse->nErr ) return; pAggInfo->directMode = 1; for(i=0, pF=pAggInfo->aFunc; i<pAggInfo->nFunc; i++, pF++){ int nArg; int addrNext = 0; int regAgg; ExprList *pList; assert( ExprUseXList(pF->pFExpr) ); |
︙ | ︙ | |||
6350 6351 6352 6353 6354 6355 6356 | } if( !pColl ){ pColl = pParse->db->pDfltColl; } if( regHit==0 && pAggInfo->nAccumulator ) regHit = ++pParse->nMem; sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0, (char *)pColl, P4_COLLSEQ); } | | | | 6600 6601 6602 6603 6604 6605 6606 6607 6608 6609 6610 6611 6612 6613 6614 6615 6616 6617 6618 6619 6620 6621 6622 6623 6624 6625 6626 6627 6628 6629 | } if( !pColl ){ pColl = pParse->db->pDfltColl; } if( regHit==0 && pAggInfo->nAccumulator ) regHit = ++pParse->nMem; sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0, (char *)pColl, P4_COLLSEQ); } sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i)); sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); sqlite3VdbeChangeP5(v, (u8)nArg); sqlite3ReleaseTempRange(pParse, regAgg, nArg); if( addrNext ){ sqlite3VdbeResolveLabel(v, addrNext); } } if( regHit==0 && pAggInfo->nAccumulator ){ regHit = regAcc; } if( regHit ){ addrHitTest = sqlite3VdbeAddOp1(v, OP_If, regHit); VdbeCoverage(v); } for(i=0, pC=pAggInfo->aCol; i<pAggInfo->nAccumulator; i++, pC++){ sqlite3ExprCode(pParse, pC->pCExpr, AggInfoColumnReg(pAggInfo,i)); } pAggInfo->directMode = 0; if( addrHitTest ){ sqlite3VdbeJumpHereOrPopInst(v, addrHitTest); } } |
︙ | ︙ | |||
6461 6462 6463 6464 6465 6466 6467 | memset(&sWalker, 0, sizeof(sWalker)); sWalker.pParse = pParse; sWalker.xExprCallback = havingToWhereExprCb; sWalker.u.pSelect = p; sqlite3WalkExpr(&sWalker, p->pHaving); #if TREETRACE_ENABLED if( sWalker.eCode && (sqlite3TreeTrace & 0x100)!=0 ){ | | | > > > | | | > | > | 6711 6712 6713 6714 6715 6716 6717 6718 6719 6720 6721 6722 6723 6724 6725 6726 6727 6728 6729 6730 6731 6732 6733 6734 6735 6736 6737 6738 6739 6740 6741 6742 6743 6744 6745 6746 6747 6748 6749 | memset(&sWalker, 0, sizeof(sWalker)); sWalker.pParse = pParse; sWalker.xExprCallback = havingToWhereExprCb; sWalker.u.pSelect = p; sqlite3WalkExpr(&sWalker, p->pHaving); #if TREETRACE_ENABLED if( sWalker.eCode && (sqlite3TreeTrace & 0x100)!=0 ){ TREETRACE(0x100,pParse,p,("Move HAVING terms into WHERE:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif } /* ** Check to see if the pThis entry of pTabList is a self-join of another view. ** Search FROM-clause entries in the range of iFirst..iEnd, including iFirst ** but stopping before iEnd. ** ** If pThis is a self-join, then return the SrcItem for the first other ** instance of that view found. If pThis is not a self-join then return 0. */ static SrcItem *isSelfJoinView( SrcList *pTabList, /* Search for self-joins in this FROM clause */ SrcItem *pThis, /* Search for prior reference to this subquery */ int iFirst, int iEnd /* Range of FROM-clause entries to search. */ ){ SrcItem *pItem; assert( pThis->pSelect!=0 ); if( pThis->pSelect->selFlags & SF_PushDown ) return 0; while( iFirst<iEnd ){ Select *pS1; pItem = &pTabList->a[iFirst++]; if( pItem->pSelect==0 ) continue; if( pItem->fg.viaCoroutine ) continue; if( pItem->zName==0 ) continue; assert( pItem->pTab!=0 ); assert( pThis->pTab!=0 ); if( pItem->pTab->pSchema!=pThis->pTab->pSchema ) continue; if( sqlite3_stricmp(pItem->zName, pThis->zName)!=0 ) continue; |
︙ | ︙ | |||
6593 6594 6595 6596 6597 6598 6599 | } pSub = pPrior; } p->pEList->a[0].pExpr = pExpr; p->selFlags &= ~SF_Aggregate; #if TREETRACE_ENABLED | | | | 6848 6849 6850 6851 6852 6853 6854 6855 6856 6857 6858 6859 6860 6861 6862 6863 | } pSub = pPrior; } p->pEList->a[0].pExpr = pExpr; p->selFlags &= ~SF_Aggregate; #if TREETRACE_ENABLED if( sqlite3TreeTrace & 0x200 ){ TREETRACE(0x200,pParse,p,("After count-of-view optimization:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif return 1; } #endif /* SQLITE_COUNTOFVIEW_OPTIMIZATION */ |
︙ | ︙ | |||
6624 6625 6626 6627 6628 6629 6630 6631 6632 6633 6634 6635 6636 6637 | && sameSrcAlias(p0, p1->pSelect->pSrc) ){ return 1; } } return 0; } /* ** Generate code for the SELECT statement given in the p argument. ** ** The results are returned according to the SelectDest structure. ** See comments in sqliteInt.h for further information. ** | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 6879 6880 6881 6882 6883 6884 6885 6886 6887 6888 6889 6890 6891 6892 6893 6894 6895 6896 6897 6898 6899 6900 6901 6902 6903 6904 6905 6906 6907 6908 6909 6910 6911 6912 6913 6914 6915 6916 6917 6918 6919 6920 6921 6922 6923 6924 6925 6926 6927 6928 6929 6930 6931 6932 6933 6934 6935 6936 6937 6938 6939 6940 6941 6942 6943 6944 6945 6946 6947 6948 | && sameSrcAlias(p0, p1->pSelect->pSrc) ){ return 1; } } return 0; } /* ** Return TRUE (non-zero) if the i-th entry in the pTabList SrcList can ** be implemented as a co-routine. The i-th entry is guaranteed to be ** a subquery. ** ** The subquery is implemented as a co-routine if all of the following are ** true: ** ** (1) The subquery will likely be implemented in the outer loop of ** the query. This will be the case if any one of the following ** conditions hold: ** (a) The subquery is the only term in the FROM clause ** (b) The subquery is the left-most term and a CROSS JOIN or similar ** requires it to be the outer loop ** (c) All of the following are true: ** (i) The subquery is the left-most subquery in the FROM clause ** (ii) There is nothing that would prevent the subquery from ** being used as the outer loop if the sqlite3WhereBegin() ** routine nominates it to that position. ** (iii) The query is not a UPDATE ... FROM ** (2) The subquery is not a CTE that should be materialized because of ** the AS MATERIALIZED keywords ** (3) The subquery is not part of a left operand for a RIGHT JOIN ** (4) The SQLITE_Coroutine optimization disable flag is not set ** (5) The subquery is not self-joined */ static int fromClauseTermCanBeCoroutine( Parse *pParse, /* Parsing context */ SrcList *pTabList, /* FROM clause */ int i, /* Which term of the FROM clause holds the subquery */ int selFlags /* Flags on the SELECT statement */ ){ SrcItem *pItem = &pTabList->a[i]; if( pItem->fg.isCte && pItem->u2.pCteUse->eM10d==M10d_Yes ) return 0;/* (2) */ if( pTabList->a[0].fg.jointype & JT_LTORJ ) return 0; /* (3) */ if( OptimizationDisabled(pParse->db, SQLITE_Coroutines) ) return 0; /* (4) */ if( isSelfJoinView(pTabList, pItem, i+1, pTabList->nSrc)!=0 ){ return 0; /* (5) */ } if( i==0 ){ if( pTabList->nSrc==1 ) return 1; /* (1a) */ if( pTabList->a[1].fg.jointype & JT_CROSS ) return 1; /* (1b) */ if( selFlags & SF_UpdateFrom ) return 0; /* (1c-iii) */ return 1; } if( selFlags & SF_UpdateFrom ) return 0; /* (1c-iii) */ while( 1 /*exit-by-break*/ ){ if( pItem->fg.jointype & (JT_OUTER|JT_CROSS) ) return 0; /* (1c-ii) */ if( i==0 ) break; i--; pItem--; if( pItem->pSelect!=0 ) return 0; /* (1c-i) */ } return 1; } /* ** Generate code for the SELECT statement given in the p argument. ** ** The results are returned according to the SelectDest structure. ** See comments in sqliteInt.h for further information. ** |
︙ | ︙ | |||
6670 6671 6672 6673 6674 6675 6676 | v = sqlite3GetVdbe(pParse); if( p==0 || pParse->nErr ){ return 1; } assert( db->mallocFailed==0 ); if( sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1; #if TREETRACE_ENABLED | | | | | | | | 6981 6982 6983 6984 6985 6986 6987 6988 6989 6990 6991 6992 6993 6994 6995 6996 6997 6998 6999 7000 7001 7002 7003 7004 7005 7006 7007 7008 7009 7010 7011 7012 7013 7014 7015 7016 7017 7018 7019 7020 7021 7022 7023 7024 7025 7026 7027 7028 7029 7030 7031 7032 7033 7034 7035 7036 7037 7038 | v = sqlite3GetVdbe(pParse); if( p==0 || pParse->nErr ){ return 1; } assert( db->mallocFailed==0 ); if( sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1; #if TREETRACE_ENABLED TREETRACE(0x1,pParse,p, ("begin processing:\n", pParse->addrExplain)); if( sqlite3TreeTrace & 0x10000 ){ if( (sqlite3TreeTrace & 0x10001)==0x10000 ){ sqlite3TreeViewLine(0, "In sqlite3Select() at %s:%d", __FILE__, __LINE__); } sqlite3ShowSelect(p); } #endif assert( p->pOrderBy==0 || pDest->eDest!=SRT_DistFifo ); assert( p->pOrderBy==0 || pDest->eDest!=SRT_Fifo ); assert( p->pOrderBy==0 || pDest->eDest!=SRT_DistQueue ); assert( p->pOrderBy==0 || pDest->eDest!=SRT_Queue ); if( IgnorableDistinct(pDest) ){ assert(pDest->eDest==SRT_Exists || pDest->eDest==SRT_Union || pDest->eDest==SRT_Except || pDest->eDest==SRT_Discard || pDest->eDest==SRT_DistQueue || pDest->eDest==SRT_DistFifo ); /* All of these destinations are also able to ignore the ORDER BY clause */ if( p->pOrderBy ){ #if TREETRACE_ENABLED TREETRACE(0x800,pParse,p, ("dropping superfluous ORDER BY:\n")); if( sqlite3TreeTrace & 0x800 ){ sqlite3TreeViewExprList(0, p->pOrderBy, 0, "ORDERBY"); } #endif sqlite3ParserAddCleanup(pParse, (void(*)(sqlite3*,void*))sqlite3ExprListDelete, p->pOrderBy); testcase( pParse->earlyCleanup ); p->pOrderBy = 0; } p->selFlags &= ~SF_Distinct; p->selFlags |= SF_NoopOrderBy; } sqlite3SelectPrep(pParse, p, 0); if( pParse->nErr ){ goto select_end; } assert( db->mallocFailed==0 ); assert( p->pEList!=0 ); #if TREETRACE_ENABLED if( sqlite3TreeTrace & 0x10 ){ TREETRACE(0x10,pParse,p, ("after name resolution:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif /* If the SF_UFSrcCheck flag is set, then this function is being called ** as part of populating the temp table for an UPDATE...FROM statement. ** In this case, it is an error if the target object (pSrc->a[0]) name |
︙ | ︙ | |||
6754 6755 6756 6757 6758 6759 6760 | #ifndef SQLITE_OMIT_WINDOWFUNC if( sqlite3WindowRewrite(pParse, p) ){ assert( pParse->nErr ); goto select_end; } #if TREETRACE_ENABLED | | | | 7065 7066 7067 7068 7069 7070 7071 7072 7073 7074 7075 7076 7077 7078 7079 7080 | #ifndef SQLITE_OMIT_WINDOWFUNC if( sqlite3WindowRewrite(pParse, p) ){ assert( pParse->nErr ); goto select_end; } #if TREETRACE_ENABLED if( p->pWin && (sqlite3TreeTrace & 0x40)!=0 ){ TREETRACE(0x40,pParse,p, ("after window rewrite:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif #endif /* SQLITE_OMIT_WINDOWFUNC */ pTabList = p->pSrc; isAgg = (p->selFlags & SF_Aggregate)!=0; memset(&sSort, 0, sizeof(sSort)); |
︙ | ︙ | |||
6786 6787 6788 6789 6790 6791 6792 | /* Convert LEFT JOIN into JOIN if there are terms of the right table ** of the LEFT JOIN used in the WHERE clause. */ if( (pItem->fg.jointype & (JT_LEFT|JT_RIGHT))==JT_LEFT && sqlite3ExprImpliesNonNullRow(p->pWhere, pItem->iCursor) && OptimizationEnabled(db, SQLITE_SimplifyJoin) ){ | | | 7097 7098 7099 7100 7101 7102 7103 7104 7105 7106 7107 7108 7109 7110 7111 | /* Convert LEFT JOIN into JOIN if there are terms of the right table ** of the LEFT JOIN used in the WHERE clause. */ if( (pItem->fg.jointype & (JT_LEFT|JT_RIGHT))==JT_LEFT && sqlite3ExprImpliesNonNullRow(p->pWhere, pItem->iCursor) && OptimizationEnabled(db, SQLITE_SimplifyJoin) ){ TREETRACE(0x1000,pParse,p, ("LEFT-JOIN simplifies to JOIN on term %d\n",i)); pItem->fg.jointype &= ~(JT_LEFT|JT_OUTER); assert( pItem->iCursor>=0 ); unsetJoinExpr(p->pWhere, pItem->iCursor, pTabList->a[0].fg.jointype & JT_LTORJ); } |
︙ | ︙ | |||
6842 6843 6844 6845 6846 6847 6848 | if( pSub->pOrderBy!=0 && (p->pOrderBy!=0 || pTabList->nSrc>1) /* Condition (5) */ && pSub->pLimit==0 /* Condition (1) */ && (pSub->selFlags & SF_OrderByReqd)==0 /* Condition (2) */ && (p->selFlags & SF_OrderByReqd)==0 /* Condition (3) and (4) */ && OptimizationEnabled(db, SQLITE_OmitOrderBy) ){ | | | 7153 7154 7155 7156 7157 7158 7159 7160 7161 7162 7163 7164 7165 7166 7167 | if( pSub->pOrderBy!=0 && (p->pOrderBy!=0 || pTabList->nSrc>1) /* Condition (5) */ && pSub->pLimit==0 /* Condition (1) */ && (pSub->selFlags & SF_OrderByReqd)==0 /* Condition (2) */ && (p->selFlags & SF_OrderByReqd)==0 /* Condition (3) and (4) */ && OptimizationEnabled(db, SQLITE_OmitOrderBy) ){ TREETRACE(0x800,pParse,p, ("omit superfluous ORDER BY on %r FROM-clause subquery\n",i+1)); sqlite3ParserAddCleanup(pParse, (void(*)(sqlite3*,void*))sqlite3ExprListDelete, pSub->pOrderBy); pSub->pOrderBy = 0; } |
︙ | ︙ | |||
6897 6898 6899 6900 6901 6902 6903 | #ifndef SQLITE_OMIT_COMPOUND_SELECT /* Handle compound SELECT statements using the separate multiSelect() ** procedure. */ if( p->pPrior ){ rc = multiSelect(pParse, p, pDest); #if TREETRACE_ENABLED | | | | | | | 7208 7209 7210 7211 7212 7213 7214 7215 7216 7217 7218 7219 7220 7221 7222 7223 7224 7225 7226 7227 7228 7229 7230 7231 7232 7233 7234 7235 7236 7237 7238 7239 7240 7241 7242 7243 7244 7245 7246 7247 7248 7249 | #ifndef SQLITE_OMIT_COMPOUND_SELECT /* Handle compound SELECT statements using the separate multiSelect() ** procedure. */ if( p->pPrior ){ rc = multiSelect(pParse, p, pDest); #if TREETRACE_ENABLED TREETRACE(0x400,pParse,p,("end compound-select processing\n")); if( (sqlite3TreeTrace & 0x400)!=0 && ExplainQueryPlanParent(pParse)==0 ){ sqlite3TreeViewSelect(0, p, 0); } #endif if( p->pNext==0 ) ExplainQueryPlanPop(pParse); return rc; } #endif /* Do the WHERE-clause constant propagation optimization if this is ** a join. No need to speed time on this operation for non-join queries ** as the equivalent optimization will be handled by query planner in ** sqlite3WhereBegin(). */ if( p->pWhere!=0 && p->pWhere->op==TK_AND && OptimizationEnabled(db, SQLITE_PropagateConst) && propagateConstants(pParse, p) ){ #if TREETRACE_ENABLED if( sqlite3TreeTrace & 0x2000 ){ TREETRACE(0x2000,pParse,p,("After constant propagation:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif }else{ TREETRACE(0x2000,pParse,p,("Constant propagation not helpful\n")); } #ifdef SQLITE_COUNTOFVIEW_OPTIMIZATION if( OptimizationEnabled(db, SQLITE_QueryFlattener|SQLITE_CountOfView) && countOfViewOptimization(pParse, p) ){ if( db->mallocFailed ) goto select_end; |
︙ | ︙ | |||
6997 6998 6999 7000 7001 7002 7003 | */ if( OptimizationEnabled(db, SQLITE_PushDown) && (pItem->fg.isCte==0 || (pItem->u2.pCteUse->eM10d!=M10d_Yes && pItem->u2.pCteUse->nUse<2)) && pushDownWhereTerms(pParse, pSub, p->pWhere, pItem) ){ #if TREETRACE_ENABLED | | | | < < < < < < < < < | < < < < | 7308 7309 7310 7311 7312 7313 7314 7315 7316 7317 7318 7319 7320 7321 7322 7323 7324 7325 7326 7327 7328 7329 7330 7331 7332 7333 7334 7335 7336 7337 7338 | */ if( OptimizationEnabled(db, SQLITE_PushDown) && (pItem->fg.isCte==0 || (pItem->u2.pCteUse->eM10d!=M10d_Yes && pItem->u2.pCteUse->nUse<2)) && pushDownWhereTerms(pParse, pSub, p->pWhere, pItem) ){ #if TREETRACE_ENABLED if( sqlite3TreeTrace & 0x4000 ){ TREETRACE(0x4000,pParse,p, ("After WHERE-clause push-down into subquery %d:\n", pSub->selId)); sqlite3TreeViewSelect(0, p, 0); } #endif assert( pItem->pSelect && (pItem->pSelect->selFlags & SF_PushDown)!=0 ); }else{ TREETRACE(0x4000,pParse,p,("Push-down not possible\n")); } zSavedAuthContext = pParse->zAuthContext; pParse->zAuthContext = pItem->zName; /* Generate code to implement the subquery */ if( fromClauseTermCanBeCoroutine(pParse, pTabList, i, p->selFlags) ){ /* Implement a co-routine that will return a single row of the result ** set on each invocation. */ int addrTop = sqlite3VdbeCurrentAddr(v)+1; pItem->regReturn = ++pParse->nMem; sqlite3VdbeAddOp3(v, OP_InitCoroutine, pItem->regReturn, 0, addrTop); |
︙ | ︙ | |||
7057 7058 7059 7060 7061 7062 7063 | CteUse *pCteUse = pItem->u2.pCteUse; sqlite3VdbeAddOp2(v, OP_Gosub, pCteUse->regRtn, pCteUse->addrM9e); if( pItem->iCursor!=pCteUse->iCur ){ sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pCteUse->iCur); VdbeComment((v, "%!S", pItem)); } pSub->nSelectRow = pCteUse->nRowEst; | | > > > > | < < < > | 7355 7356 7357 7358 7359 7360 7361 7362 7363 7364 7365 7366 7367 7368 7369 7370 7371 7372 7373 7374 7375 7376 7377 7378 7379 7380 7381 7382 7383 7384 7385 7386 7387 7388 7389 7390 7391 7392 7393 7394 7395 7396 7397 7398 7399 7400 7401 7402 7403 7404 7405 7406 7407 7408 | CteUse *pCteUse = pItem->u2.pCteUse; sqlite3VdbeAddOp2(v, OP_Gosub, pCteUse->regRtn, pCteUse->addrM9e); if( pItem->iCursor!=pCteUse->iCur ){ sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pCteUse->iCur); VdbeComment((v, "%!S", pItem)); } pSub->nSelectRow = pCteUse->nRowEst; }else if( (pPrior = isSelfJoinView(pTabList, pItem, 0, i))!=0 ){ /* This view has already been materialized by a prior entry in ** this same FROM clause. Reuse it. */ if( pPrior->addrFillSub ){ sqlite3VdbeAddOp2(v, OP_Gosub, pPrior->regReturn, pPrior->addrFillSub); } sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pPrior->iCursor); pSub->nSelectRow = pPrior->pSelect->nSelectRow; }else{ /* Materialize the view. If the view is not correlated, generate a ** subroutine to do the materialization so that subsequent uses of ** the same view can reuse the materialization. */ int topAddr; int onceAddr = 0; #ifdef SQLITE_ENABLE_STMT_SCANSTATUS int addrExplain; #endif pItem->regReturn = ++pParse->nMem; topAddr = sqlite3VdbeAddOp0(v, OP_Goto); pItem->addrFillSub = topAddr+1; pItem->fg.isMaterialized = 1; if( pItem->fg.isCorrelated==0 ){ /* If the subquery is not correlated and if we are not inside of ** a trigger, then we only need to compute the value of the subquery ** once. */ onceAddr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); VdbeComment((v, "materialize %!S", pItem)); }else{ VdbeNoopComment((v, "materialize %!S", pItem)); } sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor); ExplainQueryPlan2(addrExplain, (pParse, 1, "MATERIALIZE %!S", pItem)); sqlite3Select(pParse, pSub, &dest); pItem->pTab->nRowLogEst = pSub->nSelectRow; if( onceAddr ) sqlite3VdbeJumpHere(v, onceAddr); sqlite3VdbeAddOp2(v, OP_Return, pItem->regReturn, topAddr+1); VdbeComment((v, "end %!S", pItem)); sqlite3VdbeScanStatusRange(v, addrExplain, addrExplain, -1); sqlite3VdbeJumpHere(v, topAddr); sqlite3ClearTempRegCache(pParse); if( pItem->fg.isCte && pItem->fg.isCorrelated==0 ){ CteUse *pCteUse = pItem->u2.pCteUse; pCteUse->addrM9e = pItem->addrFillSub; pCteUse->regRtn = pItem->regReturn; pCteUse->iCur = pItem->iCursor; |
︙ | ︙ | |||
7120 7121 7122 7123 7124 7125 7126 | pEList = p->pEList; pWhere = p->pWhere; pGroupBy = p->pGroupBy; pHaving = p->pHaving; sDistinct.isTnct = (p->selFlags & SF_Distinct)!=0; #if TREETRACE_ENABLED | | | | 7420 7421 7422 7423 7424 7425 7426 7427 7428 7429 7430 7431 7432 7433 7434 7435 | pEList = p->pEList; pWhere = p->pWhere; pGroupBy = p->pGroupBy; pHaving = p->pHaving; sDistinct.isTnct = (p->selFlags & SF_Distinct)!=0; #if TREETRACE_ENABLED if( sqlite3TreeTrace & 0x8000 ){ TREETRACE(0x8000,pParse,p,("After all FROM-clause analysis:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif /* If the query is DISTINCT with an ORDER BY but is not an aggregate, and ** if the select-list is the same as the ORDER BY list, then this query ** can be rewritten as a GROUP BY. In other words, this: |
︙ | ︙ | |||
7157 7158 7159 7160 7161 7162 7163 | /* Notice that even thought SF_Distinct has been cleared from p->selFlags, ** the sDistinct.isTnct is still set. Hence, isTnct represents the ** original setting of the SF_Distinct flag, not the current setting */ assert( sDistinct.isTnct ); sDistinct.isTnct = 2; #if TREETRACE_ENABLED | | | | 7457 7458 7459 7460 7461 7462 7463 7464 7465 7466 7467 7468 7469 7470 7471 7472 | /* Notice that even thought SF_Distinct has been cleared from p->selFlags, ** the sDistinct.isTnct is still set. Hence, isTnct represents the ** original setting of the SF_Distinct flag, not the current setting */ assert( sDistinct.isTnct ); sDistinct.isTnct = 2; #if TREETRACE_ENABLED if( sqlite3TreeTrace & 0x20000 ){ TREETRACE(0x20000,pParse,p,("Transform DISTINCT into GROUP BY:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif } /* If there is an ORDER BY clause, then create an ephemeral index to ** do the sorting. But this sorting ephemeral index might end up |
︙ | ︙ | |||
7244 7245 7246 7247 7248 7249 7250 | sqlite3WindowCodeInit(pParse, p); } #endif assert( WHERE_USE_LIMIT==SF_FixedLimit ); /* Begin the database scan. */ | | | | 7544 7545 7546 7547 7548 7549 7550 7551 7552 7553 7554 7555 7556 7557 7558 7559 7560 7561 7562 7563 7564 7565 7566 7567 7568 7569 7570 7571 7572 7573 7574 7575 | sqlite3WindowCodeInit(pParse, p); } #endif assert( WHERE_USE_LIMIT==SF_FixedLimit ); /* Begin the database scan. */ TREETRACE(0x2,pParse,p,("WhereBegin\n")); pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy, p->pEList, p, wctrlFlags, p->nSelectRow); if( pWInfo==0 ) goto select_end; if( sqlite3WhereOutputRowCount(pWInfo) < p->nSelectRow ){ p->nSelectRow = sqlite3WhereOutputRowCount(pWInfo); } if( sDistinct.isTnct && sqlite3WhereIsDistinct(pWInfo) ){ sDistinct.eTnctType = sqlite3WhereIsDistinct(pWInfo); } if( sSort.pOrderBy ){ sSort.nOBSat = sqlite3WhereIsOrdered(pWInfo); sSort.labelOBLopt = sqlite3WhereOrderByLimitOptLabel(pWInfo); if( sSort.nOBSat==sSort.pOrderBy->nExpr ){ sSort.pOrderBy = 0; } } TREETRACE(0x2,pParse,p,("WhereBegin returns\n")); /* If sorting index that was created by a prior OP_OpenEphemeral ** instruction ended up not being needed, then change the OP_OpenEphemeral ** into an OP_Noop. */ if( sSort.addrSortIndex>=0 && sSort.pOrderBy==0 ){ sqlite3VdbeChangeToNoop(v, sSort.addrSortIndex); |
︙ | ︙ | |||
7300 7301 7302 7303 7304 7305 7306 | /* Use the standard inner loop. */ selectInnerLoop(pParse, p, -1, &sSort, &sDistinct, pDest, sqlite3WhereContinueLabel(pWInfo), sqlite3WhereBreakLabel(pWInfo)); /* End the database scan loop. */ | | | 7600 7601 7602 7603 7604 7605 7606 7607 7608 7609 7610 7611 7612 7613 7614 | /* Use the standard inner loop. */ selectInnerLoop(pParse, p, -1, &sSort, &sDistinct, pDest, sqlite3WhereContinueLabel(pWInfo), sqlite3WhereBreakLabel(pWInfo)); /* End the database scan loop. */ TREETRACE(0x2,pParse,p,("WhereEnd\n")); sqlite3WhereEnd(pWInfo); } }else{ /* This case when there exist aggregate functions or a GROUP BY clause ** or both */ NameContext sNC; /* Name context for processing aggregate information */ int iAMem; /* First Mem address for storing current GROUP BY */ |
︙ | ︙ | |||
7381 7382 7383 7384 7385 7386 7387 7388 7389 7390 7391 7392 | (void(*)(sqlite3*,void*))agginfoFree, pAggInfo); testcase( pParse->earlyCleanup ); } if( db->mallocFailed ){ goto select_end; } pAggInfo->selId = p->selId; memset(&sNC, 0, sizeof(sNC)); sNC.pParse = pParse; sNC.pSrcList = pTabList; sNC.uNC.pAggInfo = pAggInfo; VVA_ONLY( sNC.ncFlags = NC_UAggInfo; ) | > > > < < < < < < < < < < < < < < | | < | < < < < < < < < < < < < | < < | 7681 7682 7683 7684 7685 7686 7687 7688 7689 7690 7691 7692 7693 7694 7695 7696 7697 7698 7699 7700 7701 7702 7703 7704 7705 7706 7707 7708 7709 7710 7711 7712 7713 7714 7715 7716 7717 7718 7719 7720 7721 7722 7723 7724 7725 7726 7727 7728 7729 7730 7731 7732 7733 | (void(*)(sqlite3*,void*))agginfoFree, pAggInfo); testcase( pParse->earlyCleanup ); } if( db->mallocFailed ){ goto select_end; } pAggInfo->selId = p->selId; #ifdef SQLITE_DEBUG pAggInfo->pSelect = p; #endif memset(&sNC, 0, sizeof(sNC)); sNC.pParse = pParse; sNC.pSrcList = pTabList; sNC.uNC.pAggInfo = pAggInfo; VVA_ONLY( sNC.ncFlags = NC_UAggInfo; ) pAggInfo->nSortingColumn = pGroupBy ? pGroupBy->nExpr : 0; pAggInfo->pGroupBy = pGroupBy; sqlite3ExprAnalyzeAggList(&sNC, pEList); sqlite3ExprAnalyzeAggList(&sNC, sSort.pOrderBy); if( pHaving ){ if( pGroupBy ){ assert( pWhere==p->pWhere ); assert( pHaving==p->pHaving ); assert( pGroupBy==p->pGroupBy ); havingToWhere(pParse, p); pWhere = p->pWhere; } sqlite3ExprAnalyzeAggregates(&sNC, pHaving); } pAggInfo->nAccumulator = pAggInfo->nColumn; if( p->pGroupBy==0 && p->pHaving==0 && pAggInfo->nFunc==1 ){ minMaxFlag = minMaxQuery(db, pAggInfo->aFunc[0].pFExpr, &pMinMaxOrderBy); }else{ minMaxFlag = WHERE_ORDERBY_NORMAL; } analyzeAggFuncArgs(pAggInfo, &sNC); if( db->mallocFailed ) goto select_end; #if TREETRACE_ENABLED if( sqlite3TreeTrace & 0x20 ){ TREETRACE(0x20,pParse,p,("After aggregate analysis %p:\n", pAggInfo)); sqlite3TreeViewSelect(0, p, 0); if( minMaxFlag ){ sqlite3DebugPrintf("MIN/MAX Optimization (0x%02x) adds:\n", minMaxFlag); sqlite3TreeViewExprList(0, pMinMaxOrderBy, 0, "ORDERBY"); } printAggInfo(pAggInfo); } #endif /* Processing for aggregates with GROUP BY is very different and ** much more complex than aggregates without a GROUP BY. */ |
︙ | ︙ | |||
7514 7515 7516 7517 7518 7519 7520 | /* Begin a loop that will extract all source rows in GROUP BY order. ** This might involve two separate loops with an OP_Sort in between, or ** it might be a single loop that uses an index to extract information ** in the right order to begin with. */ sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset); | | > > > > | | 7788 7789 7790 7791 7792 7793 7794 7795 7796 7797 7798 7799 7800 7801 7802 7803 7804 7805 7806 7807 7808 7809 7810 7811 7812 7813 7814 7815 7816 | /* Begin a loop that will extract all source rows in GROUP BY order. ** This might involve two separate loops with an OP_Sort in between, or ** it might be a single loop that uses an index to extract information ** in the right order to begin with. */ sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset); TREETRACE(0x2,pParse,p,("WhereBegin\n")); pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, pDistinct, p, (sDistinct.isTnct==2 ? WHERE_DISTINCTBY : WHERE_GROUPBY) | (orderByGrp ? WHERE_SORTBYGROUP : 0) | distFlag, 0 ); if( pWInfo==0 ){ sqlite3ExprListDelete(db, pDistinct); goto select_end; } if( pParse->pIdxEpr ){ optimizeAggregateUseOfIndexedExpr(pParse, p, pAggInfo, &sNC); } assignAggregateRegisters(pParse, pAggInfo); eDist = sqlite3WhereIsDistinct(pWInfo); TREETRACE(0x2,pParse,p,("WhereBegin returns\n")); if( sqlite3WhereIsOrdered(pWInfo)==pGroupBy->nExpr ){ /* The optimizer is able to deliver rows in group by order so ** we do not have to sort. The OP_OpenEphemeral table will be ** cancelled later because we still need to use the pKeyInfo */ groupBySort = 0; }else{ |
︙ | ︙ | |||
7573 7574 7575 7576 7577 7578 7579 | } pAggInfo->directMode = 0; regRecord = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regRecord); sqlite3VdbeAddOp2(v, OP_SorterInsert, pAggInfo->sortingIdx, regRecord); sqlite3ReleaseTempReg(pParse, regRecord); sqlite3ReleaseTempRange(pParse, regBase, nCol); | | > > > > > > > > > > > > > > > > > | 7851 7852 7853 7854 7855 7856 7857 7858 7859 7860 7861 7862 7863 7864 7865 7866 7867 7868 7869 7870 7871 7872 7873 7874 7875 7876 7877 7878 7879 7880 7881 7882 7883 7884 7885 7886 7887 7888 7889 7890 | } pAggInfo->directMode = 0; regRecord = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regRecord); sqlite3VdbeAddOp2(v, OP_SorterInsert, pAggInfo->sortingIdx, regRecord); sqlite3ReleaseTempReg(pParse, regRecord); sqlite3ReleaseTempRange(pParse, regBase, nCol); TREETRACE(0x2,pParse,p,("WhereEnd\n")); sqlite3WhereEnd(pWInfo); pAggInfo->sortingIdxPTab = sortPTab = pParse->nTab++; sortOut = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_OpenPseudo, sortPTab, sortOut, nCol); sqlite3VdbeAddOp2(v, OP_SorterSort, pAggInfo->sortingIdx, addrEnd); VdbeComment((v, "GROUP BY sort")); VdbeCoverage(v); pAggInfo->useSortingIdx = 1; } /* If there entries in pAgggInfo->aFunc[] that contain subexpressions ** that are indexed (and that were previously identified and tagged ** in optimizeAggregateUseOfIndexedExpr()) then those subexpressions ** must now be converted into a TK_AGG_COLUMN node so that the value ** is correctly pulled from the index rather than being recomputed. */ if( pParse->pIdxEpr ){ aggregateConvertIndexedExprRefToColumn(pAggInfo); #if TREETRACE_ENABLED if( sqlite3TreeTrace & 0x20 ){ TREETRACE(0x20, pParse, p, ("AggInfo function expressions converted to reference index\n")); sqlite3TreeViewSelect(0, p, 0); printAggInfo(pAggInfo); } #endif } /* If the index or temporary table used by the GROUP BY sort ** will naturally deliver rows in the order required by the ORDER BY ** clause, cancel the ephemeral table open coded earlier. ** ** This is an optimization - the correct answer should result regardless. ** Use the SQLITE_GroupByOrder flag with SQLITE_TESTCTRL_OPTIMIZER to |
︙ | ︙ | |||
7651 7652 7653 7654 7655 7656 7657 | /* End of the loop */ if( groupBySort ){ sqlite3VdbeAddOp2(v, OP_SorterNext, pAggInfo->sortingIdx,addrTopOfLoop); VdbeCoverage(v); }else{ | | | 7946 7947 7948 7949 7950 7951 7952 7953 7954 7955 7956 7957 7958 7959 7960 | /* End of the loop */ if( groupBySort ){ sqlite3VdbeAddOp2(v, OP_SorterNext, pAggInfo->sortingIdx,addrTopOfLoop); VdbeCoverage(v); }else{ TREETRACE(0x2,pParse,p,("WhereEnd\n")); sqlite3WhereEnd(pWInfo); sqlite3VdbeChangeToNoop(v, addrSortingIdx); } sqlite3ExprListDelete(db, pDistinct); /* Output the final row of result */ |
︙ | ︙ | |||
7761 7762 7763 7764 7765 7766 7767 | } /* Open a read-only cursor, execute the OP_Count, close the cursor. */ sqlite3VdbeAddOp4Int(v, OP_OpenRead, iCsr, (int)iRoot, iDb, 1); if( pKeyInfo ){ sqlite3VdbeChangeP4(v, -1, (char *)pKeyInfo, P4_KEYINFO); } | > | | 8056 8057 8058 8059 8060 8061 8062 8063 8064 8065 8066 8067 8068 8069 8070 8071 | } /* Open a read-only cursor, execute the OP_Count, close the cursor. */ sqlite3VdbeAddOp4Int(v, OP_OpenRead, iCsr, (int)iRoot, iDb, 1); if( pKeyInfo ){ sqlite3VdbeChangeP4(v, -1, (char *)pKeyInfo, P4_KEYINFO); } assignAggregateRegisters(pParse, pAggInfo); sqlite3VdbeAddOp2(v, OP_Count, iCsr, AggInfoFuncReg(pAggInfo,0)); sqlite3VdbeAddOp1(v, OP_Close, iCsr); explainSimpleCount(pParse, pTab, pBest); }else{ int regAcc = 0; /* "populate accumulators" flag */ ExprList *pDistinct = 0; u16 distFlag = 0; int eDist; |
︙ | ︙ | |||
7797 7798 7799 7800 7801 7802 7803 7804 7805 7806 7807 7808 7809 7810 7811 7812 7813 7814 7815 7816 7817 7818 7819 | sqlite3VdbeAddOp2(v, OP_Integer, 0, regAcc); } }else if( pAggInfo->nFunc==1 && pAggInfo->aFunc[0].iDistinct>=0 ){ assert( ExprUseXList(pAggInfo->aFunc[0].pFExpr) ); pDistinct = pAggInfo->aFunc[0].pFExpr->x.pList; distFlag = pDistinct ? (WHERE_WANT_DISTINCT|WHERE_AGG_DISTINCT) : 0; } /* This case runs if the aggregate has no GROUP BY clause. The ** processing is much simpler since there is only a single row ** of output. */ assert( p->pGroupBy==0 ); resetAccumulator(pParse, pAggInfo); /* If this query is a candidate for the min/max optimization, then ** minMaxFlag will have been previously set to either ** WHERE_ORDERBY_MIN or WHERE_ORDERBY_MAX and pMinMaxOrderBy will ** be an appropriate ORDER BY expression for the optimization. */ assert( minMaxFlag==WHERE_ORDERBY_NORMAL || pMinMaxOrderBy!=0 ); assert( pMinMaxOrderBy==0 || pMinMaxOrderBy->nExpr==1 ); | > | | | | 8093 8094 8095 8096 8097 8098 8099 8100 8101 8102 8103 8104 8105 8106 8107 8108 8109 8110 8111 8112 8113 8114 8115 8116 8117 8118 8119 8120 8121 8122 8123 8124 8125 8126 8127 8128 8129 8130 8131 8132 8133 8134 8135 8136 8137 8138 8139 8140 8141 8142 8143 8144 | sqlite3VdbeAddOp2(v, OP_Integer, 0, regAcc); } }else if( pAggInfo->nFunc==1 && pAggInfo->aFunc[0].iDistinct>=0 ){ assert( ExprUseXList(pAggInfo->aFunc[0].pFExpr) ); pDistinct = pAggInfo->aFunc[0].pFExpr->x.pList; distFlag = pDistinct ? (WHERE_WANT_DISTINCT|WHERE_AGG_DISTINCT) : 0; } assignAggregateRegisters(pParse, pAggInfo); /* This case runs if the aggregate has no GROUP BY clause. The ** processing is much simpler since there is only a single row ** of output. */ assert( p->pGroupBy==0 ); resetAccumulator(pParse, pAggInfo); /* If this query is a candidate for the min/max optimization, then ** minMaxFlag will have been previously set to either ** WHERE_ORDERBY_MIN or WHERE_ORDERBY_MAX and pMinMaxOrderBy will ** be an appropriate ORDER BY expression for the optimization. */ assert( minMaxFlag==WHERE_ORDERBY_NORMAL || pMinMaxOrderBy!=0 ); assert( pMinMaxOrderBy==0 || pMinMaxOrderBy->nExpr==1 ); TREETRACE(0x2,pParse,p,("WhereBegin\n")); pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pMinMaxOrderBy, pDistinct, p, minMaxFlag|distFlag, 0); if( pWInfo==0 ){ goto select_end; } TREETRACE(0x2,pParse,p,("WhereBegin returns\n")); eDist = sqlite3WhereIsDistinct(pWInfo); updateAccumulator(pParse, regAcc, pAggInfo, eDist); if( eDist!=WHERE_DISTINCT_NOOP ){ struct AggInfo_func *pF = pAggInfo->aFunc; if( pF ){ fixDistinctOpenEph(pParse, eDist, pF->iDistinct, pF->iDistAddr); } } if( regAcc ) sqlite3VdbeAddOp2(v, OP_Integer, 1, regAcc); if( minMaxFlag ){ sqlite3WhereMinMaxOptEarlyOut(v, pWInfo); } TREETRACE(0x2,pParse,p,("WhereEnd\n")); sqlite3WhereEnd(pWInfo); finalizeAggFunctions(pParse, pAggInfo); } sSort.pOrderBy = 0; sqlite3ExprIfFalse(pParse, pHaving, addrEnd, SQLITE_JUMPIFNULL); selectInnerLoop(pParse, p, -1, 0, 0, |
︙ | ︙ | |||
7855 7856 7857 7858 7859 7860 7861 | explainTempTable(pParse, "DISTINCT"); } /* If there is an ORDER BY clause, then we need to sort the results ** and send them to the callback one by one. */ if( sSort.pOrderBy ){ | < < | 8152 8153 8154 8155 8156 8157 8158 8159 8160 8161 8162 8163 8164 8165 | explainTempTable(pParse, "DISTINCT"); } /* If there is an ORDER BY clause, then we need to sort the results ** and send them to the callback one by one. */ if( sSort.pOrderBy ){ assert( p->pEList==pEList ); generateSortTail(pParse, p, &sSort, pEList->nExpr, pDest); } /* Jump here to skip this query */ sqlite3VdbeResolveLabel(v, iEnd); |
︙ | ︙ | |||
7880 7881 7882 7883 7884 7885 7886 | assert( db->mallocFailed==0 || db->mallocFailed==1 ); assert( db->mallocFailed==0 || pParse->nErr!=0 ); sqlite3ExprListDelete(db, pMinMaxOrderBy); #ifdef SQLITE_DEBUG if( pAggInfo && !db->mallocFailed ){ for(i=0; i<pAggInfo->nColumn; i++){ Expr *pExpr = pAggInfo->aCol[i].pCExpr; | | | | | 8175 8176 8177 8178 8179 8180 8181 8182 8183 8184 8185 8186 8187 8188 8189 8190 8191 8192 8193 8194 8195 8196 8197 8198 8199 8200 8201 8202 8203 | assert( db->mallocFailed==0 || db->mallocFailed==1 ); assert( db->mallocFailed==0 || pParse->nErr!=0 ); sqlite3ExprListDelete(db, pMinMaxOrderBy); #ifdef SQLITE_DEBUG if( pAggInfo && !db->mallocFailed ){ for(i=0; i<pAggInfo->nColumn; i++){ Expr *pExpr = pAggInfo->aCol[i].pCExpr; if( pExpr==0 ) continue; assert( pExpr->pAggInfo==pAggInfo ); assert( pExpr->iAgg==i ); } for(i=0; i<pAggInfo->nFunc; i++){ Expr *pExpr = pAggInfo->aFunc[i].pFExpr; assert( pExpr!=0 ); assert( pExpr->pAggInfo==pAggInfo ); assert( pExpr->iAgg==i ); } } #endif #if TREETRACE_ENABLED TREETRACE(0x1,pParse,p,("end processing\n")); if( (sqlite3TreeTrace & 0x40000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ sqlite3TreeViewSelect(0, p, 0); } #endif ExplainQueryPlanPop(pParse); return rc; } |
Changes to src/shell.c.in.
︙ | ︙ | |||
17 18 19 20 21 22 23 | #define _CRT_SECURE_NO_WARNINGS #endif typedef unsigned int u32; typedef unsigned short int u16; /* ** Optionally #include a user-defined header, whereby compilation options | | | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #define _CRT_SECURE_NO_WARNINGS #endif typedef unsigned int u32; typedef unsigned short int u16; /* ** Optionally #include a user-defined header, whereby compilation options ** may be set prior to where they take effect, but after platform setup. ** If SQLITE_CUSTOM_INCLUDE=? is defined, its value names the #include ** file. Note that this macro has a like effect on sqlite3.c compilation. */ # define SHELL_STRINGIFY_(f) #f # define SHELL_STRINGIFY(f) SHELL_STRINGIFY_(f) #ifdef SQLITE_CUSTOM_INCLUDE # include SHELL_STRINGIFY(SQLITE_CUSTOM_INCLUDE) |
︙ | ︙ | |||
463 464 465 466 467 468 469 | */ static char *Argv0; /* ** Prompt strings. Initialized in main. Settable with ** .prompt main continue */ | > > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 | */ static char *Argv0; /* ** Prompt strings. Initialized in main. Settable with ** .prompt main continue */ #define PROMPT_LEN_MAX 20 /* First line prompt. default: "sqlite> " */ static char mainPrompt[PROMPT_LEN_MAX]; /* Continuation prompt. default: " ...> " */ static char continuePrompt[PROMPT_LEN_MAX]; /* ** Optionally disable dynamic continuation prompt. ** Unless disabled, the continuation prompt shows open SQL lexemes if any, ** or open parentheses level if non-zero, or continuation prompt as set. ** This facility interacts with the scanner and process_input() where the ** below 5 macros are used. */ #ifdef SQLITE_OMIT_DYNAPROMPT # define CONTINUATION_PROMPT continuePrompt # define CONTINUE_PROMPT_RESET # define CONTINUE_PROMPT_AWAITS(p,s) # define CONTINUE_PROMPT_AWAITC(p,c) # define CONTINUE_PAREN_INCR(p,n) # define CONTINUE_PROMPT_PSTATE 0 typedef void *t_NoDynaPrompt; # define SCAN_TRACKER_REFTYPE t_NoDynaPrompt #else # define CONTINUATION_PROMPT dynamicContinuePrompt() # define CONTINUE_PROMPT_RESET \ do {setLexemeOpen(&dynPrompt,0,0); trackParenLevel(&dynPrompt,0);} while(0) # define CONTINUE_PROMPT_AWAITS(p,s) \ if(p && stdin_is_interactive) setLexemeOpen(p, s, 0) # define CONTINUE_PROMPT_AWAITC(p,c) \ if(p && stdin_is_interactive) setLexemeOpen(p, 0, c) # define CONTINUE_PAREN_INCR(p,n) \ if(p && stdin_is_interactive) (trackParenLevel(p,n)) # define CONTINUE_PROMPT_PSTATE (&dynPrompt) typedef struct DynaPrompt *t_DynaPromptRef; # define SCAN_TRACKER_REFTYPE t_DynaPromptRef static struct DynaPrompt { char dynamicPrompt[PROMPT_LEN_MAX]; char acAwait[2]; int inParenLevel; char *zScannerAwaits; } dynPrompt = { {0}, {0}, 0, 0 }; /* Record parenthesis nesting level change, or force level to 0. */ static void trackParenLevel(struct DynaPrompt *p, int ni){ p->inParenLevel += ni; if( ni==0 ) p->inParenLevel = 0; p->zScannerAwaits = 0; } /* Record that a lexeme is opened, or closed with args==0. */ static void setLexemeOpen(struct DynaPrompt *p, char *s, char c){ if( s!=0 || c==0 ){ p->zScannerAwaits = s; p->acAwait[0] = 0; }else{ p->acAwait[0] = c; p->zScannerAwaits = p->acAwait; } } /* Upon demand, derive the continuation prompt to display. */ static char *dynamicContinuePrompt(void){ if( continuePrompt[0]==0 || (dynPrompt.zScannerAwaits==0 && dynPrompt.inParenLevel == 0) ){ return continuePrompt; }else{ if( dynPrompt.zScannerAwaits ){ size_t ncp = strlen(continuePrompt); size_t ndp = strlen(dynPrompt.zScannerAwaits); if( ndp > ncp-3 ) return continuePrompt; strcpy(dynPrompt.dynamicPrompt, dynPrompt.zScannerAwaits); while( ndp<3 ) dynPrompt.dynamicPrompt[ndp++] = ' '; strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3, PROMPT_LEN_MAX-4); }else{ if( dynPrompt.inParenLevel>9 ){ strncpy(dynPrompt.dynamicPrompt, "(..", 4); }else if( dynPrompt.inParenLevel<0 ){ strncpy(dynPrompt.dynamicPrompt, ")x!", 4); }else{ strncpy(dynPrompt.dynamicPrompt, "(x.", 4); dynPrompt.dynamicPrompt[2] = (char)('0'+dynPrompt.inParenLevel); } strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3, PROMPT_LEN_MAX-4); } } return dynPrompt.dynamicPrompt; } #endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */ /* ** Render output like fprintf(). Except, if the output is going to the ** console and if this is running on a Windows machine, translate the ** output from UTF-8 into MBCS. */ #if defined(_WIN32) || defined(WIN32) |
︙ | ︙ | |||
725 726 727 728 729 730 731 | #ifndef SQLITE_SHELL_FIDDLE static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ char *zPrompt; char *zResult; if( in!=0 ){ zResult = local_getline(zPrior, in); }else{ | | | 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 | #ifndef SQLITE_SHELL_FIDDLE static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ char *zPrompt; char *zResult; if( in!=0 ){ zResult = local_getline(zPrior, in); }else{ zPrompt = isContinuation ? CONTINUATION_PROMPT : mainPrompt; #if SHELL_USE_LOCAL_GETLINE printf("%s", zPrompt); fflush(stdout); zResult = local_getline(zPrior, stdin); #else free(zPrior); zResult = shell_readline(zPrompt); |
︙ | ︙ | |||
942 943 944 945 946 947 948 | int nVal, sqlite3_value **apVal ){ const char *zName; char *zFake; UNUSED_PARAMETER(nVal); zName = (const char*)sqlite3_value_text(apVal[0]); | | | 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 | int nVal, sqlite3_value **apVal ){ const char *zName; char *zFake; UNUSED_PARAMETER(nVal); zName = (const char*)sqlite3_value_text(apVal[0]); zFake = zName? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0; if( zFake ){ sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake), -1, sqlite3_free); free(zFake); } } |
︙ | ︙ | |||
1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 | INCLUDE test_windirent.c #define dirent DIRENT #endif INCLUDE ../ext/misc/memtrace.c INCLUDE ../ext/misc/shathree.c INCLUDE ../ext/misc/uint.c INCLUDE ../ext/misc/decimal.c INCLUDE ../ext/misc/ieee754.c INCLUDE ../ext/misc/series.c INCLUDE ../ext/misc/regexp.c #ifndef SQLITE_SHELL_FIDDLE INCLUDE ../ext/misc/fileio.c INCLUDE ../ext/misc/completion.c INCLUDE ../ext/misc/appendvfs.c | > > > > > > > | 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 | INCLUDE test_windirent.c #define dirent DIRENT #endif INCLUDE ../ext/misc/memtrace.c INCLUDE ../ext/misc/shathree.c INCLUDE ../ext/misc/uint.c INCLUDE ../ext/misc/decimal.c #undef sqlite3_base_init #define sqlite3_base_init sqlite3_base64_init INCLUDE ../ext/misc/base64.c #undef sqlite3_base_init #define sqlite3_base_init sqlite3_base85_init #define OMIT_BASE85_CHECKER INCLUDE ../ext/misc/base85.c INCLUDE ../ext/misc/ieee754.c INCLUDE ../ext/misc/series.c INCLUDE ../ext/misc/regexp.c #ifndef SQLITE_SHELL_FIDDLE INCLUDE ../ext/misc/fileio.c INCLUDE ../ext/misc/completion.c INCLUDE ../ext/misc/appendvfs.c |
︙ | ︙ | |||
1062 1063 1064 1065 1066 1067 1068 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) #define SQLITE_SHELL_HAVE_RECOVER 1 #else #define SQLITE_SHELL_HAVE_RECOVER 0 #endif #if SQLITE_SHELL_HAVE_RECOVER | | > | > > > > | 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) #define SQLITE_SHELL_HAVE_RECOVER 1 #else #define SQLITE_SHELL_HAVE_RECOVER 0 #endif #if SQLITE_SHELL_HAVE_RECOVER INCLUDE ../ext/recover/sqlite3recover.h # ifndef SQLITE_HAVE_SQLITE3R INCLUDE ../ext/recover/dbdata.c INCLUDE ../ext/recover/sqlite3recover.c # endif #endif #ifdef SQLITE_SHELL_EXTSRC # include SHELL_STRINGIFY(SQLITE_SHELL_EXTSRC) #endif #if defined(SQLITE_ENABLE_SESSION) /* ** State information for a single open session */ typedef struct OpenSession OpenSession; |
︙ | ︙ | |||
1492 1493 1494 1495 1496 1497 1498 | ** \r\n back into \n */ for(i=j=0; i<sz; i++){ if( p[i]=='\r' && p[i+1]=='\n' ) i++; p[j++] = p[i]; } sz = j; p[sz] = 0; | | | 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 | ** \r\n back into \n */ for(i=j=0; i<sz; i++){ if( p[i]=='\r' && p[i+1]=='\n' ) i++; p[j++] = p[i]; } sz = j; p[sz] = 0; } sqlite3_result_text64(context, (const char*)p, sz, sqlite3_free, SQLITE_UTF8); } p = 0; edit_func_end: if( f ) fclose(f); |
︙ | ︙ | |||
2073 2074 2075 2076 2077 2078 2079 | } } } /* ** Display and reset the EXPLAIN QUERY PLAN data */ | | > > | 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 | } } } /* ** Display and reset the EXPLAIN QUERY PLAN data */ static void eqp_render(ShellState *p, i64 nCycle){ EQPGraphRow *pRow = p->sGraph.pRow; if( pRow ){ if( pRow->zText[0]=='-' ){ if( pRow->pNext==0 ){ eqp_reset(p); return; } utf8_printf(p->out, "%s\n", pRow->zText+3); p->sGraph.pRow = pRow->pNext; sqlite3_free(pRow); }else if( nCycle>0 ){ utf8_printf(p->out, "QUERY PLAN (cycles=%lld [100%%])\n", nCycle); }else{ utf8_printf(p->out, "QUERY PLAN\n"); } p->sGraph.zPrefix[0] = 0; eqp_render_level(p, 0); eqp_reset(p); } |
︙ | ︙ | |||
2665 2666 2667 2668 2669 2670 2671 | len = 78; while( (zSql[len]&0xc0)==0x80 ) len--; } zCode = sqlite3_mprintf("%.*s", len, zSql); shell_check_oom(zCode); for(i=0; zCode[i]; i++){ if( IsSpace(zSql[i]) ) zCode[i] = ' '; } if( iOffset<25 ){ | | | | 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 | len = 78; while( (zSql[len]&0xc0)==0x80 ) len--; } zCode = sqlite3_mprintf("%.*s", len, zSql); shell_check_oom(zCode); for(i=0; zCode[i]; i++){ if( IsSpace(zSql[i]) ) zCode[i] = ' '; } if( iOffset<25 ){ zMsg = sqlite3_mprintf("\n %z\n %*s^--- error here", zCode,iOffset,""); }else{ zMsg = sqlite3_mprintf("\n %z\n %*serror here ---^", zCode,iOffset-14,""); } return zMsg; } /* ** Execute a query statement that will generate SQL output. Print |
︙ | ︙ | |||
2855 2856 2857 2858 2859 2860 2861 | utf8_printf(out, "%-36s %s\n", z, sqlite3_column_origin_name(pStmt,i)); #endif } } if( pArg->statsOn==3 ){ if( pArg->pStmt ){ | | | 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 | utf8_printf(out, "%-36s %s\n", z, sqlite3_column_origin_name(pStmt,i)); #endif } } if( pArg->statsOn==3 ){ if( pArg->pStmt ){ iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP,bReset); raw_printf(pArg->out, "VM-steps: %d\n", iCur); } return 0; } displayStatLine(pArg, "Memory Used:", "%lld (max %lld) bytes", SQLITE_STATUS_MEMORY_USED, bReset); |
︙ | ︙ | |||
2936 2937 2938 2939 2940 2941 2942 | iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP, bReset); raw_printf(pArg->out, "Fullscan Steps: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_SORT, bReset); raw_printf(pArg->out, "Sort Operations: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX,bReset); raw_printf(pArg->out, "Autoindex Inserts: %d\n", iCur); | | > | > | 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 | iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP, bReset); raw_printf(pArg->out, "Fullscan Steps: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_SORT, bReset); raw_printf(pArg->out, "Sort Operations: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX,bReset); raw_printf(pArg->out, "Autoindex Inserts: %d\n", iCur); iHit = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_HIT, bReset); iMiss = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_MISS, bReset); if( iHit || iMiss ){ raw_printf(pArg->out, "Bloom filter bypass taken: %d/%d\n", iHit, iHit+iMiss); } iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset); raw_printf(pArg->out, "Virtual Machine Steps: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_REPREPARE,bReset); |
︙ | ︙ | |||
2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 | #endif /* Do not remove this machine readable comment: extra-stats-output-here */ return 0; } /* ** Display scan stats. */ static void display_scanstats( sqlite3 *db, /* Database to query */ ShellState *pArg /* Pointer to ShellState */ ){ #ifndef SQLITE_ENABLE_STMT_SCANSTATUS UNUSED_PARAMETER(db); UNUSED_PARAMETER(pArg); #else | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > | > > | > > > > > > > | < > | < | > | | > > | > > > | | | > > > > | > > | > | > | > > > > | | | | > | | < | | > | | > | > > > | > | 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 | #endif /* Do not remove this machine readable comment: extra-stats-output-here */ return 0; } #ifdef SQLITE_ENABLE_STMT_SCANSTATUS static int scanStatsHeight(sqlite3_stmt *p, int iEntry){ int iPid = 0; int ret = 1; sqlite3_stmt_scanstatus_v2(p, iEntry, SQLITE_SCANSTAT_SELECTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iPid ); while( iPid!=0 ){ int ii; for(ii=0; 1; ii++){ int iId; int res; res = sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_SELECTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iId ); if( res ) break; if( iId==iPid ){ sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_PARENTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iPid ); } } ret++; } return ret; } #endif /* ** Display scan stats. */ static void display_scanstats( sqlite3 *db, /* Database to query */ ShellState *pArg /* Pointer to ShellState */ ){ #ifndef SQLITE_ENABLE_STMT_SCANSTATUS UNUSED_PARAMETER(db); UNUSED_PARAMETER(pArg); #else static const int f = SQLITE_SCANSTAT_COMPLEX; sqlite3_stmt *p = pArg->pStmt; int ii = 0; i64 nTotal = 0; int nWidth = 0; eqp_reset(pArg); for(ii=0; 1; ii++){ const char *z = 0; int n = 0; if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){ break; } n = strlen(z) + scanStatsHeight(p, ii)*3; if( n>nWidth ) nWidth = n; } nWidth += 4; sqlite3_stmt_scanstatus_v2(p, -1, SQLITE_SCANSTAT_NCYCLE, f, (void*)&nTotal); for(ii=0; 1; ii++){ i64 nLoop = 0; i64 nRow = 0; i64 nCycle = 0; int iId = 0; int iPid = 0; const char *z = 0; const char *zName = 0; char *zText = 0; double rEst = 0.0; if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){ break; } sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_EST,f,(void*)&rEst); sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NLOOP,f,(void*)&nLoop); sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NVISIT,f,(void*)&nRow); sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NCYCLE,f,(void*)&nCycle); sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_SELECTID,f,(void*)&iId); sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_PARENTID,f,(void*)&iPid); sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NAME,f,(void*)&zName); zText = sqlite3_mprintf("%s", z); if( nCycle>=0 || nLoop>=0 || nRow>=0 ){ char *z = 0; if( nCycle>=0 && nTotal>0 ){ z = sqlite3_mprintf("%zcycles=%lld [%d%%]", z, nCycle, ((nCycle*100)+nTotal/2) / nTotal ); } if( nLoop>=0 ){ z = sqlite3_mprintf("%z%sloops=%lld", z, z ? " " : "", nLoop); } if( nRow>=0 ){ z = sqlite3_mprintf("%z%srows=%lld", z, z ? " " : "", nRow); } if( zName && pArg->scanstatsOn>1 ){ double rpl = (double)nRow / (double)nLoop; z = sqlite3_mprintf("%z rpl=%.1f est=%.1f", z, rpl, rEst); } zText = sqlite3_mprintf( "% *z (%z)", -1*(nWidth-scanStatsHeight(p, ii)*3), zText, z ); } eqp_append(pArg, iId, iPid, zText); sqlite3_free(zText); } eqp_render(pArg, nTotal); #endif } /* ** Parameter azArray points to a zero-terminated array of strings. zStr ** points to a single nul-terminated string. Return non-zero if zStr ** is equal, according to strcmp(), to any of the strings in the array. |
︙ | ︙ | |||
3245 3246 3247 3248 3249 3250 3251 | #define BOX_124 "\342\224\264" /* U+2534 -'- */ #define BOX_1234 "\342\224\274" /* U+253c -|- */ /* Draw horizontal line N characters long using unicode box ** characters */ static void print_box_line(FILE *out, int N){ | | | 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 | #define BOX_124 "\342\224\264" /* U+2534 -'- */ #define BOX_1234 "\342\224\274" /* U+253c -|- */ /* Draw horizontal line N characters long using unicode box ** characters */ static void print_box_line(FILE *out, int N){ const char zDash[] = BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24; const int nDash = sizeof(zDash) - 1; N *= 3; while( N>nDash ){ utf8_printf(out, zDash); N -= nDash; |
︙ | ︙ | |||
3374 3375 3376 3377 3378 3379 3380 | }while( (n&7)!=0 && n<mxWidth ); i++; continue; } break; } zOut[j] = 0; | | | 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 | }while( (n&7)!=0 && n<mxWidth ); i++; continue; } break; } zOut[j] = 0; return (char*)zOut; } /* Extract the value of the i-th current column for pStmt as an SQL literal ** value. Memory is obtained from sqlite3_malloc64() and must be freed by ** the caller. */ static char *quoted_column(sqlite3_stmt *pStmt, int i){ |
︙ | ︙ | |||
3735 3736 3737 3738 3739 3740 3741 | ** ** If successful, SQLITE_OK is returned. Otherwise, an SQLite error ** code. In this case, (*pzErr) may be set to point to a buffer containing ** an English language error message. It is the responsibility of the ** caller to eventually free this buffer using sqlite3_free(). */ static int expertHandleSQL( | | | | | 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 | ** ** If successful, SQLITE_OK is returned. Otherwise, an SQLite error ** code. In this case, (*pzErr) may be set to point to a buffer containing ** an English language error message. It is the responsibility of the ** caller to eventually free this buffer using sqlite3_free(). */ static int expertHandleSQL( ShellState *pState, const char *zSql, char **pzErr ){ assert( pState->expert.pExpert ); assert( pzErr==0 || *pzErr==0 ); return sqlite3_expert_sql(pState->expert.pExpert, zSql, pzErr); } /* ** This function is called either to silently clean up the object ** created by the ".expert" command (if bCancel==1), or to generate a ** report from it and then clean it up (if bCancel==0). ** ** If successful, SQLITE_OK is returned. Otherwise, an SQLite error ** code. In this case, (*pzErr) may be set to point to a buffer containing ** an English language error message. It is the responsibility of the ** caller to eventually free this buffer using sqlite3_free(). */ |
︙ | ︙ | |||
3841 3842 3843 3844 3845 3846 3847 | rc = SQLITE_ERROR; } } if( rc==SQLITE_OK ){ pState->expert.pExpert = sqlite3_expert_new(pState->db, &zErr); if( pState->expert.pExpert==0 ){ | | > | 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 | rc = SQLITE_ERROR; } } if( rc==SQLITE_OK ){ pState->expert.pExpert = sqlite3_expert_new(pState->db, &zErr); if( pState->expert.pExpert==0 ){ raw_printf(stderr, "sqlite3_expert_new: %s\n", zErr ? zErr : "out of memory"); rc = SQLITE_ERROR; }else{ sqlite3_expert_config( pState->expert.pExpert, EXPERT_CONFIG_SAMPLE, iSample ); } } |
︙ | ︙ | |||
3929 3930 3931 3932 3933 3934 3935 | rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0); if( rc==SQLITE_OK ){ while( sqlite3_step(pExplain)==SQLITE_ROW ){ const char *zEQPLine = (const char*)sqlite3_column_text(pExplain,3); int iEqpId = sqlite3_column_int(pExplain, 0); int iParentId = sqlite3_column_int(pExplain, 1); if( zEQPLine==0 ) zEQPLine = ""; | | | | 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 | rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0); if( rc==SQLITE_OK ){ while( sqlite3_step(pExplain)==SQLITE_ROW ){ const char *zEQPLine = (const char*)sqlite3_column_text(pExplain,3); int iEqpId = sqlite3_column_int(pExplain, 0); int iParentId = sqlite3_column_int(pExplain, 1); if( zEQPLine==0 ) zEQPLine = ""; if( zEQPLine[0]=='-' ) eqp_render(pArg, 0); eqp_append(pArg, iEqpId, iParentId, zEQPLine); } eqp_render(pArg, 0); } sqlite3_finalize(pExplain); sqlite3_free(zEQP); if( pArg->autoEQP>=AUTOEQP_full ){ /* Also do an EXPLAIN for ".eqp full" mode */ zEQP = sqlite3_mprintf("EXPLAIN %s", zStmtSql); shell_check_oom(zEQP); |
︙ | ︙ | |||
3981 3982 3983 3984 3985 3986 3987 | explain_data_prepare(pArg, pStmt); } } bind_prepared_stmt(pArg, pStmt); exec_prepared_stmt(pArg, pStmt); explain_data_delete(pArg); | | | 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 | explain_data_prepare(pArg, pStmt); } } bind_prepared_stmt(pArg, pStmt); exec_prepared_stmt(pArg, pStmt); explain_data_delete(pArg); eqp_render(pArg, 0); /* print usage stats if stats on */ if( pArg && pArg->statsOn ){ display_stats(db, pArg, 0); } /* print loop-counters if required */ |
︙ | ︙ | |||
4521 4522 4523 4524 4525 4526 4527 | " --no-rowids Do not attempt to recover rowid values", " that are not also INTEGER PRIMARY KEYs", #endif #ifndef SQLITE_SHELL_FIDDLE ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE", ".save ?OPTIONS? FILE Write database to FILE (an alias for .backup ...)", #endif | | | 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 | " --no-rowids Do not attempt to recover rowid values", " that are not also INTEGER PRIMARY KEYs", #endif #ifndef SQLITE_SHELL_FIDDLE ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE", ".save ?OPTIONS? FILE Write database to FILE (an alias for .backup ...)", #endif ".scanstats on|off|est Turn sqlite3_stmt_scanstatus() metrics on or off", ".schema ?PATTERN? Show the CREATE statements matching PATTERN", " Options:", " --indent Try to pretty-print the schema", " --nosys Omit objects whose names start with \"sqlite_\"", ".selftest ?OPTIONS? Run tests defined in the SELFTEST table", " Options:", " --init Create a new SELFTEST table", |
︙ | ︙ | |||
4802 4803 4804 4805 4806 4807 4808 | && zBuf[3]==0x06 ){ rc = SHELL_OPEN_ZIPFILE; }else if( n==0 && dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){ rc = SHELL_OPEN_ZIPFILE; } } fclose(f); | | | 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 | && zBuf[3]==0x06 ){ rc = SHELL_OPEN_ZIPFILE; }else if( n==0 && dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){ rc = SHELL_OPEN_ZIPFILE; } } fclose(f); return rc; } #ifndef SQLITE_OMIT_DESERIALIZE /* ** Reconstruct an in-memory database using the output from the "dbtotxt" ** program. Read content from the file in p->aAuxDb[].zDbFilename. ** If p->aAuxDb[].zDbFilename is 0, then read from standard input. |
︙ | ︙ | |||
4901 4902 4903 4904 4905 4906 4907 | /* ** Scalar function "shell_int32". The first argument to this function ** must be a blob. The second a non-negative integer. This function ** reads and returns a 32-bit big-endian integer from byte ** offset (4*<arg2>) of the blob. */ static void shellInt32( | | | | 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 | /* ** Scalar function "shell_int32". The first argument to this function ** must be a blob. The second a non-negative integer. This function ** reads and returns a 32-bit big-endian integer from byte ** offset (4*<arg2>) of the blob. */ static void shellInt32( sqlite3_context *context, int argc, sqlite3_value **argv ){ const unsigned char *pBlob; int nBlob; int iInt; UNUSED_PARAMETER(argc); |
︙ | ︙ | |||
4929 4930 4931 4932 4933 4934 4935 | } /* ** Scalar function "shell_idquote(X)" returns string X quoted as an identifier, ** using "..." with internal double-quote characters doubled. */ static void shellIdQuote( | | | | | | | | | 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151 5152 5153 5154 5155 | } /* ** Scalar function "shell_idquote(X)" returns string X quoted as an identifier, ** using "..." with internal double-quote characters doubled. */ static void shellIdQuote( sqlite3_context *context, int argc, sqlite3_value **argv ){ const char *zName = (const char*)sqlite3_value_text(argv[0]); UNUSED_PARAMETER(argc); if( zName ){ char *z = sqlite3_mprintf("\"%w\"", zName); sqlite3_result_text(context, z, -1, sqlite3_free); } } /* ** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X. */ static void shellUSleepFunc( sqlite3_context *context, int argcUnused, sqlite3_value **argv ){ int sleep = sqlite3_value_int(argv[0]); (void)argcUnused; sqlite3_sleep(sleep/1000); sqlite3_result_int(context, sleep); } /* ** Scalar function "shell_escape_crnl" used by the .recover command. ** The argument passed to this function is the output of built-in ** function quote(). If the first character of the input is "'", ** indicating that the value passed to quote() was a text value, ** then this function searches the input for "\n" and "\r" characters ** and adds a wrapper similar to the following: ** ** replace(replace(<input>, '\n', char(10), '\r', char(13)); ** ** Or, if the first character of the input is not "'", then a copy ** of the input is returned. */ static void shellEscapeCrnl( sqlite3_context *context, int argc, sqlite3_value **argv ){ const char *zText = (const char*)sqlite3_value_text(argv[0]); UNUSED_PARAMETER(argc); if( zText && zText[0]=='\'' ){ i64 nText = sqlite3_value_bytes(argv[0]); i64 i; |
︙ | ︙ | |||
5070 5071 5072 5073 5074 5075 5076 | static void open_db(ShellState *p, int openFlags){ if( p->db==0 ){ const char *zDbFilename = p->pAuxDb->zDbFilename; if( p->openMode==SHELL_OPEN_UNSPEC ){ if( zDbFilename==0 || zDbFilename[0]==0 ){ p->openMode = SHELL_OPEN_NORMAL; }else{ | | | | 5241 5242 5243 5244 5245 5246 5247 5248 5249 5250 5251 5252 5253 5254 5255 5256 5257 5258 5259 5260 5261 | static void open_db(ShellState *p, int openFlags){ if( p->db==0 ){ const char *zDbFilename = p->pAuxDb->zDbFilename; if( p->openMode==SHELL_OPEN_UNSPEC ){ if( zDbFilename==0 || zDbFilename[0]==0 ){ p->openMode = SHELL_OPEN_NORMAL; }else{ p->openMode = (u8)deduceDatabaseType(zDbFilename, (openFlags & OPEN_DB_ZIPFILE)!=0); } } switch( p->openMode ){ case SHELL_OPEN_APPENDVFS: { sqlite3_open_v2(zDbFilename, &p->db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, "apndvfs"); break; } case SHELL_OPEN_HEXDB: case SHELL_OPEN_DESERIALIZE: { sqlite3_open(0, &p->db); break; |
︙ | ︙ | |||
5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 | zDbFilename, sqlite3_errmsg(p->db)); if( openFlags & OPEN_DB_KEEPALIVE ){ sqlite3_open(":memory:", &p->db); return; } exit(1); } #ifndef SQLITE_OMIT_LOAD_EXTENSION sqlite3_enable_load_extension(p->db, 1); #endif sqlite3_shathree_init(p->db, 0, 0); sqlite3_uint_init(p->db, 0, 0); sqlite3_decimal_init(p->db, 0, 0); sqlite3_regexp_init(p->db, 0, 0); sqlite3_ieee_init(p->db, 0, 0); sqlite3_series_init(p->db, 0, 0); #ifndef SQLITE_SHELL_FIDDLE sqlite3_fileio_init(p->db, 0, 0); sqlite3_completion_init(p->db, 0, 0); #endif #if SQLITE_SHELL_HAVE_RECOVER sqlite3_dbdata_init(p->db, 0, 0); #endif #ifdef SQLITE_HAVE_ZLIB if( !p->bSafeModePersist ){ sqlite3_zipfile_init(p->db, 0, 0); sqlite3_sqlar_init(p->db, 0, 0); } #endif sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0, shellAddSchemaName, 0, 0); sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0, shellModuleSchema, 0, 0); sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p, shellPutsFunc, 0, 0); sqlite3_create_function(p->db, "shell_escape_crnl", 1, SQLITE_UTF8, 0, shellEscapeCrnl, 0, 0); sqlite3_create_function(p->db, "shell_int32", 2, SQLITE_UTF8, 0, shellInt32, 0, 0); sqlite3_create_function(p->db, "shell_idquote", 1, SQLITE_UTF8, 0, shellIdQuote, 0, 0); sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0, shellUSleepFunc, 0, 0); #ifndef SQLITE_NOHAVE_SYSTEM sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0, editFunc, 0, 0); sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0, editFunc, 0, 0); #endif if( p->openMode==SHELL_OPEN_ZIPFILE ){ char *zSql = sqlite3_mprintf( "CREATE VIRTUAL TABLE zip USING zipfile(%Q);", zDbFilename); shell_check_oom(zSql); sqlite3_exec(p->db, zSql, 0, 0, 0); sqlite3_free(zSql); } | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 5282 5283 5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295 5296 5297 5298 5299 5300 5301 5302 5303 5304 5305 5306 5307 5308 5309 5310 5311 5312 5313 5314 5315 5316 5317 5318 5319 5320 5321 5322 5323 5324 5325 5326 5327 5328 5329 5330 5331 5332 5333 5334 5335 5336 5337 5338 5339 5340 5341 5342 5343 5344 5345 5346 5347 5348 5349 5350 5351 5352 5353 5354 5355 5356 5357 5358 5359 5360 5361 5362 5363 5364 5365 5366 5367 5368 5369 | zDbFilename, sqlite3_errmsg(p->db)); if( openFlags & OPEN_DB_KEEPALIVE ){ sqlite3_open(":memory:", &p->db); return; } exit(1); } #ifndef SQLITE_OMIT_LOAD_EXTENSION sqlite3_enable_load_extension(p->db, 1); #endif sqlite3_shathree_init(p->db, 0, 0); sqlite3_uint_init(p->db, 0, 0); sqlite3_decimal_init(p->db, 0, 0); sqlite3_base64_init(p->db, 0, 0); sqlite3_base85_init(p->db, 0, 0); sqlite3_regexp_init(p->db, 0, 0); sqlite3_ieee_init(p->db, 0, 0); sqlite3_series_init(p->db, 0, 0); #ifndef SQLITE_SHELL_FIDDLE sqlite3_fileio_init(p->db, 0, 0); sqlite3_completion_init(p->db, 0, 0); #endif #if SQLITE_SHELL_HAVE_RECOVER sqlite3_dbdata_init(p->db, 0, 0); #endif #ifdef SQLITE_HAVE_ZLIB if( !p->bSafeModePersist ){ sqlite3_zipfile_init(p->db, 0, 0); sqlite3_sqlar_init(p->db, 0, 0); } #endif #ifdef SQLITE_SHELL_EXTFUNCS /* Create a preprocessing mechanism for extensions to make * their own provisions for being built into the shell. * This is a short-span macro. See further below for usage. */ #define SHELL_SUB_MACRO(base, variant) base ## _ ## variant #define SHELL_SUBMACRO(base, variant) SHELL_SUB_MACRO(base, variant) /* Let custom-included extensions get their ..._init() called. * The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause * the extension's sqlite3_*_init( db, pzErrorMsg, pApi ) * inititialization routine to be called. */ { int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db); /* Let custom-included extensions expose their functionality. * The WHATEVER_EXPOSE( db, pzErrorMsg ) macro should cause * the SQL functions, virtual tables, collating sequences or * VFS's implemented by the extension to be registered. */ if( irc==SQLITE_OK || irc==SQLITE_OK_LOAD_PERMANENTLY ){ SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, EXPOSE)(p->db, 0); } #undef SHELL_SUB_MACRO #undef SHELL_SUBMACRO } #endif sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0, shellAddSchemaName, 0, 0); sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0, shellModuleSchema, 0, 0); sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p, shellPutsFunc, 0, 0); sqlite3_create_function(p->db, "shell_escape_crnl", 1, SQLITE_UTF8, 0, shellEscapeCrnl, 0, 0); sqlite3_create_function(p->db, "shell_int32", 2, SQLITE_UTF8, 0, shellInt32, 0, 0); sqlite3_create_function(p->db, "shell_idquote", 1, SQLITE_UTF8, 0, shellIdQuote, 0, 0); sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0, shellUSleepFunc, 0, 0); #ifndef SQLITE_NOHAVE_SYSTEM sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0, editFunc, 0, 0); sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0, editFunc, 0, 0); #endif if( p->openMode==SHELL_OPEN_ZIPFILE ){ char *zSql = sqlite3_mprintf( "CREATE VIRTUAL TABLE zip USING zipfile(%Q);", zDbFilename); shell_check_oom(zSql); sqlite3_exec(p->db, zSql, 0, 0, 0); sqlite3_free(zSql); } |
︙ | ︙ | |||
5199 5200 5201 5202 5203 5204 5205 | ** Attempt to close the databaes connection. Report errors. */ void close_db(sqlite3 *db){ int rc = sqlite3_close(db); if( rc ){ utf8_printf(stderr, "Error: sqlite3_close() returns %d: %s\n", rc, sqlite3_errmsg(db)); | | | 5402 5403 5404 5405 5406 5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 | ** Attempt to close the databaes connection. Report errors. */ void close_db(sqlite3 *db){ int rc = sqlite3_close(db); if( rc ){ utf8_printf(stderr, "Error: sqlite3_close() returns %d: %s\n", rc, sqlite3_errmsg(db)); } } #if HAVE_READLINE || HAVE_EDITLINE /* ** Readline completion callbacks */ static char *readline_completion_generator(const char *text, int state){ |
︙ | ︙ | |||
5229 5230 5231 5232 5233 5234 5235 5236 5237 5238 5239 5240 5241 5242 | sqlite3_finalize(pStmt); pStmt = 0; zRet = 0; } return zRet; } static char **readline_completion(const char *zText, int iStart, int iEnd){ rl_attempted_completion_over = 1; return rl_completion_matches(zText, readline_completion_generator); } #elif HAVE_LINENOISE /* ** Linenoise completion callback | > > | 5432 5433 5434 5435 5436 5437 5438 5439 5440 5441 5442 5443 5444 5445 5446 5447 | sqlite3_finalize(pStmt); pStmt = 0; zRet = 0; } return zRet; } static char **readline_completion(const char *zText, int iStart, int iEnd){ (void)iStart; (void)iEnd; rl_attempted_completion_over = 1; return rl_completion_matches(zText, readline_completion_generator); } #elif HAVE_LINENOISE /* ** Linenoise completion callback |
︙ | ︙ | |||
6440 6441 6442 6443 6444 6445 6446 | raw_printf(stderr, "Where sub-commands are:\n"); raw_printf(stderr, " fkey-indexes\n"); return SQLITE_ERROR; } #if !defined SQLITE_OMIT_VIRTUALTABLE static void shellPrepare( | | | | | | | | | 6645 6646 6647 6648 6649 6650 6651 6652 6653 6654 6655 6656 6657 6658 6659 6660 6661 6662 6663 6664 6665 6666 6667 6668 6669 6670 6671 6672 6673 6674 6675 6676 6677 6678 6679 6680 6681 6682 6683 6684 6685 6686 6687 | raw_printf(stderr, "Where sub-commands are:\n"); raw_printf(stderr, " fkey-indexes\n"); return SQLITE_ERROR; } #if !defined SQLITE_OMIT_VIRTUALTABLE static void shellPrepare( sqlite3 *db, int *pRc, const char *zSql, sqlite3_stmt **ppStmt ){ *ppStmt = 0; if( *pRc==SQLITE_OK ){ int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0); if( rc!=SQLITE_OK ){ raw_printf(stderr, "sql error: %s (%d)\n", sqlite3_errmsg(db), sqlite3_errcode(db) ); *pRc = rc; } } } /* ** Create a prepared statement using printf-style arguments for the SQL. ** ** This routine is could be marked "static". But it is not always used, ** depending on compile-time options. By omitting the "static", we avoid ** nuisance compiler warnings about "defined but not used". */ void shellPreparePrintf( sqlite3 *db, int *pRc, sqlite3_stmt **ppStmt, const char *zFmt, ... ){ *ppStmt = 0; if( *pRc==SQLITE_OK ){ va_list ap; char *z; va_start(ap, zFmt); |
︙ | ︙ | |||
6494 6495 6496 6497 6498 6499 6500 | /* Finalize the prepared statement created using shellPreparePrintf(). ** ** This routine is could be marked "static". But it is not always used, ** depending on compile-time options. By omitting the "static", we avoid ** nuisance compiler warnings about "defined but not used". */ void shellFinalize( | | | 6699 6700 6701 6702 6703 6704 6705 6706 6707 6708 6709 6710 6711 6712 6713 | /* Finalize the prepared statement created using shellPreparePrintf(). ** ** This routine is could be marked "static". But it is not always used, ** depending on compile-time options. By omitting the "static", we avoid ** nuisance compiler warnings about "defined but not used". */ void shellFinalize( int *pRc, sqlite3_stmt *pStmt ){ if( pStmt ){ sqlite3 *db = sqlite3_db_handle(pStmt); int rc = sqlite3_finalize(pStmt); if( *pRc==SQLITE_OK ){ if( rc!=SQLITE_OK ){ |
︙ | ︙ | |||
6516 6517 6518 6519 6520 6521 6522 | /* Reset the prepared statement created using shellPreparePrintf(). ** ** This routine is could be marked "static". But it is not always used, ** depending on compile-time options. By omitting the "static", we avoid ** nuisance compiler warnings about "defined but not used". */ void shellReset( | | | 6721 6722 6723 6724 6725 6726 6727 6728 6729 6730 6731 6732 6733 6734 6735 | /* Reset the prepared statement created using shellPreparePrintf(). ** ** This routine is could be marked "static". But it is not always used, ** depending on compile-time options. By omitting the "static", we avoid ** nuisance compiler warnings about "defined but not used". */ void shellReset( int *pRc, sqlite3_stmt *pStmt ){ int rc = sqlite3_reset(pStmt); if( *pRc==SQLITE_OK ){ if( rc!=SQLITE_OK ){ sqlite3 *db = sqlite3_db_handle(pStmt); raw_printf(stderr, "SQL error: %s\n", sqlite3_errmsg(db)); |
︙ | ︙ | |||
6564 6565 6566 6567 6568 6569 6570 | */ static int arUsage(FILE *f){ showHelp(f,"archive"); return SQLITE_ERROR; } /* | | | 6769 6770 6771 6772 6773 6774 6775 6776 6777 6778 6779 6780 6781 6782 6783 | */ static int arUsage(FILE *f){ showHelp(f,"archive"); return SQLITE_ERROR; } /* ** Print an error message for the .ar command to stderr and return ** SQLITE_ERROR. */ static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){ va_list ap; char *z; va_start(ap, zFmt); z = sqlite3_vmprintf(zFmt, ap); |
︙ | ︙ | |||
6645 6646 6647 6648 6649 6650 6651 | return SQLITE_OK; } /* ** Parse the command line for an ".ar" command. The results are written into ** structure (*pAr). SQLITE_OK is returned if the command line is parsed | | | 6850 6851 6852 6853 6854 6855 6856 6857 6858 6859 6860 6861 6862 6863 6864 | return SQLITE_OK; } /* ** Parse the command line for an ".ar" command. The results are written into ** structure (*pAr). SQLITE_OK is returned if the command line is parsed ** successfully, otherwise an error message is written to stderr and ** SQLITE_ERROR returned. */ static int arParseCommand( char **azArg, /* Array of arguments passed to dot command */ int nArg, /* Number of entries in azArg[] */ ArCommand *pAr /* Populate this object */ ){ |
︙ | ︙ | |||
6841 6842 6843 6844 6845 6846 6847 | ** identify all archive members that match the command arguments held ** in (*pAr). Leave this WHERE clause in (*pzWhere) before returning. ** The caller is responsible for eventually calling sqlite3_free() on ** any non-NULL (*pzWhere) value. Here, "match" means strict equality ** when pAr->bGlob is false and GLOB match when pAr->bGlob is true. */ static void arWhereClause( | | | | | | 7046 7047 7048 7049 7050 7051 7052 7053 7054 7055 7056 7057 7058 7059 7060 7061 7062 7063 7064 7065 7066 7067 7068 7069 7070 7071 7072 7073 7074 7075 7076 7077 7078 7079 7080 7081 7082 7083 7084 7085 7086 7087 7088 7089 7090 7091 7092 7093 | ** identify all archive members that match the command arguments held ** in (*pAr). Leave this WHERE clause in (*pzWhere) before returning. ** The caller is responsible for eventually calling sqlite3_free() on ** any non-NULL (*pzWhere) value. Here, "match" means strict equality ** when pAr->bGlob is false and GLOB match when pAr->bGlob is true. */ static void arWhereClause( int *pRc, ArCommand *pAr, char **pzWhere /* OUT: New WHERE clause */ ){ char *zWhere = 0; const char *zSameOp = (pAr->bGlob)? "GLOB" : "="; if( *pRc==SQLITE_OK ){ if( pAr->nArg==0 ){ zWhere = sqlite3_mprintf("1"); }else{ int i; const char *zSep = ""; for(i=0; i<pAr->nArg; i++){ const char *z = pAr->azArg[i]; zWhere = sqlite3_mprintf( "%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'", zWhere, zSep, zSameOp, z, strlen30(z)+1, zSameOp, z ); if( zWhere==0 ){ *pRc = SQLITE_NOMEM; break; } zSep = " OR "; } } } *pzWhere = zWhere; } /* ** Implementation of .ar "lisT" command. */ static int arListCommand(ArCommand *pAr){ const char *zSql = "SELECT %s FROM %s WHERE %s"; const char *azCols[] = { "name", "lsmode(mode), sz, datetime(mtime, 'unixepoch'), name" }; char *zWhere = 0; sqlite3_stmt *pSql = 0; |
︙ | ︙ | |||
6896 6897 6898 6899 6900 6901 6902 | if( pAr->bDryRun ){ utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql)); }else{ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ if( pAr->bVerbose ){ utf8_printf(pAr->p->out, "%s % 10d %s %s\n", sqlite3_column_text(pSql, 0), | | | 7101 7102 7103 7104 7105 7106 7107 7108 7109 7110 7111 7112 7113 7114 7115 | if( pAr->bDryRun ){ utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql)); }else{ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ if( pAr->bVerbose ){ utf8_printf(pAr->p->out, "%s % 10d %s %s\n", sqlite3_column_text(pSql, 0), sqlite3_column_int(pSql, 1), sqlite3_column_text(pSql, 2), sqlite3_column_text(pSql, 3) ); }else{ utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0)); } } |
︙ | ︙ | |||
6953 6954 6955 6956 6957 6958 6959 | } sqlite3_free(zWhere); sqlite3_free(zSql); return rc; } /* | | | | | 7158 7159 7160 7161 7162 7163 7164 7165 7166 7167 7168 7169 7170 7171 7172 7173 7174 7175 7176 7177 7178 7179 7180 7181 7182 | } sqlite3_free(zWhere); sqlite3_free(zSql); return rc; } /* ** Implementation of .ar "eXtract" command. */ static int arExtractCommand(ArCommand *pAr){ const char *zSql1 = "SELECT " " ($dir || name)," " writefile(($dir || name), %s, mode, mtime) " "FROM %s WHERE (%s) AND (data IS NULL OR $dirOnly = 0)" " AND name NOT GLOB '*..[/\\]*'"; const char *azExtraArg[] = { "sqlar_uncompress(data, sz)", "data" }; sqlite3_stmt *pSql = 0; int rc = SQLITE_OK; char *zDir = 0; |
︙ | ︙ | |||
6989 6990 6991 6992 6993 6994 6995 | zDir = sqlite3_mprintf("%s/", pAr->zDir); }else{ zDir = sqlite3_mprintf(""); } if( zDir==0 ) rc = SQLITE_NOMEM; } | | | 7194 7195 7196 7197 7198 7199 7200 7201 7202 7203 7204 7205 7206 7207 7208 | zDir = sqlite3_mprintf("%s/", pAr->zDir); }else{ zDir = sqlite3_mprintf(""); } if( zDir==0 ) rc = SQLITE_NOMEM; } shellPreparePrintf(pAr->db, &rc, &pSql, zSql1, azExtraArg[pAr->bZip], pAr->zSrcTable, zWhere ); if( rc==SQLITE_OK ){ j = sqlite3_bind_parameter_index(pSql, "$dir"); sqlite3_bind_text(pSql, j, zDir, -1, SQLITE_STATIC); |
︙ | ︙ | |||
7067 7068 7069 7070 7071 7072 7073 | ** "update" only overwrites if the size or mtime or mode has changed. */ static int arCreateOrUpdateCommand( ArCommand *pAr, /* Command arguments and options */ int bUpdate, /* true for a --create. */ int bOnlyIfChanged /* Only update if file has changed */ ){ | | | 7272 7273 7274 7275 7276 7277 7278 7279 7280 7281 7282 7283 7284 7285 7286 | ** "update" only overwrites if the size or mtime or mode has changed. */ static int arCreateOrUpdateCommand( ArCommand *pAr, /* Command arguments and options */ int bUpdate, /* true for a --create. */ int bOnlyIfChanged /* Only update if file has changed */ ){ const char *zCreate = "CREATE TABLE IF NOT EXISTS sqlar(\n" " name TEXT PRIMARY KEY, -- name of the file\n" " mode INT, -- access permissions\n" " mtime INT, -- last modification time\n" " sz INT, -- original file size\n" " data BLOB -- compressed content\n" ")"; |
︙ | ︙ | |||
7109 7110 7111 7112 7113 7114 7115 | char *zSql; char zTemp[50]; char *zExists = 0; arExecSql(pAr, "PRAGMA page_size=512"); rc = arExecSql(pAr, "SAVEPOINT ar;"); if( rc!=SQLITE_OK ) return rc; | | | 7314 7315 7316 7317 7318 7319 7320 7321 7322 7323 7324 7325 7326 7327 7328 | char *zSql; char zTemp[50]; char *zExists = 0; arExecSql(pAr, "PRAGMA page_size=512"); rc = arExecSql(pAr, "SAVEPOINT ar;"); if( rc!=SQLITE_OK ) return rc; zTemp[0] = 0; if( pAr->bZip ){ /* Initialize the zipfile virtual table, if necessary */ if( pAr->zFile ){ sqlite3_uint64 r; sqlite3_randomness(sizeof(r),&r); sqlite3_snprintf(sizeof(zTemp),zTemp,"zip%016llx",r); zTab = zTemp; |
︙ | ︙ | |||
7203 7204 7205 7206 7207 7208 7209 | cmd.zSrcTable = sqlite3_mprintf("zipfile(%Q)", cmd.zFile); } } cmd.bZip = 1; }else if( cmd.zFile ){ int flags; if( cmd.bAppend ) eDbType = SHELL_OPEN_APPENDVFS; | | | | | 7408 7409 7410 7411 7412 7413 7414 7415 7416 7417 7418 7419 7420 7421 7422 7423 7424 7425 7426 7427 7428 7429 7430 7431 7432 7433 7434 7435 7436 | cmd.zSrcTable = sqlite3_mprintf("zipfile(%Q)", cmd.zFile); } } cmd.bZip = 1; }else if( cmd.zFile ){ int flags; if( cmd.bAppend ) eDbType = SHELL_OPEN_APPENDVFS; if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_INSERT || cmd.eCmd==AR_CMD_REMOVE || cmd.eCmd==AR_CMD_UPDATE ){ flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE; }else{ flags = SQLITE_OPEN_READONLY; } cmd.db = 0; if( cmd.bDryRun ){ utf8_printf(pState->out, "-- open database '%s'%s\n", cmd.zFile, eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : ""); } rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0); if( rc!=SQLITE_OK ){ utf8_printf(stderr, "cannot open file: %s (%s)\n", cmd.zFile, sqlite3_errmsg(cmd.db) ); goto end_ar_command; } sqlite3_fileio_init(cmd.db, 0, 0); sqlite3_sqlar_init(cmd.db, 0, 0); sqlite3_create_function(cmd.db, "shell_putsnl", 1, SQLITE_UTF8, cmd.p, |
︙ | ︙ | |||
7333 7334 7335 7336 7337 7338 7339 | i++; zLAF = azArg[i]; }else if( n<=10 && memcmp("-no-rowids", z, n)==0 ){ bRowids = 0; } else{ | | | 7538 7539 7540 7541 7542 7543 7544 7545 7546 7547 7548 7549 7550 7551 7552 | i++; zLAF = azArg[i]; }else if( n<=10 && memcmp("-no-rowids", z, n)==0 ){ bRowids = 0; } else{ utf8_printf(stderr, "unexpected option: %s\n", azArg[i]); showHelp(pState->out, azArg[0]); return 1; } } p = sqlite3_recover_init_sql( pState->db, "main", recoverSqlCb, (void*)pState |
︙ | ︙ | |||
7696 7697 7698 7699 7700 7701 7702 | } } if( zDestFile==0 ){ raw_printf(stderr, "missing FILENAME argument on .backup\n"); return 1; } if( zDb==0 ) zDb = "main"; | | | 7901 7902 7903 7904 7905 7906 7907 7908 7909 7910 7911 7912 7913 7914 7915 | } } if( zDestFile==0 ){ raw_printf(stderr, "missing FILENAME argument on .backup\n"); return 1; } if( zDb==0 ) zDb = "main"; rc = sqlite3_open_v2(zDestFile, &pDest, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs); if( rc!=SQLITE_OK ){ utf8_printf(stderr, "Error: cannot open \"%s\"\n", zDestFile); close_db(pDest); return 1; } if( bAsync ){ |
︙ | ︙ | |||
7946 7947 7948 7949 7950 7951 7952 | sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v); utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off"); if( nArg>1 ) break; } if( nArg>1 && ii==ArraySize(aDbConfig) ){ utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]); utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n"); | | | 8151 8152 8153 8154 8155 8156 8157 8158 8159 8160 8161 8162 8163 8164 8165 | sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v); utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off"); if( nArg>1 ) break; } if( nArg>1 && ii==ArraySize(aDbConfig) ){ utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]); utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n"); } }else #if SQLITE_SHELL_HAVE_RECOVER if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbinfo", n)==0 ){ rc = shell_dbinfo_command(p, nArg, azArg); }else |
︙ | ︙ | |||
8013 8014 8015 8016 8017 8018 8019 | "name LIKE %Q ESCAPE '\\' OR EXISTS (" " SELECT 1 FROM sqlite_schema WHERE " " name LIKE %Q ESCAPE '\\' AND" " sql LIKE 'CREATE VIRTUAL TABLE%%' AND" " substr(o.name, 1, length(name)+1) == (name||'_')" ")", azArg[i], azArg[i] ); | | | 8218 8219 8220 8221 8222 8223 8224 8225 8226 8227 8228 8229 8230 8231 8232 | "name LIKE %Q ESCAPE '\\' OR EXISTS (" " SELECT 1 FROM sqlite_schema WHERE " " name LIKE %Q ESCAPE '\\' AND" " sql LIKE 'CREATE VIRTUAL TABLE%%' AND" " substr(o.name, 1, length(name)+1) == (name||'_')" ")", azArg[i], azArg[i] ); if( zLike ){ zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr); }else{ zLike = zExpr; } } } |
︙ | ︙ | |||
8146 8147 8148 8149 8150 8151 8152 | p->autoExplain = 1; } }else #ifndef SQLITE_OMIT_VIRTUALTABLE if( c=='e' && cli_strncmp(azArg[0], "expert", n)==0 ){ if( p->bSafeMode ){ | | | | | 8351 8352 8353 8354 8355 8356 8357 8358 8359 8360 8361 8362 8363 8364 8365 8366 8367 8368 8369 8370 8371 8372 8373 8374 8375 8376 8377 8378 8379 8380 8381 8382 8383 8384 8385 8386 8387 8388 8389 8390 8391 8392 8393 8394 8395 8396 8397 8398 8399 8400 8401 8402 8403 8404 8405 | p->autoExplain = 1; } }else #ifndef SQLITE_OMIT_VIRTUALTABLE if( c=='e' && cli_strncmp(azArg[0], "expert", n)==0 ){ if( p->bSafeMode ){ raw_printf(stderr, "Cannot run experimental commands such as \"%s\" in safe mode\n", azArg[0]); rc = 1; }else{ open_db(p, 0); expertDotCommand(p, azArg, nArg); } }else #endif if( c=='f' && cli_strncmp(azArg[0], "filectrl", n)==0 ){ static const struct { const char *zCtrlName; /* Name of a test-control option */ int ctrlCode; /* Integer code for that option */ const char *zUsage; /* Usage notes */ } aCtrl[] = { { "chunk_size", SQLITE_FCNTL_CHUNK_SIZE, "SIZE" }, { "data_version", SQLITE_FCNTL_DATA_VERSION, "" }, { "has_moved", SQLITE_FCNTL_HAS_MOVED, "" }, { "lock_timeout", SQLITE_FCNTL_LOCK_TIMEOUT, "MILLISEC" }, { "persist_wal", SQLITE_FCNTL_PERSIST_WAL, "[BOOLEAN]" }, /* { "pragma", SQLITE_FCNTL_PRAGMA, "NAME ARG" },*/ { "psow", SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]" }, { "reserve_bytes", SQLITE_FCNTL_RESERVE_BYTES, "[N]" }, { "size_limit", SQLITE_FCNTL_SIZE_LIMIT, "[LIMIT]" }, { "tempfilename", SQLITE_FCNTL_TEMPFILENAME, "" }, /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY, "COUNT DELAY" },*/ }; int filectrl = -1; int iCtrl = -1; sqlite3_int64 iRes = 0; /* Integer result to display if rc2==1 */ int isOk = 0; /* 0: usage 1: %lld 2: no-result */ int n2, i; const char *zCmd = 0; const char *zSchema = 0; open_db(p, 0); zCmd = nArg>=2 ? azArg[1] : "help"; if( zCmd[0]=='-' && (cli_strcmp(zCmd,"--schema")==0 || cli_strcmp(zCmd,"-schema")==0) && nArg>=4 ){ zSchema = azArg[2]; for(i=3; i<nArg; i++) azArg[i-2] = azArg[i]; nArg -= 2; zCmd = azArg[1]; |
︙ | ︙ | |||
9520 9521 9522 9523 9524 9525 9526 | } close_db(pSrc); }else #endif /* !defined(SQLITE_SHELL_FIDDLE) */ if( c=='s' && cli_strncmp(azArg[0], "scanstats", n)==0 ){ if( nArg==2 ){ | > > > | > | | 9725 9726 9727 9728 9729 9730 9731 9732 9733 9734 9735 9736 9737 9738 9739 9740 9741 9742 9743 9744 9745 9746 9747 9748 | } close_db(pSrc); }else #endif /* !defined(SQLITE_SHELL_FIDDLE) */ if( c=='s' && cli_strncmp(azArg[0], "scanstats", n)==0 ){ if( nArg==2 ){ if( cli_strcmp(azArg[1], "est")==0 ){ p->scanstatsOn = 2; }else{ p->scanstatsOn = (u8)booleanValue(azArg[1]); } #ifndef SQLITE_ENABLE_STMT_SCANSTATUS raw_printf(stderr, "Warning: .scanstats not available in this build.\n"); #endif }else{ raw_printf(stderr, "Usage: .scanstats on|off|est\n"); rc = 1; } }else if( c=='s' && cli_strncmp(azArg[0], "schema", n)==0 ){ ShellText sSelect; ShellState data; |
︙ | ︙ | |||
9560 9561 9562 9563 9564 9565 9566 | }else if( azArg[ii][0]=='-' ){ utf8_printf(stderr, "Unknown option: \"%s\"\n", azArg[ii]); rc = 1; goto meta_command_exit; }else if( zName==0 ){ zName = azArg[ii]; }else{ | > | | 9769 9770 9771 9772 9773 9774 9775 9776 9777 9778 9779 9780 9781 9782 9783 9784 | }else if( azArg[ii][0]=='-' ){ utf8_printf(stderr, "Unknown option: \"%s\"\n", azArg[ii]); rc = 1; goto meta_command_exit; }else if( zName==0 ){ zName = azArg[ii]; }else{ raw_printf(stderr, "Usage: .schema ?--indent? ?--nosys? ?LIKE-PATTERN?\n"); rc = 1; goto meta_command_exit; } } if( zName!=0 ){ int isSchema = sqlite3_strlike(zName, "sqlite_master", '\\')==0 || sqlite3_strlike(zName, "sqlite_schema", '\\')==0 |
︙ | ︙ | |||
9676 9677 9678 9679 9680 9681 9682 | rc = 0; } }else if( (c=='s' && n==11 && cli_strncmp(azArg[0], "selecttrace", n)==0) || (c=='t' && n==9 && cli_strncmp(azArg[0], "treetrace", n)==0) ){ | | | 9886 9887 9888 9889 9890 9891 9892 9893 9894 9895 9896 9897 9898 9899 9900 | rc = 0; } }else if( (c=='s' && n==11 && cli_strncmp(azArg[0], "selecttrace", n)==0) || (c=='t' && n==9 && cli_strncmp(azArg[0], "treetrace", n)==0) ){ unsigned int x = nArg>=2? (unsigned int)integerValue(azArg[1]) : 0xffffffff; sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x); }else #if defined(SQLITE_ENABLE_SESSION) if( c=='s' && cli_strncmp(azArg[0],"session",n)==0 && n>=3 ){ struct AuxDb *pAuxDb = p->pAuxDb; OpenSession *pSession = &pAuxDb->aSession[0]; |
︙ | ︙ | |||
9861 9862 9863 9864 9865 9866 9867 | for(i=0; i<pAuxDb->nSession; i++){ if( cli_strcmp(pAuxDb->aSession[i].zName,zName)==0 ){ utf8_printf(stderr, "Session \"%s\" already exists\n", zName); goto meta_command_exit; } } if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){ | > | | 10071 10072 10073 10074 10075 10076 10077 10078 10079 10080 10081 10082 10083 10084 10085 10086 | for(i=0; i<pAuxDb->nSession; i++){ if( cli_strcmp(pAuxDb->aSession[i].zName,zName)==0 ){ utf8_printf(stderr, "Session \"%s\" already exists\n", zName); goto meta_command_exit; } } if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){ raw_printf(stderr, "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession)); goto meta_command_exit; } pSession = &pAuxDb->aSession[pAuxDb->nSession]; rc = sqlite3session_create(p->db, azCmd[1], &pSession->p); if( rc ){ raw_printf(stderr, "Cannot open session: error code=%d\n", rc); rc = 0; |
︙ | ︙ | |||
10075 10076 10077 10078 10079 10080 10081 | }else{ zLike = z; bSeparate = 1; if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 1; } } if( bSchema ){ | | | | 10286 10287 10288 10289 10290 10291 10292 10293 10294 10295 10296 10297 10298 10299 10300 10301 10302 10303 10304 10305 | }else{ zLike = z; bSeparate = 1; if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 1; } } if( bSchema ){ zSql = "SELECT lower(name) as tname FROM sqlite_schema" " WHERE type='table' AND coalesce(rootpage,0)>1" " UNION ALL SELECT 'sqlite_schema'" " ORDER BY 1 collate nocase"; }else{ zSql = "SELECT lower(name) as tname FROM sqlite_schema" " WHERE type='table' AND coalesce(rootpage,0)>1" " AND name NOT LIKE 'sqlite_%'" " ORDER BY 1 collate nocase"; } sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); initText(&sQuery); initText(&sSql); |
︙ | ︙ | |||
10141 10142 10143 10144 10145 10146 10147 10148 10149 10150 10151 10152 10153 10154 | freeText(&sQuery); freeText(&sSql); if( bDebug ){ utf8_printf(p->out, "%s\n", zSql); }else{ shell_exec(p, zSql, 0); } sqlite3_free(zSql); }else #if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) if( c=='s' && (cli_strncmp(azArg[0], "shell", n)==0 || cli_strncmp(azArg[0],"system",n)==0) | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 10352 10353 10354 10355 10356 10357 10358 10359 10360 10361 10362 10363 10364 10365 10366 10367 10368 10369 10370 10371 10372 10373 10374 10375 10376 10377 10378 10379 10380 10381 10382 10383 10384 10385 10386 10387 10388 10389 10390 10391 10392 10393 10394 10395 10396 10397 10398 10399 10400 10401 10402 10403 10404 10405 10406 10407 10408 10409 10410 10411 10412 10413 10414 10415 10416 10417 | freeText(&sQuery); freeText(&sSql); if( bDebug ){ utf8_printf(p->out, "%s\n", zSql); }else{ shell_exec(p, zSql, 0); } #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) && !defined(SQLITE_OMIT_VIRTUALTABLE) { int lrc; char *zRevText = /* Query for reversible to-blob-to-text check */ "SELECT lower(name) as tname FROM sqlite_schema\n" "WHERE type='table' AND coalesce(rootpage,0)>1\n" "AND name NOT LIKE 'sqlite_%%'%s\n" "ORDER BY 1 collate nocase"; zRevText = sqlite3_mprintf(zRevText, zLike? " AND name LIKE $tspec" : ""); zRevText = sqlite3_mprintf( /* lower-case query is first run, producing upper-case query. */ "with tabcols as materialized(\n" "select tname, cname\n" "from (" " select ss.tname as tname, ti.name as cname\n" " from (%z) ss\n inner join pragma_table_info(tname) ti))\n" "select 'SELECT total(bad_text_count) AS bad_text_count\n" "FROM ('||group_concat(query, ' UNION ALL ')||')' as btc_query\n" " from (select 'SELECT COUNT(*) AS bad_text_count\n" "FROM '||tname||' WHERE '\n" "||group_concat('CAST(CAST('||cname||' AS BLOB) AS TEXT)<>'||cname\n" "|| ' AND typeof('||cname||')=''text'' ',\n" "' OR ') as query, tname from tabcols group by tname)" , zRevText); shell_check_oom(zRevText); if( bDebug ) utf8_printf(p->out, "%s\n", zRevText); lrc = sqlite3_prepare_v2(p->db, zRevText, -1, &pStmt, 0); assert(lrc==SQLITE_OK); if( zLike ) sqlite3_bind_text(pStmt,1,zLike,-1,SQLITE_STATIC); lrc = SQLITE_ROW==sqlite3_step(pStmt); if( lrc ){ const char *zGenQuery = (char*)sqlite3_column_text(pStmt,0); sqlite3_stmt *pCheckStmt; lrc = sqlite3_prepare_v2(p->db, zGenQuery, -1, &pCheckStmt, 0); if( bDebug ) utf8_printf(p->out, "%s\n", zGenQuery); if( SQLITE_OK==lrc ){ if( SQLITE_ROW==sqlite3_step(pCheckStmt) ){ double countIrreversible = sqlite3_column_double(pCheckStmt, 0); if( countIrreversible>0 ){ int sz = (int)(countIrreversible + 0.5); utf8_printf(stderr, "Digest includes %d invalidly encoded text field%s.\n", sz, (sz>1)? "s": ""); } } sqlite3_finalize(pCheckStmt); } sqlite3_finalize(pStmt); } sqlite3_free(zRevText); } #endif /* !defined(*_OMIT_SCHEMA_PRAGMAS) && !defined(*_OMIT_VIRTUALTABLE) */ sqlite3_free(zSql); }else #if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) if( c=='s' && (cli_strncmp(azArg[0], "shell", n)==0 || cli_strncmp(azArg[0],"system",n)==0) |
︙ | ︙ | |||
10370 10371 10372 10373 10374 10375 10376 | if( c=='t' && n>=8 && cli_strncmp(azArg[0], "testctrl", n)==0 ){ static const struct { const char *zCtrlName; /* Name of a test-control option */ int ctrlCode; /* Integer code for that option */ int unSafe; /* Not valid for --safe mode */ const char *zUsage; /* Usage notes */ } aCtrl[] = { | | | | | | | | | | | | | | | | | | | | | | 10633 10634 10635 10636 10637 10638 10639 10640 10641 10642 10643 10644 10645 10646 10647 10648 10649 10650 10651 10652 10653 10654 10655 10656 10657 10658 10659 10660 10661 10662 10663 10664 10665 10666 10667 10668 | if( c=='t' && n>=8 && cli_strncmp(azArg[0], "testctrl", n)==0 ){ static const struct { const char *zCtrlName; /* Name of a test-control option */ int ctrlCode; /* Integer code for that option */ int unSafe; /* Not valid for --safe mode */ const char *zUsage; /* Usage notes */ } aCtrl[] = { {"always", SQLITE_TESTCTRL_ALWAYS, 1, "BOOLEAN" }, {"assert", SQLITE_TESTCTRL_ASSERT, 1, "BOOLEAN" }, /*{"benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS,1, "" },*/ /*{"bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST, 1, "" },*/ {"byteorder", SQLITE_TESTCTRL_BYTEORDER, 0, "" }, {"extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,0,"BOOLEAN" }, /*{"fault_install", SQLITE_TESTCTRL_FAULT_INSTALL, 1,"" },*/ {"imposter", SQLITE_TESTCTRL_IMPOSTER,1,"SCHEMA ON/OFF ROOTPAGE"}, {"internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS,0,"" }, {"localtime_fault", SQLITE_TESTCTRL_LOCALTIME_FAULT,0,"BOOLEAN" }, {"never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT,1, "BOOLEAN" }, {"optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS,0,"DISABLE-MASK" }, #ifdef YYCOVERAGE {"parser_coverage", SQLITE_TESTCTRL_PARSER_COVERAGE,0,"" }, #endif {"pending_byte", SQLITE_TESTCTRL_PENDING_BYTE,0, "OFFSET " }, {"prng_restore", SQLITE_TESTCTRL_PRNG_RESTORE,0, "" }, {"prng_save", SQLITE_TESTCTRL_PRNG_SAVE, 0, "" }, {"prng_seed", SQLITE_TESTCTRL_PRNG_SEED, 0, "SEED ?db?" }, {"seek_count", SQLITE_TESTCTRL_SEEK_COUNT, 0, "" }, {"sorter_mmap", SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX" }, {"tune", SQLITE_TESTCTRL_TUNE, 1, "ID VALUE" }, }; int testctrl = -1; int iCtrl = -1; int rc2 = 0; /* 0: usage. 1: %d 2: %x 3: no-output */ int isOk = 0; int i, n2; const char *zCmd = 0; |
︙ | ︙ | |||
10652 10653 10654 10655 10656 10657 10658 | else { raw_printf(stderr, "Unknown option \"%s\" on \".trace\"\n", z); rc = 1; goto meta_command_exit; } }else{ output_file_close(p->traceOut); | | | 10915 10916 10917 10918 10919 10920 10921 10922 10923 10924 10925 10926 10927 10928 10929 | else { raw_printf(stderr, "Unknown option \"%s\" on \".trace\"\n", z); rc = 1; goto meta_command_exit; } }else{ output_file_close(p->traceOut); p->traceOut = output_file_open(z, 0); } } if( p->traceOut==0 ){ sqlite3_trace_v2(p->db, 0, 0, 0); }else{ if( mType==0 ) mType = SQLITE_TRACE_STMT; sqlite3_trace_v2(p->db, mType, sql_trace_callback, p); |
︙ | ︙ | |||
10816 10817 10818 10819 10820 10821 10822 | utf8_printf(p->out, "%s\n", zVfsName); sqlite3_free(zVfsName); } } }else if( c=='w' && cli_strncmp(azArg[0], "wheretrace", n)==0 ){ | | | 11079 11080 11081 11082 11083 11084 11085 11086 11087 11088 11089 11090 11091 11092 11093 | utf8_printf(p->out, "%s\n", zVfsName); sqlite3_free(zVfsName); } } }else if( c=='w' && cli_strncmp(azArg[0], "wheretrace", n)==0 ){ unsigned int x = nArg>=2? (unsigned int)integerValue(azArg[1]) : 0xffffffff; sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &x); }else if( c=='w' && cli_strncmp(azArg[0], "width", n)==0 ){ int j; assert( nArg<=ArraySize(azArg) ); p->nWidth = nArg-1; |
︙ | ︙ | |||
10868 10869 10870 10871 10872 10873 10874 | #define QSS_SEMITERM(qss) (((qss)&~QSS_HasDark)==QSS_EndingSemi) /* ** Scan line for classification to guide shell's handling. ** The scan is resumable for subsequent lines when prior ** return values are passed as the 2nd argument. */ | | > | 11131 11132 11133 11134 11135 11136 11137 11138 11139 11140 11141 11142 11143 11144 11145 11146 | #define QSS_SEMITERM(qss) (((qss)&~QSS_HasDark)==QSS_EndingSemi) /* ** Scan line for classification to guide shell's handling. ** The scan is resumable for subsequent lines when prior ** return values are passed as the 2nd argument. */ static QuickScanState quickscan(char *zLine, QuickScanState qss, SCAN_TRACKER_REFTYPE pst){ char cin; char cWait = (char)qss; /* intentional narrowing loss */ if( cWait==0 ){ PlainScan: assert( cWait==0 ); while( (cin = *zLine++)!=0 ){ if( IsSpace(cin) ) |
︙ | ︙ | |||
10892 10893 10894 10895 10896 10897 10898 10899 10900 10901 10902 10903 10904 10905 10906 10907 10908 10909 10910 10911 10912 10913 10914 10915 10916 10917 10918 10919 10920 10921 10922 10923 10924 10925 10926 10927 10928 10929 10930 10931 10932 10933 10934 10935 10936 10937 10938 10939 10940 10941 | case ';': qss |= QSS_EndingSemi; continue; case '/': if( *zLine=='*' ){ ++zLine; cWait = '*'; qss = QSS_SETV(qss, cWait); goto TermScan; } break; case '[': cin = ']'; /* fall thru */ case '`': case '\'': case '"': cWait = cin; qss = QSS_HasDark | cWait; goto TermScan; default: break; } qss = (qss & ~QSS_EndingSemi) | QSS_HasDark; } }else{ TermScan: while( (cin = *zLine++)!=0 ){ if( cin==cWait ){ switch( cWait ){ case '*': if( *zLine != '/' ) continue; ++zLine; cWait = 0; qss = QSS_SETV(qss, 0); goto PlainScan; case '`': case '\'': case '"': if(*zLine==cWait){ ++zLine; continue; } /* fall thru */ case ']': cWait = 0; qss = QSS_SETV(qss, 0); goto PlainScan; default: assert(0); } } } } | > > > > > > > > > > > | 11156 11157 11158 11159 11160 11161 11162 11163 11164 11165 11166 11167 11168 11169 11170 11171 11172 11173 11174 11175 11176 11177 11178 11179 11180 11181 11182 11183 11184 11185 11186 11187 11188 11189 11190 11191 11192 11193 11194 11195 11196 11197 11198 11199 11200 11201 11202 11203 11204 11205 11206 11207 11208 11209 11210 11211 11212 11213 11214 11215 11216 | case ';': qss |= QSS_EndingSemi; continue; case '/': if( *zLine=='*' ){ ++zLine; cWait = '*'; CONTINUE_PROMPT_AWAITS(pst, "/*"); qss = QSS_SETV(qss, cWait); goto TermScan; } break; case '[': cin = ']'; /* fall thru */ case '`': case '\'': case '"': cWait = cin; qss = QSS_HasDark | cWait; CONTINUE_PROMPT_AWAITC(pst, cin); goto TermScan; case '(': CONTINUE_PAREN_INCR(pst, 1); break; case ')': CONTINUE_PAREN_INCR(pst, -1); break; default: break; } qss = (qss & ~QSS_EndingSemi) | QSS_HasDark; } }else{ TermScan: while( (cin = *zLine++)!=0 ){ if( cin==cWait ){ switch( cWait ){ case '*': if( *zLine != '/' ) continue; ++zLine; cWait = 0; CONTINUE_PROMPT_AWAITC(pst, 0); qss = QSS_SETV(qss, 0); goto PlainScan; case '`': case '\'': case '"': if(*zLine==cWait){ /* Swallow doubled end-delimiter.*/ ++zLine; continue; } /* fall thru */ case ']': cWait = 0; CONTINUE_PROMPT_AWAITC(pst, 0); qss = QSS_SETV(qss, 0); goto PlainScan; default: assert(0); } } } } |
︙ | ︙ | |||
10951 10952 10953 10954 10955 10956 10957 | while( IsSpace(zLine[0]) ){ zLine++; }; if( zLine[0]=='/' ) zLine += 1; /* Oracle */ else if ( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o' ) zLine += 2; /* SQL Server */ else return 0; | | | | < < < > | 11226 11227 11228 11229 11230 11231 11232 11233 11234 11235 11236 11237 11238 11239 11240 11241 11242 11243 11244 11245 11246 11247 11248 | while( IsSpace(zLine[0]) ){ zLine++; }; if( zLine[0]=='/' ) zLine += 1; /* Oracle */ else if ( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o' ) zLine += 2; /* SQL Server */ else return 0; return quickscan(zLine, QSS_Start, 0)==QSS_Start; } /* ** The CLI needs a working sqlite3_complete() to work properly. So error ** out of the build if compiling with SQLITE_OMIT_COMPLETE. */ #ifdef SQLITE_OMIT_COMPLETE # error the CLI application is imcompatable with SQLITE_OMIT_COMPLETE. #endif /* ** Return true if zSql is a complete SQL statement. Return false if it ** ends in the middle of a string literal or C-style comment. */ static int line_is_complete(char *zSql, int nSql){ |
︙ | ︙ | |||
11034 11035 11036 11037 11038 11039 11040 | static void echo_group_input(ShellState *p, const char *zDo){ if( ShellHasFlag(p, SHFLG_Echo) ) utf8_printf(p->out, "%s\n", zDo); } #ifdef SQLITE_SHELL_FIDDLE /* | | | | | 11307 11308 11309 11310 11311 11312 11313 11314 11315 11316 11317 11318 11319 11320 11321 11322 11323 | static void echo_group_input(ShellState *p, const char *zDo){ if( ShellHasFlag(p, SHFLG_Echo) ) utf8_printf(p->out, "%s\n", zDo); } #ifdef SQLITE_SHELL_FIDDLE /* ** Alternate one_input_line() impl for wasm mode. This is not in the primary ** impl because we need the global shellState and cannot access it from that ** function without moving lots of code around (creating a larger/messier diff). */ static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ /* Parse the next line from shellState.wasm.zInput. */ const char *zBegin = shellState.wasm.zPos; const char *z = zBegin; char *zLine = 0; i64 nZ = 0; |
︙ | ︙ | |||
11093 11094 11095 11096 11097 11098 11099 11100 11101 11102 11103 11104 11105 11106 11107 11108 11109 11110 11111 11112 11113 11114 11115 11116 11117 | /* This will be more informative in a later version. */ utf8_printf(stderr,"Input nesting limit (%d) reached at line %d." " Check recursion.\n", MAX_INPUT_NESTING, p->lineno); return 1; } ++p->inputNesting; p->lineno = 0; while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){ fflush(p->out); zLine = one_input_line(p->in, zLine, nSql>0); if( zLine==0 ){ /* End of input */ if( p->in==0 && stdin_is_interactive ) printf("\n"); break; } if( seenInterrupt ){ if( p->in!=0 ) break; seenInterrupt = 0; } p->lineno++; if( QSS_INPLAIN(qss) && line_is_command_terminator(zLine) && line_is_complete(zSql, nSql) ){ memcpy(zLine,";",2); } | > | > | 11366 11367 11368 11369 11370 11371 11372 11373 11374 11375 11376 11377 11378 11379 11380 11381 11382 11383 11384 11385 11386 11387 11388 11389 11390 11391 11392 11393 11394 11395 11396 11397 11398 11399 11400 11401 11402 11403 11404 11405 11406 11407 | /* This will be more informative in a later version. */ utf8_printf(stderr,"Input nesting limit (%d) reached at line %d." " Check recursion.\n", MAX_INPUT_NESTING, p->lineno); return 1; } ++p->inputNesting; p->lineno = 0; CONTINUE_PROMPT_RESET; while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){ fflush(p->out); zLine = one_input_line(p->in, zLine, nSql>0); if( zLine==0 ){ /* End of input */ if( p->in==0 && stdin_is_interactive ) printf("\n"); break; } if( seenInterrupt ){ if( p->in!=0 ) break; seenInterrupt = 0; } p->lineno++; if( QSS_INPLAIN(qss) && line_is_command_terminator(zLine) && line_is_complete(zSql, nSql) ){ memcpy(zLine,";",2); } qss = quickscan(zLine, qss, CONTINUE_PROMPT_PSTATE); if( QSS_PLAINWHITE(qss) && nSql==0 ){ /* Just swallow single-line whitespace */ echo_group_input(p, zLine); qss = QSS_Start; continue; } if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){ CONTINUE_PROMPT_RESET; echo_group_input(p, zLine); if( zLine[0]=='.' ){ rc = do_meta_command(zLine, p); if( rc==2 ){ /* exit requested */ break; }else if( rc ){ errCnt++; |
︙ | ︙ | |||
11154 11155 11156 11157 11158 11159 11160 11161 11162 11163 11164 11165 11166 11167 11168 11169 11170 11171 11172 11173 11174 11175 11176 11177 11178 11179 11180 11181 11182 11183 11184 11185 11186 | zSql[nSql++] = '\n'; memcpy(zSql+nSql, zLine, nLine+1); nSql += nLine; } if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){ echo_group_input(p, zSql); errCnt += runOneSqlLine(p, zSql, p->in, startline); nSql = 0; if( p->outCount ){ output_reset(p); p->outCount = 0; }else{ clearTempFile(p); } p->bSafeMode = p->bSafeModePersist; qss = QSS_Start; }else if( nSql && QSS_PLAINWHITE(qss) ){ echo_group_input(p, zSql); nSql = 0; qss = QSS_Start; } } if( nSql ){ /* This may be incomplete. Let the SQL parser deal with that. */ echo_group_input(p, zSql); errCnt += runOneSqlLine(p, zSql, p->in, startline); } free(zSql); free(zLine); --p->inputNesting; return errCnt>0; } | > > | 11429 11430 11431 11432 11433 11434 11435 11436 11437 11438 11439 11440 11441 11442 11443 11444 11445 11446 11447 11448 11449 11450 11451 11452 11453 11454 11455 11456 11457 11458 11459 11460 11461 11462 11463 | zSql[nSql++] = '\n'; memcpy(zSql+nSql, zLine, nLine+1); nSql += nLine; } if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){ echo_group_input(p, zSql); errCnt += runOneSqlLine(p, zSql, p->in, startline); CONTINUE_PROMPT_RESET; nSql = 0; if( p->outCount ){ output_reset(p); p->outCount = 0; }else{ clearTempFile(p); } p->bSafeMode = p->bSafeModePersist; qss = QSS_Start; }else if( nSql && QSS_PLAINWHITE(qss) ){ echo_group_input(p, zSql); nSql = 0; qss = QSS_Start; } } if( nSql ){ /* This may be incomplete. Let the SQL parser deal with that. */ echo_group_input(p, zSql); errCnt += runOneSqlLine(p, zSql, p->in, startline); CONTINUE_PROMPT_RESET; } free(zSql); free(zLine); --p->inputNesting; return errCnt>0; } |
︙ | ︙ | |||
11248 11249 11250 11251 11252 11253 11254 11255 11256 11257 | char *z = malloc( n ); if( z ) memcpy(z, home_dir, n); home_dir = z; } return home_dir; } /* ** Read input from the file given by sqliterc_override. Or if that | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > | > > > | 11525 11526 11527 11528 11529 11530 11531 11532 11533 11534 11535 11536 11537 11538 11539 11540 11541 11542 11543 11544 11545 11546 11547 11548 11549 11550 11551 11552 11553 11554 11555 11556 11557 11558 11559 11560 11561 11562 11563 11564 11565 11566 11567 11568 11569 11570 11571 11572 11573 11574 11575 11576 11577 11578 11579 11580 11581 11582 11583 11584 11585 11586 11587 11588 11589 11590 11591 11592 11593 | char *z = malloc( n ); if( z ) memcpy(z, home_dir, n); home_dir = z; } return home_dir; } /* ** On non-Windows platforms, look for $XDG_CONFIG_HOME. ** If ${XDG_CONFIG_HOME}/sqlite3/sqliterc is found, return ** the path to it, else return 0. The result is cached for ** subsequent calls. */ static const char *find_xdg_config(void){ #if defined(_WIN32) || defined(WIN32) || defined(_WIN32_WCE) \ || defined(__RTP__) || defined(_WRS_KERNEL) return 0; #else static int alreadyTried = 0; static char *zConfig = 0; const char *zXdgHome; if( alreadyTried!=0 ){ return zConfig; } alreadyTried = 1; zXdgHome = getenv("XDG_CONFIG_HOME"); if( zXdgHome==0 ){ return 0; } zConfig = sqlite3_mprintf("%s/sqlite3/sqliterc", zXdgHome); shell_check_oom(zConfig); if( access(zConfig,0)!=0 ){ sqlite3_free(zConfig); zConfig = 0; } return zConfig; #endif } /* ** Read input from the file given by sqliterc_override. Or if that ** parameter is NULL, take input from the first of find_xdg_config() ** or ~/.sqliterc which is found. ** ** Returns the number of errors. */ static void process_sqliterc( ShellState *p, /* Configuration data */ const char *sqliterc_override /* Name of config file. NULL to use default */ ){ char *home_dir = NULL; const char *sqliterc = sqliterc_override; char *zBuf = 0; FILE *inSaved = p->in; int savedLineno = p->lineno; if( sqliterc == NULL ){ sqliterc = find_xdg_config(); } if( sqliterc == NULL ){ home_dir = find_home_dir(0); if( home_dir==0 ){ raw_printf(stderr, "-- warning: cannot find home directory;" " cannot read ~/.sqliterc\n"); return; } zBuf = sqlite3_mprintf("%s/.sqliterc",home_dir); |
︙ | ︙ | |||
11814 11815 11816 11817 11818 11819 11820 | #endif }else if( cli_strcmp(z,"-readonly")==0 ){ data.openMode = SHELL_OPEN_READONLY; }else if( cli_strcmp(z,"-nofollow")==0 ){ data.openFlags |= SQLITE_OPEN_NOFOLLOW; }else if( cli_strcmp(z,"-ascii")==0 ){ data.mode = MODE_Ascii; | | | | | | 12128 12129 12130 12131 12132 12133 12134 12135 12136 12137 12138 12139 12140 12141 12142 12143 12144 12145 12146 12147 | #endif }else if( cli_strcmp(z,"-readonly")==0 ){ data.openMode = SHELL_OPEN_READONLY; }else if( cli_strcmp(z,"-nofollow")==0 ){ data.openFlags |= SQLITE_OPEN_NOFOLLOW; }else if( cli_strcmp(z,"-ascii")==0 ){ data.mode = MODE_Ascii; sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Unit); sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Record); }else if( cli_strcmp(z,"-tabs")==0 ){ data.mode = MODE_List; sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Tab); sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Row); }else if( cli_strcmp(z,"-separator")==0 ){ sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, "%s",cmdline_option_value(argc,argv,++i)); }else if( cli_strcmp(z,"-newline")==0 ){ sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, "%s",cmdline_option_value(argc,argv,++i)); }else if( cli_strcmp(z,"-nullvalue")==0 ){ |
︙ | ︙ |
Changes to src/sqlite.h.in.
︙ | ︙ | |||
559 560 561 562 563 564 565 566 567 568 569 570 571 572 | #define SQLITE_CONSTRAINT_UNIQUE (SQLITE_CONSTRAINT | (8<<8)) #define SQLITE_CONSTRAINT_VTAB (SQLITE_CONSTRAINT | (9<<8)) #define SQLITE_CONSTRAINT_ROWID (SQLITE_CONSTRAINT |(10<<8)) #define SQLITE_CONSTRAINT_PINNED (SQLITE_CONSTRAINT |(11<<8)) #define SQLITE_CONSTRAINT_DATATYPE (SQLITE_CONSTRAINT |(12<<8)) #define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8)) #define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8)) #define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8)) #define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8)) #define SQLITE_OK_LOAD_PERMANENTLY (SQLITE_OK | (1<<8)) #define SQLITE_OK_SYMLINK (SQLITE_OK | (2<<8)) /* internal use only */ /* ** CAPI3REF: Flags For File Open Operations | > | 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 | #define SQLITE_CONSTRAINT_UNIQUE (SQLITE_CONSTRAINT | (8<<8)) #define SQLITE_CONSTRAINT_VTAB (SQLITE_CONSTRAINT | (9<<8)) #define SQLITE_CONSTRAINT_ROWID (SQLITE_CONSTRAINT |(10<<8)) #define SQLITE_CONSTRAINT_PINNED (SQLITE_CONSTRAINT |(11<<8)) #define SQLITE_CONSTRAINT_DATATYPE (SQLITE_CONSTRAINT |(12<<8)) #define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8)) #define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8)) #define SQLITE_NOTICE_RBU (SQLITE_NOTICE | (3<<8)) #define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8)) #define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8)) #define SQLITE_OK_LOAD_PERMANENTLY (SQLITE_OK | (1<<8)) #define SQLITE_OK_SYMLINK (SQLITE_OK | (2<<8)) /* internal use only */ /* ** CAPI3REF: Flags For File Open Operations |
︙ | ︙ | |||
2180 2181 2182 2183 2184 2185 2186 | ** or equal to the product of the second and third arguments. The buffer ** must be aligned to an 8-byte boundary. ^If the second argument to ** SQLITE_DBCONFIG_LOOKASIDE is not a multiple of 8, it is internally ** rounded down to the next smaller multiple of 8. ^(The lookaside memory ** configuration for a database connection can only be changed when that ** connection is not currently using lookaside memory, or in other words ** when the "current value" returned by | | | 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 | ** or equal to the product of the second and third arguments. The buffer ** must be aligned to an 8-byte boundary. ^If the second argument to ** SQLITE_DBCONFIG_LOOKASIDE is not a multiple of 8, it is internally ** rounded down to the next smaller multiple of 8. ^(The lookaside memory ** configuration for a database connection can only be changed when that ** connection is not currently using lookaside memory, or in other words ** when the "current value" returned by ** [sqlite3_db_status](D,[SQLITE_DBSTATUS_LOOKASIDE_USED],...) is zero. ** Any attempt to change the lookaside memory configuration when lookaside ** memory is in use leaves the configuration unchanged and returns ** [SQLITE_BUSY].)^</dd> ** ** [[SQLITE_DBCONFIG_ENABLE_FKEY]] ** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt> ** <dd> ^This option is used to enable or disable the enforcement of |
︙ | ︙ | |||
2330 2331 2332 2333 2334 2335 2336 | ** the database in WAL mode after the reset if it was in WAL mode before ** the reset. ** <li> sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); ** <li> [sqlite3_exec](db, "[VACUUM]", 0, 0, 0); ** <li> sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); ** </ol> ** Because resetting a database is destructive and irreversible, the | | | > > > > | 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 | ** the database in WAL mode after the reset if it was in WAL mode before ** the reset. ** <li> sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); ** <li> [sqlite3_exec](db, "[VACUUM]", 0, 0, 0); ** <li> sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); ** </ol> ** Because resetting a database is destructive and irreversible, the ** process requires the use of this obscure API and multiple steps to ** help ensure that it does not happen by accident. Because this ** feature must be capable of resetting corrupt databases, and ** shutting down virtual tables may require access to that corrupt ** storage, the library must abandon any installed virtual tables ** without calling their xDestroy() methods. ** ** [[SQLITE_DBCONFIG_DEFENSIVE]] <dt>SQLITE_DBCONFIG_DEFENSIVE</dt> ** <dd>The SQLITE_DBCONFIG_DEFENSIVE option activates or deactivates the ** "defensive" flag for a database connection. When the defensive ** flag is enabled, language features that allow ordinary SQL to ** deliberately corrupt the database file are disabled. The disabled ** features include but are not limited to the following: |
︙ | ︙ | |||
3279 3280 3281 3282 3283 3284 3285 | ** <dl> ** [[SQLITE_TRACE_STMT]] <dt>SQLITE_TRACE_STMT</dt> ** <dd>^An SQLITE_TRACE_STMT callback is invoked when a prepared statement ** first begins running and possibly at other times during the ** execution of the prepared statement, such as at the start of each ** trigger subprogram. ^The P argument is a pointer to the ** [prepared statement]. ^The X argument is a pointer to a string which | | | | | | 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 | ** <dl> ** [[SQLITE_TRACE_STMT]] <dt>SQLITE_TRACE_STMT</dt> ** <dd>^An SQLITE_TRACE_STMT callback is invoked when a prepared statement ** first begins running and possibly at other times during the ** execution of the prepared statement, such as at the start of each ** trigger subprogram. ^The P argument is a pointer to the ** [prepared statement]. ^The X argument is a pointer to a string which ** is the unexpanded SQL text of the prepared statement or an SQL comment ** that indicates the invocation of a trigger. ^The callback can compute ** the same text that would have been returned by the legacy [sqlite3_trace()] ** interface by using the X argument when X begins with "--" and invoking ** [sqlite3_expanded_sql(P)] otherwise. ** ** [[SQLITE_TRACE_PROFILE]] <dt>SQLITE_TRACE_PROFILE</dt> ** <dd>^An SQLITE_TRACE_PROFILE callback provides approximately the same ** information as is provided by the [sqlite3_profile()] callback. ** ^The P argument is a pointer to the [prepared statement] and the ** X argument points to a 64-bit integer which is approximately ** the number of nanoseconds that the prepared statement took to run. ** ^The SQLITE_TRACE_PROFILE callback is invoked when the statement finishes. ** ** [[SQLITE_TRACE_ROW]] <dt>SQLITE_TRACE_ROW</dt> ** <dd>^An SQLITE_TRACE_ROW callback is invoked whenever a prepared ** statement generates a single row of result. ** ^The P argument is a pointer to the [prepared statement] and the ** X argument is unused. ** ** [[SQLITE_TRACE_CLOSE]] <dt>SQLITE_TRACE_CLOSE</dt> ** <dd>^An SQLITE_TRACE_CLOSE callback is invoked when a database ** connection closes. ** ^The P argument is a pointer to the [database connection] object |
︙ | ︙ | |||
5545 5546 5547 5548 5549 5550 5551 | ** numeric affinity to the value. This means that an attempt is ** made to convert the value to an integer or floating point. If ** such a conversion is possible without loss of information (in other ** words, if the value is a string that looks like a number) ** then the conversion is performed. Otherwise no conversion occurs. ** The [SQLITE_INTEGER | datatype] after conversion is returned.)^ ** | < < < < < < < < < < | 5550 5551 5552 5553 5554 5555 5556 5557 5558 5559 5560 5561 5562 5563 | ** numeric affinity to the value. This means that an attempt is ** made to convert the value to an integer or floating point. If ** such a conversion is possible without loss of information (in other ** words, if the value is a string that looks like a number) ** then the conversion is performed. Otherwise no conversion occurs. ** The [SQLITE_INTEGER | datatype] after conversion is returned.)^ ** ** ^Within the [xUpdate] method of a [virtual table], the ** sqlite3_value_nochange(X) interface returns true if and only if ** the column corresponding to X is unchanged by the UPDATE operation ** that the xUpdate method call was invoked to implement and if ** and the prior [xColumn] method call that was invoked to extracted ** the value for that column returned without setting a result (probably ** because it queried [sqlite3_vtab_nochange()] and found that the column |
︙ | ︙ | |||
5619 5620 5621 5622 5623 5624 5625 5626 5627 5628 5629 5630 5631 5632 | const void *sqlite3_value_text16be(sqlite3_value*); int sqlite3_value_bytes(sqlite3_value*); int sqlite3_value_bytes16(sqlite3_value*); int sqlite3_value_type(sqlite3_value*); int sqlite3_value_numeric_type(sqlite3_value*); int sqlite3_value_nochange(sqlite3_value*); int sqlite3_value_frombind(sqlite3_value*); int sqlite3_value_encoding(sqlite3_value*); /* ** CAPI3REF: Finding The Subtype Of SQL Values ** METHOD: sqlite3_value ** ** The sqlite3_value_subtype(V) function returns the subtype for | > > > > > > > > > > > > > > > > > > > > > | 5614 5615 5616 5617 5618 5619 5620 5621 5622 5623 5624 5625 5626 5627 5628 5629 5630 5631 5632 5633 5634 5635 5636 5637 5638 5639 5640 5641 5642 5643 5644 5645 5646 5647 5648 | const void *sqlite3_value_text16be(sqlite3_value*); int sqlite3_value_bytes(sqlite3_value*); int sqlite3_value_bytes16(sqlite3_value*); int sqlite3_value_type(sqlite3_value*); int sqlite3_value_numeric_type(sqlite3_value*); int sqlite3_value_nochange(sqlite3_value*); int sqlite3_value_frombind(sqlite3_value*); /* ** CAPI3REF: Report the internal text encoding state of an sqlite3_value object ** METHOD: sqlite3_value ** ** ^(The sqlite3_value_encoding(X) interface returns one of [SQLITE_UTF8], ** [SQLITE_UTF16BE], or [SQLITE_UTF16LE] according to the current text encoding ** of the value X, assuming that X has type TEXT.)^ If sqlite3_value_type(X) ** returns something other than SQLITE_TEXT, then the return value from ** sqlite3_value_encoding(X) is meaningless. ^Calls to ** [sqlite3_value_text(X)], [sqlite3_value_text16(X)], [sqlite3_value_text16be(X)], ** [sqlite3_value_text16le(X)], [sqlite3_value_bytes(X)], or ** [sqlite3_value_bytes16(X)] might change the encoding of the value X and ** thus change the return from subsequent calls to sqlite3_value_encoding(X). ** ** This routine is intended for used by applications that test and validate ** the SQLite implementation. This routine is inquiring about the opaque ** internal state of an [sqlite3_value] object. Ordinary applications should ** not need to know what the internal state of an sqlite3_value object is and ** hence should not need to use this interface. */ int sqlite3_value_encoding(sqlite3_value*); /* ** CAPI3REF: Finding The Subtype Of SQL Values ** METHOD: sqlite3_value ** ** The sqlite3_value_subtype(V) function returns the subtype for |
︙ | ︙ | |||
6999 7000 7001 7002 7003 7004 7005 | ** CAPI3REF: Reset Automatic Extension Loading ** ** ^This interface disables all automatic extensions previously ** registered using [sqlite3_auto_extension()]. */ void sqlite3_reset_auto_extension(void); | < < < < < < < < < | 7015 7016 7017 7018 7019 7020 7021 7022 7023 7024 7025 7026 7027 7028 | ** CAPI3REF: Reset Automatic Extension Loading ** ** ^This interface disables all automatic extensions previously ** registered using [sqlite3_auto_extension()]. */ void sqlite3_reset_auto_extension(void); /* ** Structures used by the virtual table interface */ typedef struct sqlite3_vtab sqlite3_vtab; typedef struct sqlite3_index_info sqlite3_index_info; typedef struct sqlite3_vtab_cursor sqlite3_vtab_cursor; typedef struct sqlite3_module sqlite3_module; |
︙ | ︙ | |||
7249 7250 7251 7252 7253 7254 7255 | ** and hence calls to sqlite3_vtab_rhs_value() for those operators will ** always return SQLITE_NOTFOUND. ** ** The collating sequence to be used for comparison can be found using ** the [sqlite3_vtab_collation()] interface. For most real-world virtual ** tables, the collating sequence of constraints does not matter (for example ** because the constraints are numeric) and so the sqlite3_vtab_collation() | | | 7256 7257 7258 7259 7260 7261 7262 7263 7264 7265 7266 7267 7268 7269 7270 | ** and hence calls to sqlite3_vtab_rhs_value() for those operators will ** always return SQLITE_NOTFOUND. ** ** The collating sequence to be used for comparison can be found using ** the [sqlite3_vtab_collation()] interface. For most real-world virtual ** tables, the collating sequence of constraints does not matter (for example ** because the constraints are numeric) and so the sqlite3_vtab_collation() ** interface is not commonly needed. */ #define SQLITE_INDEX_CONSTRAINT_EQ 2 #define SQLITE_INDEX_CONSTRAINT_GT 4 #define SQLITE_INDEX_CONSTRAINT_LE 8 #define SQLITE_INDEX_CONSTRAINT_LT 16 #define SQLITE_INDEX_CONSTRAINT_GE 32 #define SQLITE_INDEX_CONSTRAINT_MATCH 64 |
︙ | ︙ | |||
7408 7409 7410 7411 7412 7413 7414 | ** of the new function always causes an exception to be thrown. So ** the new function is not good for anything by itself. Its only ** purpose is to be a placeholder function that can be overloaded ** by a [virtual table]. */ int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg); | < < < < < < < < < < | 7415 7416 7417 7418 7419 7420 7421 7422 7423 7424 7425 7426 7427 7428 | ** of the new function always causes an exception to be thrown. So ** the new function is not good for anything by itself. Its only ** purpose is to be a placeholder function that can be overloaded ** by a [virtual table]. */ int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg); /* ** CAPI3REF: A Handle To An Open BLOB ** KEYWORDS: {BLOB handle} {BLOB handles} ** ** An instance of this object represents an open BLOB on which ** [sqlite3_blob_open | incremental BLOB I/O] can be performed. ** ^Objects of this type are created by [sqlite3_blob_open()] |
︙ | ︙ | |||
9621 9622 9623 9624 9625 9626 9627 | ** of the constraint specifies an alternative collating sequence via ** a [COLLATE clause] on the column definition within the CREATE TABLE ** statement that was passed into [sqlite3_declare_vtab()], then the ** name of that alternative collating sequence is returned. ** <li><p> Otherwise, "BINARY" is returned. ** </ol> */ | | | 9618 9619 9620 9621 9622 9623 9624 9625 9626 9627 9628 9629 9630 9631 9632 | ** of the constraint specifies an alternative collating sequence via ** a [COLLATE clause] on the column definition within the CREATE TABLE ** statement that was passed into [sqlite3_declare_vtab()], then the ** name of that alternative collating sequence is returned. ** <li><p> Otherwise, "BINARY" is returned. ** </ol> */ const char *sqlite3_vtab_collation(sqlite3_index_info*,int); /* ** CAPI3REF: Determine if a virtual table query is DISTINCT ** METHOD: sqlite3_index_info ** ** This API may only be used from within an [xBestIndex|xBestIndex method] ** of a [virtual table] implementation. The result of calling this |
︙ | ︙ | |||
9890 9891 9892 9893 9894 9895 9896 9897 9898 9899 9900 9901 9902 9903 | ** [sqlite3_stmt_scanstatus(S,X,T,V)] interface. Each constant designates a ** different metric for sqlite3_stmt_scanstatus() to return. ** ** When the value returned to V is a string, space to hold that string is ** managed by the prepared statement S and will be automatically freed when ** S is finalized. ** ** <dl> ** [[SQLITE_SCANSTAT_NLOOP]] <dt>SQLITE_SCANSTAT_NLOOP</dt> ** <dd>^The [sqlite3_int64] variable pointed to by the V parameter will be ** set to the total number of times that the X-th loop has run.</dd> ** ** [[SQLITE_SCANSTAT_NVISIT]] <dt>SQLITE_SCANSTAT_NVISIT</dt> ** <dd>^The [sqlite3_int64] variable pointed to by the V parameter will be set | > > > > | 9887 9888 9889 9890 9891 9892 9893 9894 9895 9896 9897 9898 9899 9900 9901 9902 9903 9904 | ** [sqlite3_stmt_scanstatus(S,X,T,V)] interface. Each constant designates a ** different metric for sqlite3_stmt_scanstatus() to return. ** ** When the value returned to V is a string, space to hold that string is ** managed by the prepared statement S and will be automatically freed when ** S is finalized. ** ** Not all values are available for all query elements. When a value is ** not available, the output variable is set to -1 if the value is numeric, ** or to NULL if it is a string (SQLITE_SCANSTAT_NAME). ** ** <dl> ** [[SQLITE_SCANSTAT_NLOOP]] <dt>SQLITE_SCANSTAT_NLOOP</dt> ** <dd>^The [sqlite3_int64] variable pointed to by the V parameter will be ** set to the total number of times that the X-th loop has run.</dd> ** ** [[SQLITE_SCANSTAT_NVISIT]] <dt>SQLITE_SCANSTAT_NVISIT</dt> ** <dd>^The [sqlite3_int64] variable pointed to by the V parameter will be set |
︙ | ︙ | |||
9917 9918 9919 9920 9921 9922 9923 | ** used for the X-th loop. ** ** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt> ** <dd>^The "const char *" variable pointed to by the V parameter will be set ** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN] ** description for the X-th loop. ** | | | < | | > > > > > > > > > > > > > > > | | | > > > > > > > > > | > | | > | | | < < < < < > > > > > > > > > > > > > | 9918 9919 9920 9921 9922 9923 9924 9925 9926 9927 9928 9929 9930 9931 9932 9933 9934 9935 9936 9937 9938 9939 9940 9941 9942 9943 9944 9945 9946 9947 9948 9949 9950 9951 9952 9953 9954 9955 9956 9957 9958 9959 9960 9961 9962 9963 9964 9965 9966 9967 9968 9969 9970 9971 9972 9973 9974 9975 9976 9977 9978 9979 9980 9981 9982 9983 9984 9985 9986 9987 9988 9989 9990 9991 9992 9993 9994 9995 9996 9997 9998 9999 10000 10001 10002 10003 10004 10005 10006 10007 10008 10009 10010 10011 10012 10013 10014 10015 10016 | ** used for the X-th loop. ** ** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt> ** <dd>^The "const char *" variable pointed to by the V parameter will be set ** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN] ** description for the X-th loop. ** ** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECTID</dt> ** <dd>^The "int" variable pointed to by the V parameter will be set to the ** id for the X-th query plan element. The id value is unique within the ** statement. The select-id is the same value as is output in the first ** column of an [EXPLAIN QUERY PLAN] query. ** </dl> ** ** [[SQLITE_SCANSTAT_PARENTID]] <dt>SQLITE_SCANSTAT_PARENTID</dt> ** <dd>The "int" variable pointed to by the V parameter will be set to the ** the id of the parent of the current query element, if applicable, or ** to zero if the query element has no parent. This is the same value as ** returned in the second column of an [EXPLAIN QUERY PLAN] query. ** ** [[SQLITE_SCANSTAT_NCYCLE]] <dt>SQLITE_SCANSTAT_NCYCLE</dt> ** <dd>The sqlite3_int64 output value is set to the number of cycles, ** according to the processor time-stamp counter, that elapsed while the ** query element was being processed. This value is not available for ** all query elements - if it is unavailable the output variable is ** set to -1. */ #define SQLITE_SCANSTAT_NLOOP 0 #define SQLITE_SCANSTAT_NVISIT 1 #define SQLITE_SCANSTAT_EST 2 #define SQLITE_SCANSTAT_NAME 3 #define SQLITE_SCANSTAT_EXPLAIN 4 #define SQLITE_SCANSTAT_SELECTID 5 #define SQLITE_SCANSTAT_PARENTID 6 #define SQLITE_SCANSTAT_NCYCLE 7 /* ** CAPI3REF: Prepared Statement Scan Status ** METHOD: sqlite3_stmt ** ** These interfaces return information about the predicted and measured ** performance for pStmt. Advanced applications can use this ** interface to compare the predicted and the measured performance and ** issue warnings and/or rerun [ANALYZE] if discrepancies are found. ** ** Since this interface is expected to be rarely used, it is only ** available if SQLite is compiled using the [SQLITE_ENABLE_STMT_SCANSTATUS] ** compile-time option. ** ** The "iScanStatusOp" parameter determines which status information to return. ** The "iScanStatusOp" must be one of the [scanstatus options] or the behavior ** of this interface is undefined. ^The requested measurement is written into ** a variable pointed to by the "pOut" parameter. ** ** The "flags" parameter must be passed a mask of flags. At present only ** one flag is defined - SQLITE_SCANSTAT_COMPLEX. If SQLITE_SCANSTAT_COMPLEX ** is specified, then status information is available for all elements ** of a query plan that are reported by "EXPLAIN QUERY PLAN" output. If ** SQLITE_SCANSTAT_COMPLEX is not specified, then only query plan elements ** that correspond to query loops (the "SCAN..." and "SEARCH..." elements of ** the EXPLAIN QUERY PLAN output) are available. Invoking API ** sqlite3_stmt_scanstatus() is equivalent to calling ** sqlite3_stmt_scanstatus_v2() with a zeroed flags parameter. ** ** Parameter "idx" identifies the specific query element to retrieve statistics ** for. Query elements are numbered starting from zero. A value of -1 may be ** to query for statistics regarding the entire query. ^If idx is out of range ** - less than -1 or greater than or equal to the total number of query ** elements used to implement the statement - a non-zero value is returned and ** the variable that pOut points to is unchanged. ** ** See also: [sqlite3_stmt_scanstatus_reset()] */ int sqlite3_stmt_scanstatus( sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ int idx, /* Index of loop to report on */ int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ void *pOut /* Result written here */ ); int sqlite3_stmt_scanstatus_v2( sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ int idx, /* Index of loop to report on */ int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ int flags, /* Mask of flags defined below */ void *pOut /* Result written here */ ); /* ** CAPI3REF: Prepared Statement Scan Status ** KEYWORDS: {scan status flags} */ #define SQLITE_SCANSTAT_COMPLEX 0x0001 /* ** CAPI3REF: Zero Scan-Status Counters ** METHOD: sqlite3_stmt ** ** ^Zero all [sqlite3_stmt_scanstatus()] related event counters. ** |
︙ | ︙ |
Changes to src/sqliteInt.h.
︙ | ︙ | |||
969 970 971 972 973 974 975 | ** all alignment restrictions correct. ** ** Except, if SQLITE_4_BYTE_ALIGNED_MALLOC is defined, then the ** underlying malloc() implementation might return us 4-byte aligned ** pointers. In that case, only verify 4-byte alignment. */ #ifdef SQLITE_4_BYTE_ALIGNED_MALLOC | | | | 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 | ** all alignment restrictions correct. ** ** Except, if SQLITE_4_BYTE_ALIGNED_MALLOC is defined, then the ** underlying malloc() implementation might return us 4-byte aligned ** pointers. In that case, only verify 4-byte alignment. */ #ifdef SQLITE_4_BYTE_ALIGNED_MALLOC # define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&3)==0) #else # define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&7)==0) #endif /* ** Disable MMAP on platforms where it is known to not work */ #if defined(__OpenBSD__) || defined(__QNXNTO__) # undef SQLITE_MAX_MMAP_SIZE |
︙ | ︙ | |||
1025 1026 1027 1028 1029 1030 1031 | #if !defined(SQLITE_AMALGAMATION) extern u32 sqlite3TreeTrace; #endif #if defined(SQLITE_DEBUG) \ && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_SELECTTRACE) \ || defined(SQLITE_ENABLE_TREETRACE)) # define TREETRACE_ENABLED 1 | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 | #if !defined(SQLITE_AMALGAMATION) extern u32 sqlite3TreeTrace; #endif #if defined(SQLITE_DEBUG) \ && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_SELECTTRACE) \ || defined(SQLITE_ENABLE_TREETRACE)) # define TREETRACE_ENABLED 1 # define TREETRACE(K,P,S,X) \ if(sqlite3TreeTrace&(K)) \ sqlite3DebugPrintf("%u/%d/%p: ",(S)->selId,(P)->addrExplain,(S)),\ sqlite3DebugPrintf X #else # define TREETRACE(K,P,S,X) # define TREETRACE_ENABLED 0 #endif /* TREETRACE flag meanings: ** ** 0x00000001 Beginning and end of SELECT processing ** 0x00000002 WHERE clause processing ** 0x00000004 Query flattener ** 0x00000008 Result-set wildcard expansion ** 0x00000010 Query name resolution ** 0x00000020 Aggregate analysis ** 0x00000040 Window functions ** 0x00000080 Generated column names ** 0x00000100 Move HAVING terms into WHERE ** 0x00000200 Count-of-view optimization ** 0x00000400 Compound SELECT processing ** 0x00000800 Drop superfluous ORDER BY ** 0x00001000 LEFT JOIN simplifies to JOIN ** 0x00002000 Constant propagation ** 0x00004000 Push-down optimization ** 0x00008000 After all FROM-clause analysis ** 0x00010000 Beginning of DELETE/INSERT/UPDATE processing ** 0x00020000 Transform DISTINCT into GROUP BY ** 0x00040000 SELECT tree dump after all code has been generated */ /* ** Macros for "wheretrace" */ extern u32 sqlite3WhereTrace; #if defined(SQLITE_DEBUG) \ && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_WHERETRACE)) # define WHERETRACE(K,X) if(sqlite3WhereTrace&(K)) sqlite3DebugPrintf X # define WHERETRACE_ENABLED 1 #else # define WHERETRACE(K,X) #endif /* ** Bits for the sqlite3WhereTrace mask: ** ** (---any--) Top-level block structure ** 0x-------F High-level debug messages ** 0x----FFF- More detail ** 0xFFFF---- Low-level debug messages ** ** 0x00000001 Code generation ** 0x00000002 Solver ** 0x00000004 Solver costs ** 0x00000008 WhereLoop inserts ** ** 0x00000010 Display sqlite3_index_info xBestIndex calls ** 0x00000020 Range an equality scan metrics ** 0x00000040 IN operator decisions ** 0x00000080 WhereLoop cost adjustements ** 0x00000100 ** 0x00000200 Covering index decisions ** 0x00000400 OR optimization ** 0x00000800 Index scanner ** 0x00001000 More details associated with code generation ** 0x00002000 ** 0x00004000 Show all WHERE terms at key points ** 0x00008000 Show the full SELECT statement at key places ** ** 0x00010000 Show more detail when printing WHERE terms ** 0x00020000 Show WHERE terms returned from whereScanNext() */ /* ** An instance of the following structure is used to store the busy-handler ** callback for a given sqlite handle. ** ** The sqlite.busyHandler member of the sqlite struct contains the busy |
︙ | ︙ | |||
1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 | #define SQLITE_BloomFilter 0x00080000 /* Use a Bloom filter on searches */ #define SQLITE_BloomPulldown 0x00100000 /* Run Bloom filters early */ #define SQLITE_BalancedMerge 0x00200000 /* Balance multi-way merges */ #define SQLITE_ReleaseReg 0x00400000 /* Use OP_ReleaseReg for testing */ #define SQLITE_FlttnUnionAll 0x00800000 /* Disable the UNION ALL flattener */ /* TH3 expects this value ^^^^^^^^^^ See flatten04.test */ #define SQLITE_IndexedExpr 0x01000000 /* Pull exprs from index when able */ #define SQLITE_AllOpts 0xffffffff /* All optimizations */ /* ** Macros for testing whether or not optimizations are enabled or disabled. */ #define OptimizationDisabled(db, mask) (((db)->dbOptFlags&(mask))!=0) #define OptimizationEnabled(db, mask) (((db)->dbOptFlags&(mask))==0) | > | 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 | #define SQLITE_BloomFilter 0x00080000 /* Use a Bloom filter on searches */ #define SQLITE_BloomPulldown 0x00100000 /* Run Bloom filters early */ #define SQLITE_BalancedMerge 0x00200000 /* Balance multi-way merges */ #define SQLITE_ReleaseReg 0x00400000 /* Use OP_ReleaseReg for testing */ #define SQLITE_FlttnUnionAll 0x00800000 /* Disable the UNION ALL flattener */ /* TH3 expects this value ^^^^^^^^^^ See flatten04.test */ #define SQLITE_IndexedExpr 0x01000000 /* Pull exprs from index when able */ #define SQLITE_Coroutines 0x02000000 /* Co-routines for subqueries */ #define SQLITE_AllOpts 0xffffffff /* All optimizations */ /* ** Macros for testing whether or not optimizations are enabled or disabled. */ #define OptimizationDisabled(db, mask) (((db)->dbOptFlags&(mask))!=0) #define OptimizationEnabled(db, mask) (((db)->dbOptFlags&(mask))==0) |
︙ | ︙ | |||
2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 | */ #define SQLITE_AFF_NONE 0x40 /* '@' */ #define SQLITE_AFF_BLOB 0x41 /* 'A' */ #define SQLITE_AFF_TEXT 0x42 /* 'B' */ #define SQLITE_AFF_NUMERIC 0x43 /* 'C' */ #define SQLITE_AFF_INTEGER 0x44 /* 'D' */ #define SQLITE_AFF_REAL 0x45 /* 'E' */ #define sqlite3IsNumericAffinity(X) ((X)>=SQLITE_AFF_NUMERIC) /* ** The SQLITE_AFF_MASK values masks off the significant bits of an ** affinity value. */ | > | 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 | */ #define SQLITE_AFF_NONE 0x40 /* '@' */ #define SQLITE_AFF_BLOB 0x41 /* 'A' */ #define SQLITE_AFF_TEXT 0x42 /* 'B' */ #define SQLITE_AFF_NUMERIC 0x43 /* 'C' */ #define SQLITE_AFF_INTEGER 0x44 /* 'D' */ #define SQLITE_AFF_REAL 0x45 /* 'E' */ #define SQLITE_AFF_FLEXNUM 0x46 /* 'F' */ #define sqlite3IsNumericAffinity(X) ((X)>=SQLITE_AFF_NUMERIC) /* ** The SQLITE_AFF_MASK values masks off the significant bits of an ** affinity value. */ |
︙ | ︙ | |||
2712 2713 2714 2715 2716 2717 2718 2719 2720 | ** fields do not need to be freed when deallocating the AggInfo structure. */ struct AggInfo { u8 directMode; /* Direct rendering mode means take data directly ** from source tables rather than from accumulators */ u8 useSortingIdx; /* In direct mode, reference the sorting index rather ** than the source table */ int sortingIdx; /* Cursor number of the sorting index */ int sortingIdxPTab; /* Cursor number of pseudo-table */ | > < | < < > > > > > > > > > > > > > > | 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 | ** fields do not need to be freed when deallocating the AggInfo structure. */ struct AggInfo { u8 directMode; /* Direct rendering mode means take data directly ** from source tables rather than from accumulators */ u8 useSortingIdx; /* In direct mode, reference the sorting index rather ** than the source table */ u16 nSortingColumn; /* Number of columns in the sorting index */ int sortingIdx; /* Cursor number of the sorting index */ int sortingIdxPTab; /* Cursor number of pseudo-table */ int iFirstReg; /* First register in range for aCol[] and aFunc[] */ ExprList *pGroupBy; /* The group by clause */ struct AggInfo_col { /* For each column used in source tables */ Table *pTab; /* Source table */ Expr *pCExpr; /* The original expression */ int iTable; /* Cursor number of the source table */ i16 iColumn; /* Column number within the source table */ i16 iSorterColumn; /* Column number in the sorting index */ } *aCol; int nColumn; /* Number of used entries in aCol[] */ int nAccumulator; /* Number of columns that show through to the output. ** Additional columns are used only as parameters to ** aggregate functions */ struct AggInfo_func { /* For each aggregate function */ Expr *pFExpr; /* Expression encoding the function */ FuncDef *pFunc; /* The aggregate function implementation */ int iDistinct; /* Ephemeral table used to enforce DISTINCT */ int iDistAddr; /* Address of OP_OpenEphemeral */ } *aFunc; int nFunc; /* Number of entries in aFunc[] */ u32 selId; /* Select to which this AggInfo belongs */ #ifdef SQLITE_DEBUG Select *pSelect; /* SELECT statement that this AggInfo supports */ #endif }; /* ** Macros to compute aCol[] and aFunc[] register numbers. ** ** These macros should not be used prior to the call to ** assignAggregateRegisters() that computes the value of pAggInfo->iFirstReg. ** The assert()s that are part of this macro verify that constraint. */ #define AggInfoColumnReg(A,I) (assert((A)->iFirstReg),(A)->iFirstReg+(I)) #define AggInfoFuncReg(A,I) \ (assert((A)->iFirstReg),(A)->iFirstReg+(A)->nColumn+(I)) /* ** The datatype ynVar is a signed integer, either 16-bit or 32-bit. ** Usually it is 16-bits. But if SQLITE_MAX_VARIABLE_NUMBER is greater ** than 32767 we have to make it 32-bit. 16-bit is preferred because ** it uses less memory in the Expr object, which is a big memory user ** in systems with lots of prepared statements. And few applications ** need more than about 10 or 20 variables. But some extreme users want |
︙ | ︙ | |||
3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 | #define SF_View 0x0200000 /* SELECT statement is a view */ #define SF_NoopOrderBy 0x0400000 /* ORDER BY is ignored for this query */ #define SF_UFSrcCheck 0x0800000 /* Check pSrc as required by UPDATE...FROM */ #define SF_PushDown 0x1000000 /* SELECT has be modified by push-down opt */ #define SF_MultiPart 0x2000000 /* Has multiple incompatible PARTITIONs */ #define SF_CopyCte 0x4000000 /* SELECT statement is a copy of a CTE */ #define SF_OrderByReqd 0x8000000 /* The ORDER BY clause may not be omitted */ /* True if S exists and has SF_NestedFrom */ #define IsNestedFrom(S) ((S)!=0 && ((S)->selFlags&SF_NestedFrom)!=0) /* ** The results of a SELECT can be distributed in several ways, as defined ** by one of the following macros. The "SRT" prefix means "SELECT Result | > | 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 | #define SF_View 0x0200000 /* SELECT statement is a view */ #define SF_NoopOrderBy 0x0400000 /* ORDER BY is ignored for this query */ #define SF_UFSrcCheck 0x0800000 /* Check pSrc as required by UPDATE...FROM */ #define SF_PushDown 0x1000000 /* SELECT has be modified by push-down opt */ #define SF_MultiPart 0x2000000 /* Has multiple incompatible PARTITIONs */ #define SF_CopyCte 0x4000000 /* SELECT statement is a copy of a CTE */ #define SF_OrderByReqd 0x8000000 /* The ORDER BY clause may not be omitted */ #define SF_UpdateFrom 0x10000000 /* Query originates with UPDATE FROM */ /* True if S exists and has SF_NestedFrom */ #define IsNestedFrom(S) ((S)!=0 && ((S)->selFlags&SF_NestedFrom)!=0) /* ** The results of a SELECT can be distributed in several ways, as defined ** by one of the following macros. The "SRT" prefix means "SELECT Result |
︙ | ︙ | |||
3507 3508 3509 3510 3511 3512 3513 | */ struct SelectDest { u8 eDest; /* How to dispose of the results. One of SRT_* above. */ int iSDParm; /* A parameter used by the eDest disposal method */ int iSDParm2; /* A second parameter for the eDest disposal method */ int iSdst; /* Base register where results are written */ int nSdst; /* Number of registers allocated */ | | | 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 | */ struct SelectDest { u8 eDest; /* How to dispose of the results. One of SRT_* above. */ int iSDParm; /* A parameter used by the eDest disposal method */ int iSDParm2; /* A second parameter for the eDest disposal method */ int iSdst; /* Base register where results are written */ int nSdst; /* Number of registers allocated */ char *zAffSdst; /* Affinity used for SRT_Set */ ExprList *pOrderBy; /* Key columns for SRT_Queue and SRT_DistQueue */ }; /* ** During code generation of statements that do inserts into AUTOINCREMENT ** tables, the following information is attached to the Table.u.autoInc.p ** pointer of each autoincrement table to record some side information that |
︙ | ︙ | |||
3566 3567 3568 3569 3570 3571 3572 | # define DbMaskZero(M) memset((M),0,sizeof(M)) # define DbMaskSet(M,I) (M)[(I)/8]|=(1<<((I)&7)) # define DbMaskAllZero(M) sqlite3DbMaskAllZero(M) # define DbMaskNonZero(M) (sqlite3DbMaskAllZero(M)==0) #else typedef unsigned int yDbMask; # define DbMaskTest(M,I) (((M)&(((yDbMask)1)<<(I)))!=0) | | | | | | 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 | # define DbMaskZero(M) memset((M),0,sizeof(M)) # define DbMaskSet(M,I) (M)[(I)/8]|=(1<<((I)&7)) # define DbMaskAllZero(M) sqlite3DbMaskAllZero(M) # define DbMaskNonZero(M) (sqlite3DbMaskAllZero(M)==0) #else typedef unsigned int yDbMask; # define DbMaskTest(M,I) (((M)&(((yDbMask)1)<<(I)))!=0) # define DbMaskZero(M) ((M)=0) # define DbMaskSet(M,I) ((M)|=(((yDbMask)1)<<(I))) # define DbMaskAllZero(M) ((M)==0) # define DbMaskNonZero(M) ((M)!=0) #endif /* ** For each index X that has as one of its arguments either an expression ** or the name of a virtual generated column, and if X is in scope such that ** the value of the expression can simply be read from the index, then ** there is an instance of this object on the Parse.pIdxExpr list. |
︙ | ︙ | |||
3652 3653 3654 3655 3656 3657 3658 | int szOpAlloc; /* Bytes of memory space allocated for Vdbe.aOp[] */ int iSelfTab; /* Table associated with an index on expr, or negative ** of the base register during check-constraint eval */ int nLabel; /* The *negative* of the number of labels used */ int nLabelAlloc; /* Number of slots in aLabel */ int *aLabel; /* Space to hold the labels */ ExprList *pConstExpr;/* Constant expressions */ | | | 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 | int szOpAlloc; /* Bytes of memory space allocated for Vdbe.aOp[] */ int iSelfTab; /* Table associated with an index on expr, or negative ** of the base register during check-constraint eval */ int nLabel; /* The *negative* of the number of labels used */ int nLabelAlloc; /* Number of slots in aLabel */ int *aLabel; /* Space to hold the labels */ ExprList *pConstExpr;/* Constant expressions */ IndexedExpr *pIdxEpr;/* List of expressions used by active indexes */ Token constraintName;/* Name of the constraint currently being parsed */ yDbMask writeMask; /* Start a write transaction on these databases */ yDbMask cookieMask; /* Bitmask of schema verified databases */ int regRowid; /* Register holding rowid of CREATE TABLE entry */ int regRoot; /* Register holding root page number for new objects */ int nMaxArg; /* Max args passed to user function by sub-program */ int nSelect; /* Number of SELECT stmts. Counter for Select.selId */ |
︙ | ︙ | |||
4612 4613 4614 4615 4616 4617 4618 | void sqlite3ColumnSetExpr(Parse*,Table*,Column*,Expr*); Expr *sqlite3ColumnExpr(Table*,Column*); void sqlite3ColumnSetColl(sqlite3*,Column*,const char*zColl); const char *sqlite3ColumnColl(Column*); void sqlite3DeleteColumnNames(sqlite3*,Table*); void sqlite3GenerateColumnNames(Parse *pParse, Select *pSelect); int sqlite3ColumnsFromExprList(Parse*,ExprList*,i16*,Column**); | | | 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 | void sqlite3ColumnSetExpr(Parse*,Table*,Column*,Expr*); Expr *sqlite3ColumnExpr(Table*,Column*); void sqlite3ColumnSetColl(sqlite3*,Column*,const char*zColl); const char *sqlite3ColumnColl(Column*); void sqlite3DeleteColumnNames(sqlite3*,Table*); void sqlite3GenerateColumnNames(Parse *pParse, Select *pSelect); int sqlite3ColumnsFromExprList(Parse*,ExprList*,i16*,Column**); void sqlite3SubqueryColumnTypes(Parse*,Table*,Select*,char); Table *sqlite3ResultSetOfSelect(Parse*,Select*,char); void sqlite3OpenSchemaTable(Parse *, int); Index *sqlite3PrimaryKeyIndex(Table*); i16 sqlite3TableColumnToIndex(Index*, i16); #ifdef SQLITE_OMIT_GENERATED_COLUMNS # define sqlite3TableColumnToStorage(T,X) (X) /* No-op pass-through */ # define sqlite3StorageColumnToTable(T,X) (X) /* No-op pass-through */ |
︙ | ︙ | |||
4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 | const char *sqlite3IndexAffinityStr(sqlite3*, Index*); char *sqlite3TableAffinityStr(sqlite3*,const Table*); void sqlite3TableAffinity(Vdbe*, Table*, int); char sqlite3CompareAffinity(const Expr *pExpr, char aff2); int sqlite3IndexAffinityOk(const Expr *pExpr, char idx_affinity); char sqlite3TableColumnAffinity(const Table*,int); char sqlite3ExprAffinity(const Expr *pExpr); int sqlite3Atoi64(const char*, i64*, int, u8); int sqlite3DecOrHexToI64(const char*, i64*); void sqlite3ErrorWithMsg(sqlite3*, int, const char*,...); void sqlite3Error(sqlite3*,int); void sqlite3ErrorClear(sqlite3*); void sqlite3SystemError(sqlite3*,int); void *sqlite3HexToBlob(sqlite3*, const char *z, int n); u8 sqlite3HexToInt(int h); int sqlite3TwoPartName(Parse *, Token *, Token *, Token **); #if defined(SQLITE_NEED_ERR_NAME) const char *sqlite3ErrName(int); #endif #ifndef SQLITE_OMIT_DESERIALIZE int sqlite3MemdbInit(void); #endif const char *sqlite3ErrStr(int); int sqlite3ReadSchema(Parse *pParse); CollSeq *sqlite3FindCollSeq(sqlite3*,u8 enc, const char*,int); int sqlite3IsBinary(const CollSeq*); CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char*zName); | > > > > | 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 | const char *sqlite3IndexAffinityStr(sqlite3*, Index*); char *sqlite3TableAffinityStr(sqlite3*,const Table*); void sqlite3TableAffinity(Vdbe*, Table*, int); char sqlite3CompareAffinity(const Expr *pExpr, char aff2); int sqlite3IndexAffinityOk(const Expr *pExpr, char idx_affinity); char sqlite3TableColumnAffinity(const Table*,int); char sqlite3ExprAffinity(const Expr *pExpr); int sqlite3ExprDataType(const Expr *pExpr); int sqlite3Atoi64(const char*, i64*, int, u8); int sqlite3DecOrHexToI64(const char*, i64*); void sqlite3ErrorWithMsg(sqlite3*, int, const char*,...); void sqlite3Error(sqlite3*,int); void sqlite3ErrorClear(sqlite3*); void sqlite3SystemError(sqlite3*,int); void *sqlite3HexToBlob(sqlite3*, const char *z, int n); u8 sqlite3HexToInt(int h); int sqlite3TwoPartName(Parse *, Token *, Token *, Token **); #if defined(SQLITE_NEED_ERR_NAME) const char *sqlite3ErrName(int); #endif #ifndef SQLITE_OMIT_DESERIALIZE int sqlite3MemdbInit(void); int sqlite3IsMemdb(const sqlite3_vfs*); #else # define sqlite3IsMemdb(X) 0 #endif const char *sqlite3ErrStr(int); int sqlite3ReadSchema(Parse *pParse); CollSeq *sqlite3FindCollSeq(sqlite3*,u8 enc, const char*,int); int sqlite3IsBinary(const CollSeq*); CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char*zName); |
︙ | ︙ | |||
5138 5139 5140 5141 5142 5143 5144 | void sqlite3NoopDestructor(void*); void *sqlite3OomFault(sqlite3*); void sqlite3OomClear(sqlite3*); int sqlite3ApiExit(sqlite3 *db, int); int sqlite3OpenTempDatabase(Parse *); void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int); | | | 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 | void sqlite3NoopDestructor(void*); void *sqlite3OomFault(sqlite3*); void sqlite3OomClear(sqlite3*); int sqlite3ApiExit(sqlite3 *db, int); int sqlite3OpenTempDatabase(Parse *); void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int); int sqlite3StrAccumEnlarge(StrAccum*, i64); char *sqlite3StrAccumFinish(StrAccum*); void sqlite3StrAccumSetError(StrAccum*, u8); void sqlite3ResultStrAccum(sqlite3_context*,StrAccum*); void sqlite3SelectDestInit(SelectDest*,int,int); Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int); void sqlite3RecordErrorByteOffset(sqlite3*,const char*); void sqlite3RecordErrorOffsetOfExpr(sqlite3*,const Expr*); |
︙ | ︙ | |||
5495 5496 5497 5498 5499 5500 5501 5502 5503 | #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS const char **sqlite3CompileOptions(int *pnOpt); #endif #if SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL) int sqlite3KvvfsInit(void); #endif #endif /* SQLITEINT_H */ | > > > > > > | 5567 5568 5569 5570 5571 5572 5573 5574 5575 5576 5577 5578 5579 5580 5581 | #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS const char **sqlite3CompileOptions(int *pnOpt); #endif #if SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL) int sqlite3KvvfsInit(void); #endif #if defined(VDBE_PROFILE) \ || defined(SQLITE_PERFORMANCE_TRACE) \ || defined(SQLITE_ENABLE_STMT_SCANSTATUS) sqlite3_uint64 sqlite3Hwtime(void); #endif #endif /* SQLITEINT_H */ |
Changes to src/test1.c.
︙ | ︙ | |||
2184 2185 2186 2187 2188 2189 2190 | iValue = sqlite3_stmt_status(pStmt, op, resetFlag); Tcl_SetObjResult(interp, Tcl_NewIntObj(iValue)); return TCL_OK; } #ifdef SQLITE_ENABLE_STMT_SCANSTATUS /* | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | > > | | | > > > > > > > > > > > > > > | | | > | | | > | | | > | | | > | | > > > > > > > > > > > > | | | > | 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 | iValue = sqlite3_stmt_status(pStmt, op, resetFlag); Tcl_SetObjResult(interp, Tcl_NewIntObj(iValue)); return TCL_OK; } #ifdef SQLITE_ENABLE_STMT_SCANSTATUS /* ** Usage: sqlite3_stmt_scanstatus ?-flags FLAGS? STMT IDX */ static int SQLITE_TCLAPI test_stmt_scanstatus( void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ sqlite3_stmt *pStmt; /* First argument */ int idx; /* Second argument */ const char *zName; const char *zExplain; sqlite3_int64 nLoop; sqlite3_int64 nVisit; sqlite3_int64 nCycle; double rEst; int res; int flags = 0; int iSelectId = 0; int iParentId = 0; if( objc==5 ){ struct Flag { const char *zFlag; int flag; } aTbl[] = { {"complex", SQLITE_SCANSTAT_COMPLEX}, {0, 0} }; Tcl_Obj **aFlag = 0; int nFlag = 0; int ii; if( Tcl_ListObjGetElements(interp, objv[2], &nFlag, &aFlag) ){ return TCL_ERROR; } for(ii=0; ii<nFlag; ii++){ int iVal = 0; int res = Tcl_GetIndexFromObjStruct( interp, aFlag[ii], aTbl, sizeof(aTbl[0]), "flag", 0, &iVal ); if( res ) return TCL_ERROR; flags |= aTbl[iVal].flag; } } if( objc!=3 && objc!=5 ){ Tcl_WrongNumArgs(interp, 1, objv, "-flags FLAGS STMT IDX"); return TCL_ERROR; } if( getStmtPointer(interp, Tcl_GetString(objv[objc-2]), &pStmt) || Tcl_GetIntFromObj(interp, objv[objc-1], &idx) ){ return TCL_ERROR; } if( idx<0 ){ Tcl_Obj *pRet = Tcl_NewObj(); res = sqlite3_stmt_scanstatus_v2( pStmt, -1, SQLITE_SCANSTAT_NCYCLE, flags, (void*)&nCycle ); sqlite3_stmt_scanstatus_v2( pStmt, idx, SQLITE_SCANSTAT_NCYCLE, flags, (void*)&nCycle); Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("nCycle", -1)); Tcl_ListObjAppendElement(0, pRet, Tcl_NewWideIntObj(nCycle)); Tcl_SetObjResult(interp, pRet); }else{ res = sqlite3_stmt_scanstatus_v2( pStmt, idx, SQLITE_SCANSTAT_NLOOP, flags, (void*)&nLoop ); if( res==0 ){ Tcl_Obj *pRet = Tcl_NewObj(); Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("nLoop", -1)); Tcl_ListObjAppendElement(0, pRet, Tcl_NewWideIntObj(nLoop)); sqlite3_stmt_scanstatus_v2( pStmt, idx, SQLITE_SCANSTAT_NVISIT, flags, (void*)&nVisit); Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("nVisit", -1)); Tcl_ListObjAppendElement(0, pRet, Tcl_NewWideIntObj(nVisit)); sqlite3_stmt_scanstatus_v2( pStmt, idx, SQLITE_SCANSTAT_EST, flags, (void*)&rEst); Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("nEst", -1)); Tcl_ListObjAppendElement(0, pRet, Tcl_NewDoubleObj(rEst)); sqlite3_stmt_scanstatus_v2( pStmt, idx, SQLITE_SCANSTAT_NAME, flags, (void*)&zName); Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("zName", -1)); Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj(zName, -1)); sqlite3_stmt_scanstatus_v2( pStmt, idx, SQLITE_SCANSTAT_EXPLAIN, flags, (void*)&zExplain); Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("zExplain", -1)); Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj(zExplain, -1)); sqlite3_stmt_scanstatus_v2( pStmt, idx, SQLITE_SCANSTAT_SELECTID, flags, (void*)&iSelectId); Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("iSelectId", -1)); Tcl_ListObjAppendElement(0, pRet, Tcl_NewIntObj(iSelectId)); sqlite3_stmt_scanstatus_v2( pStmt, idx, SQLITE_SCANSTAT_PARENTID, flags, (void*)&iParentId); Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("iParentId", -1)); Tcl_ListObjAppendElement(0, pRet, Tcl_NewIntObj(iParentId)); sqlite3_stmt_scanstatus_v2( pStmt, idx, SQLITE_SCANSTAT_NCYCLE, flags, (void*)&nCycle); Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("nCycle", -1)); Tcl_ListObjAppendElement(0, pRet, Tcl_NewWideIntObj(nCycle)); Tcl_SetObjResult(interp, pRet); }else{ Tcl_ResetResult(interp); } } return TCL_OK; } /* ** Usage: sqlite3_stmt_scanstatus_reset STMT */ |
︙ | ︙ | |||
7705 7706 7707 7708 7709 7710 7711 7712 7713 7714 7715 7716 7717 7718 | void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ extern int sqlite3_amatch_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_appendvfs_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_carray_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_closure_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_csv_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_eval_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_explain_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_fileio_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_decimal_init(sqlite3*,char**,const sqlite3_api_routines*); | > | 7768 7769 7770 7771 7772 7773 7774 7775 7776 7777 7778 7779 7780 7781 7782 | void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ extern int sqlite3_amatch_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_appendvfs_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_basexx_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_carray_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_closure_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_csv_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_eval_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_explain_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_fileio_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_decimal_init(sqlite3*,char**,const sqlite3_api_routines*); |
︙ | ︙ | |||
7736 7737 7738 7739 7740 7741 7742 7743 7744 7745 7746 7747 7748 7749 | #endif static const struct { const char *zExtName; int (*pInit)(sqlite3*,char**,const sqlite3_api_routines*); } aExtension[] = { { "amatch", sqlite3_amatch_init }, { "appendvfs", sqlite3_appendvfs_init }, { "carray", sqlite3_carray_init }, { "closure", sqlite3_closure_init }, { "csv", sqlite3_csv_init }, { "decimal", sqlite3_decimal_init }, { "eval", sqlite3_eval_init }, { "explain", sqlite3_explain_init }, { "fileio", sqlite3_fileio_init }, | > | 7800 7801 7802 7803 7804 7805 7806 7807 7808 7809 7810 7811 7812 7813 7814 | #endif static const struct { const char *zExtName; int (*pInit)(sqlite3*,char**,const sqlite3_api_routines*); } aExtension[] = { { "amatch", sqlite3_amatch_init }, { "appendvfs", sqlite3_appendvfs_init }, { "basexx", sqlite3_basexx_init }, { "carray", sqlite3_carray_init }, { "closure", sqlite3_closure_init }, { "csv", sqlite3_csv_init }, { "decimal", sqlite3_decimal_init }, { "eval", sqlite3_eval_init }, { "explain", sqlite3_explain_init }, { "fileio", sqlite3_fileio_init }, |
︙ | ︙ |
Changes to src/test_demovfs.c.
︙ | ︙ | |||
457 458 459 460 461 462 463 | int rc; /* Return code */ rc = unlink(zPath); if( rc!=0 && errno==ENOENT ) return SQLITE_OK; if( rc==0 && dirSync ){ int dfd; /* File descriptor open on directory */ | < | 457 458 459 460 461 462 463 464 465 466 467 468 469 470 | int rc; /* Return code */ rc = unlink(zPath); if( rc!=0 && errno==ENOENT ) return SQLITE_OK; if( rc==0 && dirSync ){ int dfd; /* File descriptor open on directory */ char *zSlash; char zDir[MAXPATHNAME+1]; /* Name of directory containing file zPath */ /* Figure out the directory name from the path of the file deleted. */ sqlite3_snprintf(MAXPATHNAME, zDir, "%s", zPath); zDir[MAXPATHNAME] = '\0'; zSlash = strrchr(zDir,'/'); |
︙ | ︙ |
Changes to src/treeview.c.
︙ | ︙ | |||
483 484 485 486 487 488 489 | char zFlgs[200]; sqlite3TreeViewPush(&pView, moreToFollow); if( pExpr==0 ){ sqlite3TreeViewLine(pView, "nil"); sqlite3TreeViewPop(&pView); return; } | | > > > | 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 | char zFlgs[200]; sqlite3TreeViewPush(&pView, moreToFollow); if( pExpr==0 ){ sqlite3TreeViewLine(pView, "nil"); sqlite3TreeViewPop(&pView); return; } if( pExpr->flags || pExpr->affExpr || pExpr->vvaFlags || pExpr->pAggInfo ){ StrAccum x; sqlite3StrAccumInit(&x, 0, zFlgs, sizeof(zFlgs), 0); sqlite3_str_appendf(&x, " fg.af=%x.%c", pExpr->flags, pExpr->affExpr ? pExpr->affExpr : 'n'); if( ExprHasProperty(pExpr, EP_OuterON) ){ sqlite3_str_appendf(&x, " outer.iJoin=%d", pExpr->w.iJoin); } if( ExprHasProperty(pExpr, EP_InnerON) ){ sqlite3_str_appendf(&x, " inner.iJoin=%d", pExpr->w.iJoin); } if( ExprHasProperty(pExpr, EP_FromDDL) ){ sqlite3_str_appendf(&x, " DDL"); } if( ExprHasVVAProperty(pExpr, EP_Immutable) ){ sqlite3_str_appendf(&x, " IMMUTABLE"); } if( pExpr->pAggInfo!=0 ){ sqlite3_str_appendf(&x, " agg-column[%d]", pExpr->iAgg); } sqlite3StrAccumFinish(&x); }else{ zFlgs[0] = 0; } switch( pExpr->op ){ case TK_AGG_COLUMN: { sqlite3TreeViewLine(pView, "AGG{%d:%d}%s", |
︙ | ︙ |
Changes to src/update.c.
︙ | ︙ | |||
259 260 261 262 263 264 265 | for(i=0; i<pChanges->nExpr; i++){ pList = sqlite3ExprListAppend(pParse, pList, sqlite3ExprDup(db, pChanges->a[i].pExpr, 0) ); } } pSelect = sqlite3SelectNew(pParse, pList, | | > | 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 | for(i=0; i<pChanges->nExpr; i++){ pList = sqlite3ExprListAppend(pParse, pList, sqlite3ExprDup(db, pChanges->a[i].pExpr, 0) ); } } pSelect = sqlite3SelectNew(pParse, pList, pSrc, pWhere2, pGrp, 0, pOrderBy2, SF_UFSrcCheck|SF_IncludeHidden|SF_UpdateFrom, pLimit2 ); if( pSelect ) pSelect->selFlags |= SF_OrderByReqd; sqlite3SelectDestInit(&dest, eDest, iEph); dest.iSDParm2 = (pPk ? pPk->nKeyCol : -1); sqlite3Select(pParse, pSelect, &dest); sqlite3SelectDelete(db, pSelect); } |
︙ | ︙ |
Changes to src/util.c.
︙ | ︙ | |||
1709 1710 1711 1712 1713 1714 1715 | do{ const char *z = (const char*)&pIn[i+2]; if( strncmp(z,zName,nName)==0 && z[nName]==0 ) return pIn[i]; i += pIn[i+1]; }while( i<mx ); return 0; } | > > > > > > > > > | 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 | do{ const char *z = (const char*)&pIn[i+2]; if( strncmp(z,zName,nName)==0 && z[nName]==0 ) return pIn[i]; i += pIn[i+1]; }while( i<mx ); return 0; } /* ** High-resolution hardware timer used for debugging and testing only. */ #if defined(VDBE_PROFILE) \ || defined(SQLITE_PERFORMANCE_TRACE) \ || defined(SQLITE_ENABLE_STMT_SCANSTATUS) # include "hwtime.h" #endif |
Changes to src/vdbe.c.
︙ | ︙ | |||
129 130 131 132 133 134 135 136 137 138 139 140 141 142 | ** test_addop_breakpoint(pc,pOp) ** sqlite3CorruptError(lineno) ** sqlite3MisuseError(lineno) ** sqlite3CantopenError(lineno) */ static void test_trace_breakpoint(int pc, Op *pOp, Vdbe *v){ static int n = 0; n++; } #endif /* ** Invoke the VDBE coverage callback, if that callback is defined. This ** feature is used for test suite validation only and does not appear an | > > > | 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | ** test_addop_breakpoint(pc,pOp) ** sqlite3CorruptError(lineno) ** sqlite3MisuseError(lineno) ** sqlite3CantopenError(lineno) */ static void test_trace_breakpoint(int pc, Op *pOp, Vdbe *v){ static int n = 0; (void)pc; (void)pOp; (void)v; n++; } #endif /* ** Invoke the VDBE coverage callback, if that callback is defined. This ** feature is used for test suite validation only and does not appear an |
︙ | ︙ | |||
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 | ** SQLITE_AFF_NUMERIC: ** Try to convert pRec to an integer representation or a ** floating-point representation if an integer representation ** is not possible. Note that the integer representation is ** always preferred, even if the affinity is REAL, because ** an integer representation is more space efficient on disk. ** ** SQLITE_AFF_TEXT: ** Convert pRec to a text representation. ** ** SQLITE_AFF_BLOB: ** SQLITE_AFF_NONE: ** No-op. pRec is unchanged. */ static void applyAffinity( Mem *pRec, /* The value to apply affinity to */ char affinity, /* The affinity to be applied */ u8 enc /* Use this text encoding */ ){ if( affinity>=SQLITE_AFF_NUMERIC ){ assert( affinity==SQLITE_AFF_INTEGER || affinity==SQLITE_AFF_REAL | > > > > | | | | 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 | ** SQLITE_AFF_NUMERIC: ** Try to convert pRec to an integer representation or a ** floating-point representation if an integer representation ** is not possible. Note that the integer representation is ** always preferred, even if the affinity is REAL, because ** an integer representation is more space efficient on disk. ** ** SQLITE_AFF_FLEXNUM: ** If the value is text, then try to convert it into a number of ** some kind (integer or real) but do not make any other changes. ** ** SQLITE_AFF_TEXT: ** Convert pRec to a text representation. ** ** SQLITE_AFF_BLOB: ** SQLITE_AFF_NONE: ** No-op. pRec is unchanged. */ static void applyAffinity( Mem *pRec, /* The value to apply affinity to */ char affinity, /* The affinity to be applied */ u8 enc /* Use this text encoding */ ){ if( affinity>=SQLITE_AFF_NUMERIC ){ assert( affinity==SQLITE_AFF_INTEGER || affinity==SQLITE_AFF_REAL || affinity==SQLITE_AFF_NUMERIC || affinity==SQLITE_AFF_FLEXNUM ); if( (pRec->flags & MEM_Int)==0 ){ /*OPTIMIZATION-IF-FALSE*/ if( (pRec->flags & MEM_Real)==0 ){ if( pRec->flags & MEM_Str ) applyNumericAffinity(pRec,1); }else if( affinity<=SQLITE_AFF_REAL ){ sqlite3VdbeIntegerAffinity(pRec); } } }else if( affinity==SQLITE_AFF_TEXT ){ /* Only attempt the conversion to TEXT if there is an integer or real ** representation (blob and NULL do not get converted) but no string ** representation. It would be harmless to repeat the conversion if |
︙ | ︙ | |||
613 614 615 616 617 618 619 | #ifdef SQLITE_DEBUG # define REGISTER_TRACE(R,M) if(db->flags&SQLITE_VdbeTrace)registerTrace(R,M) #else # define REGISTER_TRACE(R,M) #endif | < < < < < < < < < < < | 620 621 622 623 624 625 626 627 628 629 630 631 632 633 | #ifdef SQLITE_DEBUG # define REGISTER_TRACE(R,M) if(db->flags&SQLITE_VdbeTrace)registerTrace(R,M) #else # define REGISTER_TRACE(R,M) #endif #ifndef NDEBUG /* ** This function is only called from within an assert() expression. It ** checks that the sqlite3.nTransaction variable is correctly set to ** the number of non-transaction savepoints currently in the ** linked list starting at sqlite3.pSavepoint. ** |
︙ | ︙ | |||
713 714 715 716 717 718 719 | ** This is the core of sqlite3_step(). */ int sqlite3VdbeExec( Vdbe *p /* The VDBE */ ){ Op *aOp = p->aOp; /* Copy of p->aOp */ Op *pOp = aOp; /* Current operation */ | | < < > | | > | > < | 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 | ** This is the core of sqlite3_step(). */ int sqlite3VdbeExec( Vdbe *p /* The VDBE */ ){ Op *aOp = p->aOp; /* Copy of p->aOp */ Op *pOp = aOp; /* Current operation */ #ifdef SQLITE_DEBUG Op *pOrigOp; /* Value of pOp at the top of the loop */ int nExtraDelete = 0; /* Verifies FORDELETE and AUXDELETE flags */ u8 iCompareIsInit = 0; /* iCompare is initialized */ #endif int rc = SQLITE_OK; /* Value to return */ sqlite3 *db = p->db; /* The database */ u8 resetSchemaOnFault = 0; /* Reset schema after an error if positive */ u8 encoding = ENC(db); /* The database encoding */ int iCompare = 0; /* Result of last comparison */ u64 nVmStep = 0; /* Number of virtual machine steps */ #ifndef SQLITE_OMIT_PROGRESS_CALLBACK u64 nProgressLimit; /* Invoke xProgress() when nVmStep reaches this */ #endif Mem *aMem = p->aMem; /* Copy of p->aMem */ Mem *pIn1 = 0; /* 1st input operand */ Mem *pIn2 = 0; /* 2nd input operand */ Mem *pIn3 = 0; /* 3rd input operand */ Mem *pOut = 0; /* Output operand */ #if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) u64 *pnCycle = 0; #endif /*** INSERT STACK UNION HERE ***/ assert( p->eVdbeState==VDBE_RUN_STATE ); /* sqlite3_step() verifies this */ if( DbMaskNonZero(p->lockMask) ){ sqlite3VdbeEnter(p); } #ifndef SQLITE_OMIT_PROGRESS_CALLBACK if( db->xProgress ){ u32 iPrior = p->aCounter[SQLITE_STMTSTATUS_VM_STEP]; assert( 0 < db->nProgressOps ); nProgressLimit = db->nProgressOps - (iPrior % db->nProgressOps); }else{ nProgressLimit = LARGEST_UINT64; } #endif if( p->rc==SQLITE_NOMEM ){ /* This happens if a malloc() inside a call to sqlite3_column_text() or ** sqlite3_column_text16() failed. */ goto no_mem; } assert( p->rc==SQLITE_OK || (p->rc&0xff)==SQLITE_BUSY ); testcase( p->rc!=SQLITE_OK ); p->rc = SQLITE_OK; assert( p->bIsReader || p->readOnly!=0 ); p->iCurrentTime = 0; assert( p->explain==0 ); db->busyHandler.nBusy = 0; if( AtomicLoad(&db->u1.isInterrupted) ) goto abort_due_to_interrupt; sqlite3VdbeIOTraceSql(p); #ifdef SQLITE_DEBUG sqlite3BeginBenignMalloc(); if( p->pc==0 && (p->db->flags & (SQLITE_VdbeListing|SQLITE_VdbeEQP|SQLITE_VdbeTrace))!=0 |
︙ | ︙ | |||
797 798 799 800 801 802 803 | #endif for(pOp=&aOp[p->pc]; 1; pOp++){ /* Errors are detected by individual opcodes, with an immediate ** jumps to abort_due_to_error. */ assert( rc==SQLITE_OK ); assert( pOp>=aOp && pOp<&aOp[p->nOp]); | > > > > | | | | < < | 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 | #endif for(pOp=&aOp[p->pc]; 1; pOp++){ /* Errors are detected by individual opcodes, with an immediate ** jumps to abort_due_to_error. */ assert( rc==SQLITE_OK ); assert( pOp>=aOp && pOp<&aOp[p->nOp]); nVmStep++; #if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) pOp->nExec++; pnCycle = &pOp->nCycle; # ifdef VDBE_PROFILE if( sqlite3NProfileCnt==0 ) # endif *pnCycle -= sqlite3Hwtime(); #endif /* Only allow tracing if SQLITE_DEBUG is defined. */ #ifdef SQLITE_DEBUG if( db->flags & SQLITE_VdbeTrace ){ sqlite3VdbePrintOp(stdout, (int)(pOp - aOp), pOp); |
︙ | ︙ | |||
864 865 866 867 868 869 870 | if( (opProperty & OPFLG_OUT3)!=0 ){ assert( pOp->p3>0 ); assert( pOp->p3<=(p->nMem+1 - p->nCursor) ); memAboutToChange(p, &aMem[pOp->p3]); } } #endif | | | 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 | if( (opProperty & OPFLG_OUT3)!=0 ){ assert( pOp->p3>0 ); assert( pOp->p3<=(p->nMem+1 - p->nCursor) ); memAboutToChange(p, &aMem[pOp->p3]); } } #endif #ifdef SQLITE_DEBUG pOrigOp = pOp; #endif switch( pOp->opcode ){ /***************************************************************************** ** What follows is a massive switch statement where each case implements a |
︙ | ︙ | |||
1589 1590 1591 1592 1593 1594 1595 | */ case OP_ResultRow: { assert( p->nResColumn==pOp->p2 ); assert( pOp->p1>0 || CORRUPT_DB ); assert( pOp->p1+pOp->p2<=(p->nMem+1 - p->nCursor)+1 ); p->cacheCtr = (p->cacheCtr + 2)|1; | | | | 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 | */ case OP_ResultRow: { assert( p->nResColumn==pOp->p2 ); assert( pOp->p1>0 || CORRUPT_DB ); assert( pOp->p1+pOp->p2<=(p->nMem+1 - p->nCursor)+1 ); p->cacheCtr = (p->cacheCtr + 2)|1; p->pResultRow = &aMem[pOp->p1]; #ifdef SQLITE_DEBUG { Mem *pMem = p->pResultRow; int i; for(i=0; i<pOp->p2; i++){ assert( memIsValid(&pMem[i]) ); REGISTER_TRACE(pOp->p1+i, &pMem[i]); /* The registers in the result will not be used again when the ** prepared statement restarts. This is because sqlite3_column() ** APIs might have caused type conversions of made other changes to |
︙ | ︙ | |||
2122 2123 2124 2125 2126 2127 2128 | u16 flags3; /* Copy of initial value of pIn3->flags */ pIn1 = &aMem[pOp->p1]; pIn3 = &aMem[pOp->p3]; flags1 = pIn1->flags; flags3 = pIn3->flags; if( (flags1 & flags3 & MEM_Int)!=0 ){ | < > > > | 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 | u16 flags3; /* Copy of initial value of pIn3->flags */ pIn1 = &aMem[pOp->p1]; pIn3 = &aMem[pOp->p3]; flags1 = pIn1->flags; flags3 = pIn3->flags; if( (flags1 & flags3 & MEM_Int)!=0 ){ /* Common case of comparison of two integers */ if( pIn3->u.i > pIn1->u.i ){ if( sqlite3aGTb[pOp->opcode] ){ VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); goto jump_to_p2; } iCompare = +1; VVA_ONLY( iCompareIsInit = 1; ) }else if( pIn3->u.i < pIn1->u.i ){ if( sqlite3aLTb[pOp->opcode] ){ VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); goto jump_to_p2; } iCompare = -1; VVA_ONLY( iCompareIsInit = 1; ) }else{ if( sqlite3aEQb[pOp->opcode] ){ VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); goto jump_to_p2; } iCompare = 0; VVA_ONLY( iCompareIsInit = 1; ) } VdbeBranchTaken(0, (pOp->p5 & SQLITE_NULLEQ)?2:3); break; } if( (flags1 | flags3)&MEM_Null ){ /* One or both operands are NULL */ if( pOp->p5 & SQLITE_NULLEQ ){ |
︙ | ︙ | |||
2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 | ** The jump is taken if the SQLITE_JUMPIFNULL bit is set. */ VdbeBranchTaken(2,3); if( pOp->p5 & SQLITE_JUMPIFNULL ){ goto jump_to_p2; } iCompare = 1; /* Operands are not equal */ break; } }else{ /* Neither operand is NULL and we couldn't do the special high-speed ** integer comparison case. So do a general-case comparison. */ affinity = pOp->p5 & SQLITE_AFF_MASK; if( affinity>=SQLITE_AFF_NUMERIC ){ if( (flags1 | flags3)&MEM_Str ){ if( (flags1 & (MEM_Int|MEM_IntReal|MEM_Real|MEM_Str))==MEM_Str ){ applyNumericAffinity(pIn1,0); testcase( flags3==pIn3->flags ); flags3 = pIn3->flags; } if( (flags3 & (MEM_Int|MEM_IntReal|MEM_Real|MEM_Str))==MEM_Str ){ applyNumericAffinity(pIn3,0); } } | > | | | 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 | ** The jump is taken if the SQLITE_JUMPIFNULL bit is set. */ VdbeBranchTaken(2,3); if( pOp->p5 & SQLITE_JUMPIFNULL ){ goto jump_to_p2; } iCompare = 1; /* Operands are not equal */ VVA_ONLY( iCompareIsInit = 1; ) break; } }else{ /* Neither operand is NULL and we couldn't do the special high-speed ** integer comparison case. So do a general-case comparison. */ affinity = pOp->p5 & SQLITE_AFF_MASK; if( affinity>=SQLITE_AFF_NUMERIC ){ if( (flags1 | flags3)&MEM_Str ){ if( (flags1 & (MEM_Int|MEM_IntReal|MEM_Real|MEM_Str))==MEM_Str ){ applyNumericAffinity(pIn1,0); testcase( flags3==pIn3->flags ); flags3 = pIn3->flags; } if( (flags3 & (MEM_Int|MEM_IntReal|MEM_Real|MEM_Str))==MEM_Str ){ applyNumericAffinity(pIn3,0); } } }else if( affinity==SQLITE_AFF_TEXT && ((flags1 | flags3) & MEM_Str)!=0 ){ if( (flags1 & MEM_Str)==0 && (flags1&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ testcase( pIn1->flags & MEM_Int ); testcase( pIn1->flags & MEM_Real ); testcase( pIn1->flags & MEM_IntReal ); sqlite3VdbeMemStringify(pIn1, encoding, 1); testcase( (flags1&MEM_Dyn) != (pIn1->flags&MEM_Dyn) ); flags1 = (pIn1->flags & ~MEM_TypeMask) | (flags1 & MEM_TypeMask); if( NEVER(pIn1==pIn3) ) flags3 = flags1 | MEM_Str; } if( (flags3 & MEM_Str)==0 && (flags3&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ testcase( pIn3->flags & MEM_Int ); testcase( pIn3->flags & MEM_Real ); testcase( pIn3->flags & MEM_IntReal ); sqlite3VdbeMemStringify(pIn3, encoding, 1); testcase( (flags3&MEM_Dyn) != (pIn3->flags&MEM_Dyn) ); |
︙ | ︙ | |||
2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 | res2 = sqlite3aLTb[pOp->opcode]; }else if( res==0 ){ res2 = sqlite3aEQb[pOp->opcode]; }else{ res2 = sqlite3aGTb[pOp->opcode]; } iCompare = res; /* Undo any changes made by applyAffinity() to the input registers. */ assert( (pIn3->flags & MEM_Dyn) == (flags3 & MEM_Dyn) ); pIn3->flags = flags3; assert( (pIn1->flags & MEM_Dyn) == (flags1 & MEM_Dyn) ); pIn1->flags = flags1; | > | 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 | res2 = sqlite3aLTb[pOp->opcode]; }else if( res==0 ){ res2 = sqlite3aEQb[pOp->opcode]; }else{ res2 = sqlite3aGTb[pOp->opcode]; } iCompare = res; VVA_ONLY( iCompareIsInit = 1; ) /* Undo any changes made by applyAffinity() to the input registers. */ assert( (pIn3->flags & MEM_Dyn) == (flags3 & MEM_Dyn) ); pIn3->flags = flags3; assert( (pIn1->flags & MEM_Dyn) == (flags1 & MEM_Dyn) ); pIn1->flags = flags1; |
︙ | ︙ | |||
2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 | int iAddr; for(iAddr = (int)(pOp - aOp) - 1; ALWAYS(iAddr>=0); iAddr--){ if( aOp[iAddr].opcode==OP_ReleaseReg ) continue; assert( aOp[iAddr].opcode==OP_Lt || aOp[iAddr].opcode==OP_Gt ); break; } #endif /* SQLITE_DEBUG */ VdbeBranchTaken(iCompare==0, 2); if( iCompare==0 ) goto jump_to_p2; break; } /* Opcode: Permutation * * * P4 * | > | 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 | int iAddr; for(iAddr = (int)(pOp - aOp) - 1; ALWAYS(iAddr>=0); iAddr--){ if( aOp[iAddr].opcode==OP_ReleaseReg ) continue; assert( aOp[iAddr].opcode==OP_Lt || aOp[iAddr].opcode==OP_Gt ); break; } #endif /* SQLITE_DEBUG */ assert( iCompareIsInit ); VdbeBranchTaken(iCompare==0, 2); if( iCompare==0 ) goto jump_to_p2; break; } /* Opcode: Permutation * * * P4 * |
︙ | ︙ | |||
2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 | assert( memIsValid(&aMem[p2+idx]) ); REGISTER_TRACE(p1+idx, &aMem[p1+idx]); REGISTER_TRACE(p2+idx, &aMem[p2+idx]); assert( i<pKeyInfo->nKeyField ); pColl = pKeyInfo->aColl[i]; bRev = (pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_DESC); iCompare = sqlite3MemCompare(&aMem[p1+idx], &aMem[p2+idx], pColl); if( iCompare ){ if( (pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_BIGNULL) && ((aMem[p1+idx].flags & MEM_Null) || (aMem[p2+idx].flags & MEM_Null)) ){ iCompare = -iCompare; } if( bRev ) iCompare = -iCompare; | > | 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 | assert( memIsValid(&aMem[p2+idx]) ); REGISTER_TRACE(p1+idx, &aMem[p1+idx]); REGISTER_TRACE(p2+idx, &aMem[p2+idx]); assert( i<pKeyInfo->nKeyField ); pColl = pKeyInfo->aColl[i]; bRev = (pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_DESC); iCompare = sqlite3MemCompare(&aMem[p1+idx], &aMem[p2+idx], pColl); VVA_ONLY( iCompareIsInit = 1; ) if( iCompare ){ if( (pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_BIGNULL) && ((aMem[p1+idx].flags & MEM_Null) || (aMem[p2+idx].flags & MEM_Null)) ){ iCompare = -iCompare; } if( bRev ) iCompare = -iCompare; |
︙ | ︙ | |||
2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 | ** in the most recent OP_Compare instruction the P1 vector was less than ** equal to, or greater than the P2 vector, respectively. ** ** This opcode must immediately follow an OP_Compare opcode. */ case OP_Jump: { /* jump */ assert( pOp>aOp && pOp[-1].opcode==OP_Compare ); if( iCompare<0 ){ VdbeBranchTaken(0,4); pOp = &aOp[pOp->p1 - 1]; }else if( iCompare==0 ){ VdbeBranchTaken(1,4); pOp = &aOp[pOp->p2 - 1]; }else{ VdbeBranchTaken(2,4); pOp = &aOp[pOp->p3 - 1]; } | > | 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 | ** in the most recent OP_Compare instruction the P1 vector was less than ** equal to, or greater than the P2 vector, respectively. ** ** This opcode must immediately follow an OP_Compare opcode. */ case OP_Jump: { /* jump */ assert( pOp>aOp && pOp[-1].opcode==OP_Compare ); assert( iCompareIsInit ); if( iCompare<0 ){ VdbeBranchTaken(0,4); pOp = &aOp[pOp->p1 - 1]; }else if( iCompare==0 ){ VdbeBranchTaken(1,4); pOp = &aOp[pOp->p2 - 1]; }else{ VdbeBranchTaken(2,4); pOp = &aOp[pOp->p3 - 1]; } |
︙ | ︙ | |||
2784 2785 2786 2787 2788 2789 2790 | ** If the OPFLAG_LENGTHARG bit is set in P5 then the result is guaranteed ** to only be used by the length() function or the equivalent. The content ** of large blobs is not loaded, thus saving CPU cycles. If the ** OPFLAG_TYPEOFARG bit is set then the result will only be used by the ** typeof() function or the IS NULL or IS NOT NULL operators or the ** equivalent. In this case, all content loading can be omitted. */ | | | 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 | ** If the OPFLAG_LENGTHARG bit is set in P5 then the result is guaranteed ** to only be used by the length() function or the equivalent. The content ** of large blobs is not loaded, thus saving CPU cycles. If the ** OPFLAG_TYPEOFARG bit is set then the result will only be used by the ** typeof() function or the IS NULL or IS NOT NULL operators or the ** equivalent. In this case, all content loading can be omitted. */ case OP_Column: { /* ncycle */ u32 p2; /* column number to retrieve */ VdbeCursor *pC; /* The VDBE cursor */ BtCursor *pCrsr; /* The B-Tree cursor corresponding to pC */ u32 *aOffset; /* aOffset[i] is offset to start of data for i-th column */ int len; /* The length of the serialized data for the column */ int i; /* Loop counter */ Mem *pDest; /* Where to write the extracted value */ |
︙ | ︙ | |||
4136 4137 4138 4139 4140 4141 4142 | ** </ul> ** ** This instruction works like OpenRead except that it opens the cursor ** in read/write mode. ** ** See also: OP_OpenRead, OP_ReopenIdx */ | | | | 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 | ** </ul> ** ** This instruction works like OpenRead except that it opens the cursor ** in read/write mode. ** ** See also: OP_OpenRead, OP_ReopenIdx */ case OP_ReopenIdx: { /* ncycle */ int nField; KeyInfo *pKeyInfo; u32 p2; int iDb; int wrFlag; Btree *pX; VdbeCursor *pCur; Db *pDb; assert( pOp->p5==0 || pOp->p5==OPFLAG_SEEKEQ ); assert( pOp->p4type==P4_KEYINFO ); pCur = p->apCsr[pOp->p1]; if( pCur && pCur->pgnoRoot==(u32)pOp->p2 ){ assert( pCur->iDb==pOp->p3 ); /* Guaranteed by the code generator */ assert( pCur->eCurType==CURTYPE_BTREE ); sqlite3BtreeClearCursor(pCur->uc.pCursor); goto open_cursor_set_hints; } /* If the cursor is not currently open or is open on a different ** index, then fall through into OP_OpenRead to force a reopen */ case OP_OpenRead: /* ncycle */ case OP_OpenWrite: assert( pOp->opcode==OP_OpenWrite || pOp->p5==0 || pOp->p5==OPFLAG_SEEKEQ ); assert( p->bIsReader ); assert( pOp->opcode==OP_OpenRead || pOp->opcode==OP_ReopenIdx || p->readOnly==0 ); |
︙ | ︙ | |||
4251 4252 4253 4254 4255 4256 4257 | ** ** Open a new cursor P1 that points to the same ephemeral table as ** cursor P2. The P2 cursor must have been opened by a prior OP_OpenEphemeral ** opcode. Only ephemeral cursors may be duplicated. ** ** Duplicate ephemeral cursors are used for self-joins of materialized views. */ | | | 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 | ** ** Open a new cursor P1 that points to the same ephemeral table as ** cursor P2. The P2 cursor must have been opened by a prior OP_OpenEphemeral ** opcode. Only ephemeral cursors may be duplicated. ** ** Duplicate ephemeral cursors are used for self-joins of materialized views. */ case OP_OpenDup: { /* ncycle */ VdbeCursor *pOrig; /* The original cursor to be duplicated */ VdbeCursor *pCx; /* The new cursor */ pOrig = p->apCsr[pOp->p2]; assert( pOrig ); assert( pOrig->isEphemeral ); /* Only ephemeral cursors can be duplicated */ |
︙ | ︙ | |||
4313 4314 4315 4316 4317 4318 4319 | ** Synopsis: nColumn=P2 ** ** This opcode works the same as OP_OpenEphemeral. It has a ** different name to distinguish its use. Tables created using ** by this opcode will be used for automatically created transient ** indices in joins. */ | | | | 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 | ** Synopsis: nColumn=P2 ** ** This opcode works the same as OP_OpenEphemeral. It has a ** different name to distinguish its use. Tables created using ** by this opcode will be used for automatically created transient ** indices in joins. */ case OP_OpenAutoindex: /* ncycle */ case OP_OpenEphemeral: { /* ncycle */ VdbeCursor *pCx; KeyInfo *pKeyInfo; static const int vfsFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE | |
︙ | ︙ | |||
4472 4473 4474 4475 4476 4477 4478 | } /* Opcode: Close P1 * * * * ** ** Close a cursor previously opened as P1. If P1 is not ** currently open, this instruction is a no-op. */ | | | 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 | } /* Opcode: Close P1 * * * * ** ** Close a cursor previously opened as P1. If P1 is not ** currently open, this instruction is a no-op. */ case OP_Close: { /* ncycle */ assert( pOp->p1>=0 && pOp->p1<p->nCursor ); sqlite3VdbeFreeCursor(p, p->apCsr[pOp->p1]); p->apCsr[pOp->p1] = 0; break; } #ifdef SQLITE_ENABLE_COLUMN_USED_MASK |
︙ | ︙ | |||
4589 4590 4591 4592 4593 4594 4595 | ** The IdxGE opcode will be skipped if this opcode succeeds, but the ** IdxGE opcode will be used on subsequent loop iterations. The ** OPFLAG_SEEKEQ flags is a hint to the btree layer to say that this ** is an equality search. ** ** See also: Found, NotFound, SeekGt, SeekGe, SeekLt */ | | | | | | 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 | ** The IdxGE opcode will be skipped if this opcode succeeds, but the ** IdxGE opcode will be used on subsequent loop iterations. The ** OPFLAG_SEEKEQ flags is a hint to the btree layer to say that this ** is an equality search. ** ** See also: Found, NotFound, SeekGt, SeekGe, SeekLt */ case OP_SeekLT: /* jump, in3, group, ncycle */ case OP_SeekLE: /* jump, in3, group, ncycle */ case OP_SeekGE: /* jump, in3, group, ncycle */ case OP_SeekGT: { /* jump, in3, group, ncycle */ int res; /* Comparison result */ int oc; /* Opcode */ VdbeCursor *pC; /* The cursor to seek */ UnpackedRecord r; /* The key to seek for */ int nField; /* Number of columns or fields in the key */ i64 iKey; /* The rowid we are to seek to */ int eqOnly; /* Only interested in == results */ |
︙ | ︙ | |||
4858 4859 4860 4861 4862 4863 4864 | ** btree) then jump to SeekGE.P2, ending the loop. ** ** <li> If the cursor ends up on a valid row that is past the target row ** (indicating that the target row does not exist in the btree) then ** jump to SeekOP.P2 if This.P5==0 or to This.P2 if This.P5>0. ** </ol> */ | | | 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 | ** btree) then jump to SeekGE.P2, ending the loop. ** ** <li> If the cursor ends up on a valid row that is past the target row ** (indicating that the target row does not exist in the btree) then ** jump to SeekOP.P2 if This.P5==0 or to This.P2 if This.P5>0. ** </ol> */ case OP_SeekScan: { /* ncycle */ VdbeCursor *pC; int res; int nStep; UnpackedRecord r; assert( pOp[1].opcode==OP_SeekGE ); |
︙ | ︙ | |||
4980 4981 4982 4983 4984 4985 4986 | ** there is known to be at least one match. If the seekHit value is smaller ** than the total number of equality terms in an index lookup, then the ** OP_IfNoHope opcode might run to see if the IN loop can be abandoned ** early, thus saving work. This is part of the IN-early-out optimization. ** ** P1 must be a valid b-tree cursor. */ | | | 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 | ** there is known to be at least one match. If the seekHit value is smaller ** than the total number of equality terms in an index lookup, then the ** OP_IfNoHope opcode might run to see if the IN loop can be abandoned ** early, thus saving work. This is part of the IN-early-out optimization. ** ** P1 must be a valid b-tree cursor. */ case OP_SeekHit: { /* ncycle */ VdbeCursor *pC; assert( pOp->p1>=0 && pOp->p1<p->nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( pOp->p3>=pOp->p2 ); if( pC->seekHit<pOp->p2 ){ #ifdef SQLITE_DEBUG |
︙ | ︙ | |||
5112 5113 5114 5115 5116 5117 5118 | ** ** This operation leaves the cursor in a state where it cannot be ** advanced in either direction. In other words, the Next and Prev ** opcodes do not work after this operation. ** ** See also: NotFound, Found, NotExists */ | | | | | | 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 | ** ** This operation leaves the cursor in a state where it cannot be ** advanced in either direction. In other words, the Next and Prev ** opcodes do not work after this operation. ** ** See also: NotFound, Found, NotExists */ case OP_IfNoHope: { /* jump, in3, ncycle */ VdbeCursor *pC; assert( pOp->p1>=0 && pOp->p1<p->nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); #ifdef SQLITE_DEBUG if( db->flags&SQLITE_VdbeTrace ){ printf("seekHit is %d\n", pC->seekHit); } #endif if( pC->seekHit>=pOp->p4.i ) break; /* Fall through into OP_NotFound */ /* no break */ deliberate_fall_through } case OP_NoConflict: /* jump, in3, ncycle */ case OP_NotFound: /* jump, in3, ncycle */ case OP_Found: { /* jump, in3, ncycle */ int alreadyExists; int ii; VdbeCursor *pC; UnpackedRecord *pIdxKey; UnpackedRecord r; #ifdef SQLITE_TEST |
︙ | ︙ | |||
5258 5259 5260 5261 5262 5263 5264 | ** ** This opcode leaves the cursor in a state where it cannot be advanced ** in either direction. In other words, the Next and Prev opcodes will ** not work following this opcode. ** ** See also: Found, NotFound, NoConflict, SeekRowid */ | | | 5263 5264 5265 5266 5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 | ** ** This opcode leaves the cursor in a state where it cannot be advanced ** in either direction. In other words, the Next and Prev opcodes will ** not work following this opcode. ** ** See also: Found, NotFound, NoConflict, SeekRowid */ case OP_SeekRowid: { /* jump, in3, ncycle */ VdbeCursor *pC; BtCursor *pCrsr; int res; u64 iKey; pIn3 = &aMem[pOp->p3]; testcase( pIn3->flags & MEM_Int ); |
︙ | ︙ | |||
5283 5284 5285 5286 5287 5288 5289 | applyAffinity(&x, SQLITE_AFF_NUMERIC, encoding); if( (x.flags & MEM_Int)==0 ) goto jump_to_p2; iKey = x.u.i; goto notExistsWithKey; } /* Fall through into OP_NotExists */ /* no break */ deliberate_fall_through | | | 5288 5289 5290 5291 5292 5293 5294 5295 5296 5297 5298 5299 5300 5301 5302 | applyAffinity(&x, SQLITE_AFF_NUMERIC, encoding); if( (x.flags & MEM_Int)==0 ) goto jump_to_p2; iKey = x.u.i; goto notExistsWithKey; } /* Fall through into OP_NotExists */ /* no break */ deliberate_fall_through case OP_NotExists: /* jump, in3, ncycle */ pIn3 = &aMem[pOp->p3]; assert( (pIn3->flags & MEM_Int)!=0 || pOp->opcode==OP_SeekRowid ); assert( pOp->p1>=0 && pOp->p1<p->nCursor ); iKey = pIn3->u.i; notExistsWithKey: pC = p->apCsr[pOp->p1]; assert( pC!=0 ); |
︙ | ︙ | |||
5563 5564 5565 5566 5567 5568 5569 | /* Prevent post-update hook from running in cases when it should not */ pTab = 0; } } if( pOp->p5 & OPFLAG_ISNOOP ) break; #endif | > | > | > > | 5568 5569 5570 5571 5572 5573 5574 5575 5576 5577 5578 5579 5580 5581 5582 5583 5584 5585 5586 5587 5588 5589 5590 5591 5592 5593 5594 5595 5596 5597 | /* Prevent post-update hook from running in cases when it should not */ pTab = 0; } } if( pOp->p5 & OPFLAG_ISNOOP ) break; #endif assert( (pOp->p5 & OPFLAG_LASTROWID)==0 || (pOp->p5 & OPFLAG_NCHANGE)!=0 ); if( pOp->p5 & OPFLAG_NCHANGE ){ p->nChange++; if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = x.nKey; } assert( (pData->flags & (MEM_Blob|MEM_Str))!=0 || pData->n==0 ); x.pData = pData->z; x.nData = pData->n; seekResult = ((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0); if( pData->flags & MEM_Zero ){ x.nZero = pData->u.nZero; }else{ x.nZero = 0; } x.pKey = 0; assert( BTREE_PREFORMAT==OPFLAG_PREFORMAT ); rc = sqlite3BtreeInsert(pC->uc.pCursor, &x, (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION|OPFLAG_PREFORMAT)), seekResult ); pC->deferredMoveto = 0; pC->cacheStatus = CACHE_STALE; |
︙ | ︙ | |||
5906 5907 5908 5909 5910 5911 5912 | ** Store in register P2 an integer which is the key of the table entry that ** P1 is currently point to. ** ** P1 can be either an ordinary table or a virtual table. There used to ** be a separate OP_VRowid opcode for use with virtual tables, but this ** one opcode now works for both table types. */ | | | 5915 5916 5917 5918 5919 5920 5921 5922 5923 5924 5925 5926 5927 5928 5929 | ** Store in register P2 an integer which is the key of the table entry that ** P1 is currently point to. ** ** P1 can be either an ordinary table or a virtual table. There used to ** be a separate OP_VRowid opcode for use with virtual tables, but this ** one opcode now works for both table types. */ case OP_Rowid: { /* out2, ncycle */ VdbeCursor *pC; i64 v; sqlite3_vtab *pVtab; const sqlite3_module *pModule; pOut = out2Prerelease(p, pOp); assert( pOp->p1>=0 && pOp->p1<p->nCursor ); |
︙ | ︙ | |||
6005 6006 6007 6008 6009 6010 6011 | ** If P2 is 0 or if the table or index is not empty, fall through ** to the following instruction. ** ** This opcode leaves the cursor configured to move in reverse order, ** from the end toward the beginning. In other words, the cursor is ** configured to use Prev, not Next. */ | | | | 6014 6015 6016 6017 6018 6019 6020 6021 6022 6023 6024 6025 6026 6027 6028 6029 | ** If P2 is 0 or if the table or index is not empty, fall through ** to the following instruction. ** ** This opcode leaves the cursor configured to move in reverse order, ** from the end toward the beginning. In other words, the cursor is ** configured to use Prev, not Next. */ case OP_SeekEnd: /* ncycle */ case OP_Last: { /* jump, ncycle */ VdbeCursor *pC; BtCursor *pCrsr; int res; assert( pOp->p1>=0 && pOp->p1<p->nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); |
︙ | ︙ | |||
6111 6112 6113 6114 6115 6116 6117 | ** If the table or index is not empty, fall through to the following ** instruction. ** ** This opcode leaves the cursor configured to move in forward order, ** from the beginning toward the end. In other words, the cursor is ** configured to use Next, not Prev. */ | | | 6120 6121 6122 6123 6124 6125 6126 6127 6128 6129 6130 6131 6132 6133 6134 | ** If the table or index is not empty, fall through to the following ** instruction. ** ** This opcode leaves the cursor configured to move in forward order, ** from the beginning toward the end. In other words, the cursor is ** configured to use Next, not Prev. */ case OP_Rewind: { /* jump, ncycle */ VdbeCursor *pC; BtCursor *pCrsr; int res; assert( pOp->p1>=0 && pOp->p1<p->nCursor ); assert( pOp->p5==0 ); pC = p->apCsr[pOp->p1]; |
︙ | ︙ | |||
6205 6206 6207 6208 6209 6210 6211 | VdbeCursor *pC; pC = p->apCsr[pOp->p1]; assert( isSorter(pC) ); rc = sqlite3VdbeSorterNext(db, pC); goto next_tail; | | | | 6214 6215 6216 6217 6218 6219 6220 6221 6222 6223 6224 6225 6226 6227 6228 6229 6230 6231 6232 6233 6234 6235 6236 6237 6238 6239 6240 6241 6242 6243 | VdbeCursor *pC; pC = p->apCsr[pOp->p1]; assert( isSorter(pC) ); rc = sqlite3VdbeSorterNext(db, pC); goto next_tail; case OP_Prev: /* jump, ncycle */ assert( pOp->p1>=0 && pOp->p1<p->nCursor ); assert( pOp->p5==0 || pOp->p5==SQLITE_STMTSTATUS_FULLSCAN_STEP || pOp->p5==SQLITE_STMTSTATUS_AUTOINDEX); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( pC->deferredMoveto==0 ); assert( pC->eCurType==CURTYPE_BTREE ); assert( pC->seekOp==OP_SeekLT || pC->seekOp==OP_SeekLE || pC->seekOp==OP_Last || pC->seekOp==OP_IfNoHope || pC->seekOp==OP_NullRow); rc = sqlite3BtreePrevious(pC->uc.pCursor, pOp->p3); goto next_tail; case OP_Next: /* jump, ncycle */ assert( pOp->p1>=0 && pOp->p1<p->nCursor ); assert( pOp->p5==0 || pOp->p5==SQLITE_STMTSTATUS_FULLSCAN_STEP || pOp->p5==SQLITE_STMTSTATUS_AUTOINDEX); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( pC->deferredMoveto==0 ); |
︙ | ︙ | |||
6412 6413 6414 6415 6416 6417 6418 | ** ** Write into register P2 an integer which is the last entry in the record at ** the end of the index key pointed to by cursor P1. This integer should be ** the rowid of the table entry to which this index entry points. ** ** See also: Rowid, MakeRecord. */ | | | | 6421 6422 6423 6424 6425 6426 6427 6428 6429 6430 6431 6432 6433 6434 6435 6436 | ** ** Write into register P2 an integer which is the last entry in the record at ** the end of the index key pointed to by cursor P1. This integer should be ** the rowid of the table entry to which this index entry points. ** ** See also: Rowid, MakeRecord. */ case OP_DeferredSeek: /* ncycle */ case OP_IdxRowid: { /* out2, ncycle */ VdbeCursor *pC; /* The P1 index cursor */ VdbeCursor *pTabCur; /* The P2 table cursor (OP_DeferredSeek only) */ i64 rowid; /* Rowid that P1 current points to */ assert( pOp->p1>=0 && pOp->p1<p->nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); |
︙ | ︙ | |||
6475 6476 6477 6478 6479 6480 6481 | /* Opcode: FinishSeek P1 * * * * ** ** If cursor P1 was previously moved via OP_DeferredSeek, complete that ** seek operation now, without further delay. If the cursor seek has ** already occurred, this instruction is a no-op. */ | | | | 6484 6485 6486 6487 6488 6489 6490 6491 6492 6493 6494 6495 6496 6497 6498 6499 | /* Opcode: FinishSeek P1 * * * * ** ** If cursor P1 was previously moved via OP_DeferredSeek, complete that ** seek operation now, without further delay. If the cursor seek has ** already occurred, this instruction is a no-op. */ case OP_FinishSeek: { /* ncycle */ VdbeCursor *pC; /* The P1 index cursor */ assert( pOp->p1>=0 && pOp->p1<p->nCursor ); pC = p->apCsr[pOp->p1]; if( pC->deferredMoveto ){ rc = sqlite3VdbeFinishMoveto(pC); if( rc ) goto abort_due_to_error; } |
︙ | ︙ | |||
6531 6532 6533 6534 6535 6536 6537 | ** key that omits the PRIMARY KEY or ROWID. Compare this key value against ** the index that P1 is currently pointing to, ignoring the PRIMARY KEY or ** ROWID on the P1 index. ** ** If the P1 index entry is less than or equal to the key value then jump ** to P2. Otherwise fall through to the next instruction. */ | | | | | | 6540 6541 6542 6543 6544 6545 6546 6547 6548 6549 6550 6551 6552 6553 6554 6555 6556 6557 | ** key that omits the PRIMARY KEY or ROWID. Compare this key value against ** the index that P1 is currently pointing to, ignoring the PRIMARY KEY or ** ROWID on the P1 index. ** ** If the P1 index entry is less than or equal to the key value then jump ** to P2. Otherwise fall through to the next instruction. */ case OP_IdxLE: /* jump, ncycle */ case OP_IdxGT: /* jump, ncycle */ case OP_IdxLT: /* jump, ncycle */ case OP_IdxGE: { /* jump, ncycle */ VdbeCursor *pC; int res; UnpackedRecord r; assert( pOp->p1>=0 && pOp->p1<p->nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); |
︙ | ︙ | |||
7155 7156 7157 7158 7159 7160 7161 | pFrame->aMem = p->aMem; pFrame->nMem = p->nMem; pFrame->apCsr = p->apCsr; pFrame->nCursor = p->nCursor; pFrame->aOp = p->aOp; pFrame->nOp = p->nOp; pFrame->token = pProgram->token; | < < < | 7164 7165 7166 7167 7168 7169 7170 7171 7172 7173 7174 7175 7176 7177 | pFrame->aMem = p->aMem; pFrame->nMem = p->nMem; pFrame->apCsr = p->apCsr; pFrame->nCursor = p->nCursor; pFrame->aOp = p->aOp; pFrame->nOp = p->nOp; pFrame->token = pProgram->token; #ifdef SQLITE_DEBUG pFrame->iFrameMagic = SQLITE_FRAME_MAGIC; #endif pEnd = &VdbeFrameMem(pFrame)[pFrame->nChildMem]; for(pMem=VdbeFrameMem(pFrame); pMem!=pEnd; pMem++){ pMem->flags = MEM_Undefined; |
︙ | ︙ | |||
7194 7195 7196 7197 7198 7199 7200 | p->nMem = pFrame->nChildMem; p->nCursor = (u16)pFrame->nChildCsr; p->apCsr = (VdbeCursor **)&aMem[p->nMem]; pFrame->aOnce = (u8*)&p->apCsr[pProgram->nCsr]; memset(pFrame->aOnce, 0, (pProgram->nOp + 7)/8); p->aOp = aOp = pProgram->aOp; p->nOp = pProgram->nOp; | < < < | 7200 7201 7202 7203 7204 7205 7206 7207 7208 7209 7210 7211 7212 7213 | p->nMem = pFrame->nChildMem; p->nCursor = (u16)pFrame->nChildCsr; p->apCsr = (VdbeCursor **)&aMem[p->nMem]; pFrame->aOnce = (u8*)&p->apCsr[pProgram->nCsr]; memset(pFrame->aOnce, 0, (pProgram->nOp + 7)/8); p->aOp = aOp = pProgram->aOp; p->nOp = pProgram->nOp; #ifdef SQLITE_DEBUG /* Verify that second and subsequent executions of the same trigger do not ** try to reuse register values from the first use. */ { int i; for(i=0; i<p->nMem; i++){ aMem[i].pScopyFrom = 0; /* Prevent false-positive AboutToChange() errs */ |
︙ | ︙ | |||
7953 7954 7955 7956 7957 7958 7959 | #ifndef SQLITE_OMIT_VIRTUALTABLE /* Opcode: VOpen P1 * * P4 * ** ** P4 is a pointer to a virtual table object, an sqlite3_vtab structure. ** P1 is a cursor number. This opcode opens a cursor to the virtual ** table and stores that cursor in P1. */ | | | 7956 7957 7958 7959 7960 7961 7962 7963 7964 7965 7966 7967 7968 7969 7970 | #ifndef SQLITE_OMIT_VIRTUALTABLE /* Opcode: VOpen P1 * * P4 * ** ** P4 is a pointer to a virtual table object, an sqlite3_vtab structure. ** P1 is a cursor number. This opcode opens a cursor to the virtual ** table and stores that cursor in P1. */ case OP_VOpen: { /* ncycle */ VdbeCursor *pCur; sqlite3_vtab_cursor *pVCur; sqlite3_vtab *pVtab; const sqlite3_module *pModule; assert( p->bIsReader ); pCur = 0; |
︙ | ︙ | |||
8000 8001 8002 8003 8004 8005 8006 | ** Set register P2 to be a pointer to a ValueList object for cursor P1 ** with cache register P3 and output register P3+1. This ValueList object ** can be used as the first argument to sqlite3_vtab_in_first() and ** sqlite3_vtab_in_next() to extract all of the values stored in the P1 ** cursor. Register P3 is used to hold the values returned by ** sqlite3_vtab_in_first() and sqlite3_vtab_in_next(). */ | | | 8003 8004 8005 8006 8007 8008 8009 8010 8011 8012 8013 8014 8015 8016 8017 | ** Set register P2 to be a pointer to a ValueList object for cursor P1 ** with cache register P3 and output register P3+1. This ValueList object ** can be used as the first argument to sqlite3_vtab_in_first() and ** sqlite3_vtab_in_next() to extract all of the values stored in the P1 ** cursor. Register P3 is used to hold the values returned by ** sqlite3_vtab_in_first() and sqlite3_vtab_in_next(). */ case OP_VInitIn: { /* out2, ncycle */ VdbeCursor *pC; /* The cursor containing the RHS values */ ValueList *pRhs; /* New ValueList object to put in reg[P2] */ pC = p->apCsr[pOp->p1]; pRhs = sqlite3_malloc64( sizeof(*pRhs) ); if( pRhs==0 ) goto no_mem; pRhs->pCsr = pC->uc.pCursor; |
︙ | ︙ | |||
8037 8038 8039 8040 8041 8042 8043 | ** P3. Register P3+1 stores the argc parameter to be passed to the ** xFilter method. Registers P3+2..P3+1+argc are the argc ** additional parameters which are passed to ** xFilter as argv. Register P3+2 becomes argv[0] when passed to xFilter. ** ** A jump is made to P2 if the result set after filtering would be empty. */ | | | 8040 8041 8042 8043 8044 8045 8046 8047 8048 8049 8050 8051 8052 8053 8054 | ** P3. Register P3+1 stores the argc parameter to be passed to the ** xFilter method. Registers P3+2..P3+1+argc are the argc ** additional parameters which are passed to ** xFilter as argv. Register P3+2 becomes argv[0] when passed to xFilter. ** ** A jump is made to P2 if the result set after filtering would be empty. */ case OP_VFilter: { /* jump, ncycle */ int nArg; int iQuery; const sqlite3_module *pModule; Mem *pQuery; Mem *pArgc; sqlite3_vtab_cursor *pVCur; sqlite3_vtab *pVtab; |
︙ | ︙ | |||
8097 8098 8099 8100 8101 8102 8103 | ** an unchanging column during an UPDATE operation, then the P5 ** value is OPFLAG_NOCHNG. This will cause the sqlite3_vtab_nochange() ** function to return true inside the xColumn method of the virtual ** table implementation. The P5 column might also contain other ** bits (OPFLAG_LENGTHARG or OPFLAG_TYPEOFARG) but those bits are ** unused by OP_VColumn. */ | | | 8100 8101 8102 8103 8104 8105 8106 8107 8108 8109 8110 8111 8112 8113 8114 | ** an unchanging column during an UPDATE operation, then the P5 ** value is OPFLAG_NOCHNG. This will cause the sqlite3_vtab_nochange() ** function to return true inside the xColumn method of the virtual ** table implementation. The P5 column might also contain other ** bits (OPFLAG_LENGTHARG or OPFLAG_TYPEOFARG) but those bits are ** unused by OP_VColumn. */ case OP_VColumn: { /* ncycle */ sqlite3_vtab *pVtab; const sqlite3_module *pModule; Mem *pDest; sqlite3_context sContext; VdbeCursor *pCur = p->apCsr[pOp->p1]; assert( pCur!=0 ); |
︙ | ︙ | |||
8149 8150 8151 8152 8153 8154 8155 | #ifndef SQLITE_OMIT_VIRTUALTABLE /* Opcode: VNext P1 P2 * * * ** ** Advance virtual table P1 to the next row in its result set and ** jump to instruction P2. Or, if the virtual table has reached ** the end of its result set, then fall through to the next instruction. */ | | | 8152 8153 8154 8155 8156 8157 8158 8159 8160 8161 8162 8163 8164 8165 8166 | #ifndef SQLITE_OMIT_VIRTUALTABLE /* Opcode: VNext P1 P2 * * * ** ** Advance virtual table P1 to the next row in its result set and ** jump to instruction P2. Or, if the virtual table has reached ** the end of its result set, then fall through to the next instruction. */ case OP_VNext: { /* jump, ncycle */ sqlite3_vtab *pVtab; const sqlite3_module *pModule; int res; VdbeCursor *pCur; pCur = p->apCsr[pOp->p1]; assert( pCur!=0 ); |
︙ | ︙ | |||
8732 8733 8734 8735 8736 8737 8738 | ** The cases of the switch statement above this line should all be indented ** by 6 spaces. But the left-most 6 spaces have been removed to improve the ** readability. From this point on down, the normal indentation rules are ** restored. *****************************************************************************/ } | | < | | > | < > | 8735 8736 8737 8738 8739 8740 8741 8742 8743 8744 8745 8746 8747 8748 8749 8750 8751 8752 8753 8754 | ** The cases of the switch statement above this line should all be indented ** by 6 spaces. But the left-most 6 spaces have been removed to improve the ** readability. From this point on down, the normal indentation rules are ** restored. *****************************************************************************/ } #if defined(VDBE_PROFILE) *pnCycle += sqlite3NProfileCnt ? sqlite3NProfileCnt : sqlite3Hwtime(); pnCycle = 0; #elif defined(SQLITE_ENABLE_STMT_SCANSTATUS) *pnCycle += sqlite3Hwtime(); pnCycle = 0; #endif /* The following code adds nothing to the actual functionality ** of the program. It is only here for testing and debugging. ** On the other hand, it does burn CPU cycles every time through ** the evaluator loop. So we can leave it out when NDEBUG is defined. */ |
︙ | ︙ | |||
8813 8814 8815 8816 8817 8818 8819 8820 8821 8822 8823 8824 8825 8826 8827 8828 8829 8830 | sqlite3ResetOneSchema(db, resetSchemaOnFault-1); } /* This is the only way out of this procedure. We have to ** release the mutexes on btrees that were acquired at the ** top. */ vdbe_return: #ifndef SQLITE_OMIT_PROGRESS_CALLBACK while( nVmStep>=nProgressLimit && db->xProgress!=0 ){ nProgressLimit += db->nProgressOps; if( db->xProgress(db->pProgressArg) ){ nProgressLimit = LARGEST_UINT64; rc = SQLITE_INTERRUPT; goto abort_due_to_error; } } #endif p->aCounter[SQLITE_STMTSTATUS_VM_STEP] += (int)nVmStep; | > > > > > > > > > > > > > | > | 8816 8817 8818 8819 8820 8821 8822 8823 8824 8825 8826 8827 8828 8829 8830 8831 8832 8833 8834 8835 8836 8837 8838 8839 8840 8841 8842 8843 8844 8845 8846 8847 8848 8849 8850 8851 8852 8853 8854 8855 | sqlite3ResetOneSchema(db, resetSchemaOnFault-1); } /* This is the only way out of this procedure. We have to ** release the mutexes on btrees that were acquired at the ** top. */ vdbe_return: #if defined(VDBE_PROFILE) if( pnCycle ){ *pnCycle += sqlite3NProfileCnt ? sqlite3NProfileCnt : sqlite3Hwtime(); pnCycle = 0; } #elif defined(SQLITE_ENABLE_STMT_SCANSTATUS) if( pnCycle ){ *pnCycle += sqlite3Hwtime(); pnCycle = 0; } #endif #ifndef SQLITE_OMIT_PROGRESS_CALLBACK while( nVmStep>=nProgressLimit && db->xProgress!=0 ){ nProgressLimit += db->nProgressOps; if( db->xProgress(db->pProgressArg) ){ nProgressLimit = LARGEST_UINT64; rc = SQLITE_INTERRUPT; goto abort_due_to_error; } } #endif p->aCounter[SQLITE_STMTSTATUS_VM_STEP] += (int)nVmStep; if( DbMaskNonZero(p->lockMask) ){ sqlite3VdbeLeave(p); } assert( rc!=SQLITE_OK || nExtraDelete==0 || sqlite3_strlike("DELETE%",p->zSql,0)!=0 ); return rc; /* Jump to here if a string or blob larger than SQLITE_MAX_LENGTH ** is encountered. |
︙ | ︙ |
Changes to src/vdbe.h.
︙ | ︙ | |||
63 64 65 66 67 68 69 | #ifdef SQLITE_ENABLE_CURSOR_HINTS Expr *pExpr; /* Used when p4type is P4_EXPR */ #endif } p4; #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS char *zComment; /* Comment to improve readability */ #endif | < < < < > > > > | 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | #ifdef SQLITE_ENABLE_CURSOR_HINTS Expr *pExpr; /* Used when p4type is P4_EXPR */ #endif } p4; #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS char *zComment; /* Comment to improve readability */ #endif #ifdef SQLITE_VDBE_COVERAGE u32 iSrcLine; /* Source-code line that generated this opcode ** with flags in the upper 8 bits */ #endif #if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) u64 nExec; u64 nCycle; #endif }; typedef struct VdbeOp VdbeOp; /* ** A sub-routine used to implement a trigger program. |
︙ | ︙ | |||
201 202 203 204 205 206 207 | void sqlite3VdbeNoJumpsOutsideSubrtn(Vdbe*,int,int,int); #else # define sqlite3VdbeVerifyAbortable(A,B) # define sqlite3VdbeNoJumpsOutsideSubrtn(A,B,C,D) #endif VdbeOp *sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp,int iLineno); #ifndef SQLITE_OMIT_EXPLAIN | | > > > > > > | 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | void sqlite3VdbeNoJumpsOutsideSubrtn(Vdbe*,int,int,int); #else # define sqlite3VdbeVerifyAbortable(A,B) # define sqlite3VdbeNoJumpsOutsideSubrtn(A,B,C,D) #endif VdbeOp *sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp,int iLineno); #ifndef SQLITE_OMIT_EXPLAIN int sqlite3VdbeExplain(Parse*,u8,const char*,...); void sqlite3VdbeExplainPop(Parse*); int sqlite3VdbeExplainParent(Parse*); # define ExplainQueryPlan(P) sqlite3VdbeExplain P # ifdef SQLITE_ENABLE_STMT_SCANSTATUS # define ExplainQueryPlan2(V,P) (V = sqlite3VdbeExplain P) # else # define ExplainQueryPlan2(V,P) ExplainQueryPlan(P) # endif # define ExplainQueryPlanPop(P) sqlite3VdbeExplainPop(P) # define ExplainQueryPlanParent(P) sqlite3VdbeExplainParent(P) #else # define ExplainQueryPlan(P) # define ExplainQueryPlan2(V,P) # define ExplainQueryPlanPop(P) # define ExplainQueryPlanParent(P) 0 # define sqlite3ExplainBreakpoint(A,B) /*no-op*/ #endif #if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_EXPLAIN) void sqlite3ExplainBreakpoint(const char*,const char*); #else |
︙ | ︙ | |||
381 382 383 384 385 386 387 388 | # define VdbeCoverageNeverNullIf(v,x) # define VdbeCoverageEqNe(v) # define VDBE_OFFSET_LINENO(x) 0 #endif #ifdef SQLITE_ENABLE_STMT_SCANSTATUS void sqlite3VdbeScanStatus(Vdbe*, int, int, int, LogEst, const char*); #else | > > | > > | 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 | # define VdbeCoverageNeverNullIf(v,x) # define VdbeCoverageEqNe(v) # define VDBE_OFFSET_LINENO(x) 0 #endif #ifdef SQLITE_ENABLE_STMT_SCANSTATUS void sqlite3VdbeScanStatus(Vdbe*, int, int, int, LogEst, const char*); void sqlite3VdbeScanStatusRange(Vdbe*, int, int, int); void sqlite3VdbeScanStatusCounters(Vdbe*, int, int, int); #else # define sqlite3VdbeScanStatus(a,b,c,d,e,f) # define sqlite3VdbeScanStatusRange(a,b,c,d) # define sqlite3VdbeScanStatusCounters(a,b,c,d) #endif #if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) void sqlite3VdbePrintOp(FILE*, int, VdbeOp*); #endif #endif /* SQLITE_VDBE_H */ |
Changes to src/vdbeInt.h.
︙ | ︙ | |||
167 168 169 170 171 172 173 | ** set to NULL if the currently executing frame is the main program. */ typedef struct VdbeFrame VdbeFrame; struct VdbeFrame { Vdbe *v; /* VM this frame belongs to */ VdbeFrame *pParent; /* Parent of this frame, or NULL if parent is main */ Op *aOp; /* Program instructions for parent frame */ | < | 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | ** set to NULL if the currently executing frame is the main program. */ typedef struct VdbeFrame VdbeFrame; struct VdbeFrame { Vdbe *v; /* VM this frame belongs to */ VdbeFrame *pParent; /* Parent of this frame, or NULL if parent is main */ Op *aOp; /* Program instructions for parent frame */ Mem *aMem; /* Array of memory cells for parent frame */ VdbeCursor **apCsr; /* Array of Vdbe cursors for parent frame */ u8 *aOnce; /* Bitmask used by OP_Once */ void *token; /* Copy of SubProgram.token */ i64 lastRowid; /* Last insert rowid (sqlite3.lastRowid) */ AuxData *pAuxData; /* Linked list of auxdata allocations */ #if SQLITE_DEBUG |
︙ | ︙ | |||
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 | /* A bitfield type for use inside of structures. Always follow with :N where ** N is the number of bits. */ typedef unsigned bft; /* Bit Field Type */ /* The ScanStatus object holds a single value for the ** sqlite3_stmt_scanstatus() interface. */ typedef struct ScanStatus ScanStatus; struct ScanStatus { int addrExplain; /* OP_Explain for loop */ int addrLoop; /* Address of "loops" counter */ int addrVisit; /* Address of "rows visited" counter */ int iSelectID; /* The "Select-ID" for this loop */ LogEst nEst; /* Estimated output rows per loop */ char *zName; /* Name of table or index */ }; | > > > > > > > > > | 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 | /* A bitfield type for use inside of structures. Always follow with :N where ** N is the number of bits. */ typedef unsigned bft; /* Bit Field Type */ /* The ScanStatus object holds a single value for the ** sqlite3_stmt_scanstatus() interface. ** ** aAddrRange[]: ** This array is used by ScanStatus elements associated with EQP ** notes that make an SQLITE_SCANSTAT_NCYCLE value available. It is ** an array of up to 3 ranges of VM addresses for which the Vdbe.anCycle[] ** values should be summed to calculate the NCYCLE value. Each pair of ** integer addresses is a start and end address (both inclusive) for a range ** instructions. A start value of 0 indicates an empty range. */ typedef struct ScanStatus ScanStatus; struct ScanStatus { int addrExplain; /* OP_Explain for loop */ int aAddrRange[6]; int addrLoop; /* Address of "loops" counter */ int addrVisit; /* Address of "rows visited" counter */ int iSelectID; /* The "Select-ID" for this loop */ LogEst nEst; /* Estimated output rows per loop */ char *zName; /* Name of table or index */ }; |
︙ | ︙ | |||
442 443 444 445 446 447 448 | /* When allocating a new Vdbe object, all of the fields below should be ** initialized to zero or NULL */ Op *aOp; /* Space to hold the virtual machine's program */ int nOp; /* Number of instructions in the program */ int nOpAlloc; /* Slots allocated for aOp[] */ Mem *aColName; /* Column names to return */ | | | 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 | /* When allocating a new Vdbe object, all of the fields below should be ** initialized to zero or NULL */ Op *aOp; /* Space to hold the virtual machine's program */ int nOp; /* Number of instructions in the program */ int nOpAlloc; /* Slots allocated for aOp[] */ Mem *aColName; /* Column names to return */ Mem *pResultRow; /* Current output row */ char *zErrMsg; /* Error message written here */ VList *pVList; /* Name of variables */ #ifndef SQLITE_OMIT_TRACE i64 startTime; /* Time when query started - used for profiling */ #endif #ifdef SQLITE_DEBUG int rcApp; /* errcode set by sqlite3_result_error_code() */ |
︙ | ︙ | |||
479 480 481 482 483 484 485 | VdbeFrame *pFrame; /* Parent frame */ VdbeFrame *pDelFrame; /* List of frame objects to free on VM reset */ int nFrame; /* Number of frames in pFrame list */ u32 expmask; /* Binding to these vars invalidates VM */ SubProgram *pProgram; /* Linked list of all sub-programs used by VM */ AuxData *pAuxData; /* Linked list of auxdata allocations */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS | < | 487 488 489 490 491 492 493 494 495 496 497 498 499 500 | VdbeFrame *pFrame; /* Parent frame */ VdbeFrame *pDelFrame; /* List of frame objects to free on VM reset */ int nFrame; /* Number of frames in pFrame list */ u32 expmask; /* Binding to these vars invalidates VM */ SubProgram *pProgram; /* Linked list of all sub-programs used by VM */ AuxData *pAuxData; /* Linked list of auxdata allocations */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS int nScan; /* Entries in aScan[] */ ScanStatus *aScan; /* Scan definitions for sqlite3_stmt_scanstatus() */ #endif }; /* ** The following are allowed values for Vdbe.eVdbeState |
︙ | ︙ |
Changes to src/vdbeapi.c.
︙ | ︙ | |||
11 12 13 14 15 16 17 18 19 20 21 22 23 24 | ************************************************************************* ** ** This file contains code use to implement APIs that are part of the ** VDBE. */ #include "sqliteInt.h" #include "vdbeInt.h" #ifndef SQLITE_OMIT_DEPRECATED /* ** Return TRUE (non-zero) of the statement supplied as an argument needs ** to be recompiled. A statement needs to be recompiled whenever the ** execution environment changes in a way that would alter the program ** that sqlite3_prepare() generates. For example, if new functions or | > | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | ************************************************************************* ** ** This file contains code use to implement APIs that are part of the ** VDBE. */ #include "sqliteInt.h" #include "vdbeInt.h" #include "opcodes.h" #ifndef SQLITE_OMIT_DEPRECATED /* ** Return TRUE (non-zero) of the statement supplied as an argument needs ** to be recompiled. A statement needs to be recompiled whenever the ** execution environment changes in a way that would alter the program ** that sqlite3_prepare() generates. For example, if new functions or |
︙ | ︙ | |||
501 502 503 504 505 506 507 | const char *z, sqlite3_uint64 n, void (*xDel)(void *), unsigned char enc ){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); assert( xDel!=SQLITE_DYNAMIC ); | > | > > | | | | 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 | const char *z, sqlite3_uint64 n, void (*xDel)(void *), unsigned char enc ){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); assert( xDel!=SQLITE_DYNAMIC ); if( enc!=SQLITE_UTF8 ){ if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; n &= ~(u64)1; } if( n>0x7fffffff ){ (void)invokeValueDestructor(z, xDel, pCtx); }else{ setResultStrOrError(pCtx, z, (int)n, enc, xDel); } } #ifndef SQLITE_OMIT_UTF16 void sqlite3_result_text16( sqlite3_context *pCtx, const void *z, int n, void (*xDel)(void *) ){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); setResultStrOrError(pCtx, z, n & ~(u64)1, SQLITE_UTF16NATIVE, xDel); } void sqlite3_result_text16be( sqlite3_context *pCtx, const void *z, int n, void (*xDel)(void *) ){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); setResultStrOrError(pCtx, z, n & ~(u64)1, SQLITE_UTF16BE, xDel); } void sqlite3_result_text16le( sqlite3_context *pCtx, const void *z, int n, void (*xDel)(void *) ){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); setResultStrOrError(pCtx, z, n & ~(u64)1, SQLITE_UTF16LE, xDel); } #endif /* SQLITE_OMIT_UTF16 */ void sqlite3_result_value(sqlite3_context *pCtx, sqlite3_value *pValue){ Mem *pOut = pCtx->pOut; assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); sqlite3VdbeMemCopy(pOut, pValue); sqlite3VdbeChangeEncoding(pOut, pCtx->enc); |
︙ | ︙ | |||
745 746 747 748 749 750 751 | db->errCode = SQLITE_ROW; return SQLITE_ROW; }else{ #ifndef SQLITE_OMIT_TRACE /* If the statement completed successfully, invoke the profile callback */ checkProfileCallback(db, p); #endif | | | 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 | db->errCode = SQLITE_ROW; return SQLITE_ROW; }else{ #ifndef SQLITE_OMIT_TRACE /* If the statement completed successfully, invoke the profile callback */ checkProfileCallback(db, p); #endif p->pResultRow = 0; if( rc==SQLITE_DONE && db->autoCommit ){ assert( p->rc==SQLITE_OK ); p->rc = doWalCallbacks(db); if( p->rc!=SQLITE_OK ){ rc = SQLITE_ERROR; } }else if( rc!=SQLITE_DONE && (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 ){ |
︙ | ︙ | |||
1109 1110 1111 1112 1113 1114 1115 | /* ** Return the number of values available from the current row of the ** currently executing statement pStmt. */ int sqlite3_data_count(sqlite3_stmt *pStmt){ Vdbe *pVm = (Vdbe *)pStmt; | | | 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 | /* ** Return the number of values available from the current row of the ** currently executing statement pStmt. */ int sqlite3_data_count(sqlite3_stmt *pStmt){ Vdbe *pVm = (Vdbe *)pStmt; if( pVm==0 || pVm->pResultRow==0 ) return 0; return pVm->nResColumn; } /* ** Return a pointer to static memory containing an SQL NULL value. */ static const Mem *columnNullValue(void){ |
︙ | ︙ | |||
1164 1165 1166 1167 1168 1169 1170 | Vdbe *pVm; Mem *pOut; pVm = (Vdbe *)pStmt; if( pVm==0 ) return (Mem*)columnNullValue(); assert( pVm->db ); sqlite3_mutex_enter(pVm->db->mutex); | | | | 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 | Vdbe *pVm; Mem *pOut; pVm = (Vdbe *)pStmt; if( pVm==0 ) return (Mem*)columnNullValue(); assert( pVm->db ); sqlite3_mutex_enter(pVm->db->mutex); if( pVm->pResultRow!=0 && i<pVm->nResColumn && i>=0 ){ pOut = &pVm->pResultRow[i]; }else{ sqlite3Error(pVm->db, SQLITE_RANGE); pOut = (Mem*)columnNullValue(); } return pOut; } |
︙ | ︙ | |||
1599 1600 1601 1602 1603 1604 1605 | int i, const char *zData, sqlite3_uint64 nData, void (*xDel)(void*), unsigned char enc ){ assert( xDel!=SQLITE_DYNAMIC ); | > | > > | | | 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 | int i, const char *zData, sqlite3_uint64 nData, void (*xDel)(void*), unsigned char enc ){ assert( xDel!=SQLITE_DYNAMIC ); if( enc!=SQLITE_UTF8 ){ if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; nData &= ~(u16)1; } return bindText(pStmt, i, zData, nData, xDel, enc); } #ifndef SQLITE_OMIT_UTF16 int sqlite3_bind_text16( sqlite3_stmt *pStmt, int i, const void *zData, int n, void (*xDel)(void*) ){ return bindText(pStmt, i, zData, n & ~(u64)1, xDel, SQLITE_UTF16NATIVE); } #endif /* SQLITE_OMIT_UTF16 */ int sqlite3_bind_value(sqlite3_stmt *pStmt, int i, const sqlite3_value *pValue){ int rc; switch( sqlite3_value_type((sqlite3_value*)pValue) ){ case SQLITE_INTEGER: { rc = sqlite3_bind_int64(pStmt, i, pValue->u.i); |
︙ | ︙ | |||
2101 2102 2103 2104 2105 2106 2107 | } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS /* ** Return status data for a single loop within query pStmt. */ | | | > > > > > > > > > > > > > | > > > | > > > > > > > > > > > > > > > > | > > > > | > | 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 | } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS /* ** Return status data for a single loop within query pStmt. */ int sqlite3_stmt_scanstatus_v2( sqlite3_stmt *pStmt, /* Prepared statement being queried */ int iScan, /* Index of loop to report on */ int iScanStatusOp, /* Which metric to return */ int flags, void *pOut /* OUT: Write the answer here */ ){ Vdbe *p = (Vdbe*)pStmt; ScanStatus *pScan; int idx; if( iScan<0 ){ int ii; if( iScanStatusOp==SQLITE_SCANSTAT_NCYCLE ){ i64 res = 0; for(ii=0; ii<p->nOp; ii++){ res += p->aOp[ii].nCycle; } *(i64*)pOut = res; return 0; } return 1; } if( flags & SQLITE_SCANSTAT_COMPLEX ){ idx = iScan; pScan = &p->aScan[idx]; }else{ /* If the COMPLEX flag is clear, then this function must ignore any ** ScanStatus structures with ScanStatus.addrLoop set to 0. */ for(idx=0; idx<p->nScan; idx++){ pScan = &p->aScan[idx]; if( pScan->zName ){ iScan--; if( iScan<0 ) break; } } } if( idx>=p->nScan ) return 1; switch( iScanStatusOp ){ case SQLITE_SCANSTAT_NLOOP: { if( pScan->addrLoop>0 ){ *(sqlite3_int64*)pOut = p->aOp[pScan->addrLoop].nExec; }else{ *(sqlite3_int64*)pOut = -1; } break; } case SQLITE_SCANSTAT_NVISIT: { if( pScan->addrVisit>0 ){ *(sqlite3_int64*)pOut = p->aOp[pScan->addrVisit].nExec; }else{ *(sqlite3_int64*)pOut = -1; } break; } case SQLITE_SCANSTAT_EST: { double r = 1.0; LogEst x = pScan->nEst; while( x<100 ){ x += 10; |
︙ | ︙ | |||
2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 | case SQLITE_SCANSTAT_SELECTID: { if( pScan->addrExplain ){ *(int*)pOut = p->aOp[ pScan->addrExplain ].p1; }else{ *(int*)pOut = -1; } break; } default: { return 1; } } return 0; } /* ** Zero all counters associated with the sqlite3_stmt_scanstatus() data. */ void sqlite3_stmt_scanstatus_reset(sqlite3_stmt *pStmt){ Vdbe *p = (Vdbe*)pStmt; | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > | > | 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 | case SQLITE_SCANSTAT_SELECTID: { if( pScan->addrExplain ){ *(int*)pOut = p->aOp[ pScan->addrExplain ].p1; }else{ *(int*)pOut = -1; } break; } case SQLITE_SCANSTAT_PARENTID: { if( pScan->addrExplain ){ *(int*)pOut = p->aOp[ pScan->addrExplain ].p2; }else{ *(int*)pOut = -1; } break; } case SQLITE_SCANSTAT_NCYCLE: { i64 res = 0; if( pScan->aAddrRange[0]==0 ){ res = -1; }else{ int ii; for(ii=0; ii<ArraySize(pScan->aAddrRange); ii+=2){ int iIns = pScan->aAddrRange[ii]; int iEnd = pScan->aAddrRange[ii+1]; if( iIns==0 ) break; if( iIns>0 ){ while( iIns<=iEnd ){ res += p->aOp[iIns].nCycle; iIns++; } }else{ int iOp; for(iOp=0; iOp<p->nOp; iOp++){ Op *pOp = &p->aOp[iOp]; if( pOp->p1!=iEnd ) continue; if( (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_NCYCLE)==0 ){ continue; } res += p->aOp[iOp].nCycle; } } } } *(i64*)pOut = res; break; } default: { return 1; } } return 0; } /* ** Return status data for a single loop within query pStmt. */ int sqlite3_stmt_scanstatus( sqlite3_stmt *pStmt, /* Prepared statement being queried */ int iScan, /* Index of loop to report on */ int iScanStatusOp, /* Which metric to return */ void *pOut /* OUT: Write the answer here */ ){ return sqlite3_stmt_scanstatus_v2(pStmt, iScan, iScanStatusOp, 0, pOut); } /* ** Zero all counters associated with the sqlite3_stmt_scanstatus() data. */ void sqlite3_stmt_scanstatus_reset(sqlite3_stmt *pStmt){ Vdbe *p = (Vdbe*)pStmt; int ii; for(ii=0; ii<p->nOp; ii++){ Op *pOp = &p->aOp[ii]; pOp->nExec = 0; pOp->nCycle = 0; } } #endif /* SQLITE_ENABLE_STMT_SCANSTATUS */ |
Changes to src/vdbeaux.c.
︙ | ︙ | |||
208 209 210 211 212 213 214 215 216 217 218 219 220 221 | ** test_trace_breakpoint(pc,pOp) ** sqlite3CorruptError(lineno) ** sqlite3MisuseError(lineno) ** sqlite3CantopenError(lineno) */ static void test_addop_breakpoint(int pc, Op *pOp){ static int n = 0; n++; } #endif /* ** Add a new instruction to the list of instructions current in the ** VDBE. Return the address of the new instruction. | > > | 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | ** test_trace_breakpoint(pc,pOp) ** sqlite3CorruptError(lineno) ** sqlite3MisuseError(lineno) ** sqlite3CantopenError(lineno) */ static void test_addop_breakpoint(int pc, Op *pOp){ static int n = 0; (void)pc; (void)pOp; n++; } #endif /* ** Add a new instruction to the list of instructions current in the ** VDBE. Return the address of the new instruction. |
︙ | ︙ | |||
257 258 259 260 261 262 263 264 265 266 267 268 269 270 | pOp->p1 = p1; pOp->p2 = p2; pOp->p3 = p3; pOp->p4.p = 0; pOp->p4type = P4_NOTUSED; #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS pOp->zComment = 0; #endif #ifdef SQLITE_DEBUG if( p->db->flags & SQLITE_VdbeAddopTrace ){ sqlite3VdbePrintOp(0, i, &p->aOp[i]); test_addop_breakpoint(i, &p->aOp[i]); } #endif | > > > > < < < < | 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 | pOp->p1 = p1; pOp->p2 = p2; pOp->p3 = p3; pOp->p4.p = 0; pOp->p4type = P4_NOTUSED; #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS pOp->zComment = 0; #endif #if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) pOp->nExec = 0; pOp->nCycle = 0; #endif #ifdef SQLITE_DEBUG if( p->db->flags & SQLITE_VdbeAddopTrace ){ sqlite3VdbePrintOp(0, i, &p->aOp[i]); test_addop_breakpoint(i, &p->aOp[i]); } #endif #ifdef SQLITE_VDBE_COVERAGE pOp->iSrcLine = 0; #endif return i; } int sqlite3VdbeAddOp0(Vdbe *p, int op){ return sqlite3VdbeAddOp3(p, op, 0, 0, 0); |
︙ | ︙ | |||
435 436 437 438 439 440 441 | /* ** Add a new OP_Explain opcode. ** ** If the bPush flag is true, then make this opcode the parent for ** subsequent Explains until sqlite3VdbeExplainPop() is called. */ | | > | | > > | 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 | /* ** Add a new OP_Explain opcode. ** ** If the bPush flag is true, then make this opcode the parent for ** subsequent Explains until sqlite3VdbeExplainPop() is called. */ int sqlite3VdbeExplain(Parse *pParse, u8 bPush, const char *zFmt, ...){ int addr = 0; #if !defined(SQLITE_DEBUG) && !defined(SQLITE_ENABLE_STMT_SCANSTATUS) /* Always include the OP_Explain opcodes if SQLITE_DEBUG is defined. ** But omit them (for performance) during production builds */ if( pParse->explain==2 ) #endif { char *zMsg; Vdbe *v; va_list ap; int iThis; va_start(ap, zFmt); zMsg = sqlite3VMPrintf(pParse->db, zFmt, ap); va_end(ap); v = pParse->pVdbe; iThis = v->nOp; addr = sqlite3VdbeAddOp4(v, OP_Explain, iThis, pParse->addrExplain, 0, zMsg, P4_DYNAMIC); sqlite3ExplainBreakpoint(bPush?"PUSH":"", sqlite3VdbeGetLastOp(v)->p4.z); if( bPush){ pParse->addrExplain = iThis; } sqlite3VdbeScanStatus(v, iThis, 0, 0, 0, 0); } return addr; } /* ** Pop the EXPLAIN QUERY PLAN stack one level. */ void sqlite3VdbeExplainPop(Parse *pParse){ sqlite3ExplainBreakpoint("POP", 0); |
︙ | ︙ | |||
1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 | const char *zName /* Name of table or index being scanned */ ){ sqlite3_int64 nByte = (p->nScan+1) * sizeof(ScanStatus); ScanStatus *aNew; aNew = (ScanStatus*)sqlite3DbRealloc(p->db, p->aScan, nByte); if( aNew ){ ScanStatus *pNew = &aNew[p->nScan++]; pNew->addrExplain = addrExplain; pNew->addrLoop = addrLoop; pNew->addrVisit = addrVisit; pNew->nEst = nEst; pNew->zName = sqlite3DbStrDup(p->db, zName); p->aScan = aNew; } } #endif /* ** Change the value of the opcode, or P1, P2, P3, or P5 operands ** for a specific instruction. */ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 | const char *zName /* Name of table or index being scanned */ ){ sqlite3_int64 nByte = (p->nScan+1) * sizeof(ScanStatus); ScanStatus *aNew; aNew = (ScanStatus*)sqlite3DbRealloc(p->db, p->aScan, nByte); if( aNew ){ ScanStatus *pNew = &aNew[p->nScan++]; memset(pNew, 0, sizeof(ScanStatus)); pNew->addrExplain = addrExplain; pNew->addrLoop = addrLoop; pNew->addrVisit = addrVisit; pNew->nEst = nEst; pNew->zName = sqlite3DbStrDup(p->db, zName); p->aScan = aNew; } } /* ** Add the range of instructions from addrStart to addrEnd (inclusive) to ** the set of those corresponding to the sqlite3_stmt_scanstatus() counters ** associated with the OP_Explain instruction at addrExplain. The ** sum of the sqlite3Hwtime() values for each of these instructions ** will be returned for SQLITE_SCANSTAT_NCYCLE requests. */ void sqlite3VdbeScanStatusRange( Vdbe *p, int addrExplain, int addrStart, int addrEnd ){ ScanStatus *pScan = 0; int ii; for(ii=p->nScan-1; ii>=0; ii--){ pScan = &p->aScan[ii]; if( pScan->addrExplain==addrExplain ) break; pScan = 0; } if( pScan ){ if( addrEnd<0 ) addrEnd = sqlite3VdbeCurrentAddr(p)-1; for(ii=0; ii<ArraySize(pScan->aAddrRange); ii+=2){ if( pScan->aAddrRange[ii]==0 ){ pScan->aAddrRange[ii] = addrStart; pScan->aAddrRange[ii+1] = addrEnd; break; } } } } /* ** Set the addresses for the SQLITE_SCANSTAT_NLOOP and SQLITE_SCANSTAT_NROW ** counters for the query element associated with the OP_Explain at ** addrExplain. */ void sqlite3VdbeScanStatusCounters( Vdbe *p, int addrExplain, int addrLoop, int addrVisit ){ ScanStatus *pScan = 0; int ii; for(ii=p->nScan-1; ii>=0; ii--){ pScan = &p->aScan[ii]; if( pScan->addrExplain==addrExplain ) break; pScan = 0; } if( pScan ){ pScan->addrLoop = addrLoop; pScan->addrVisit = addrVisit; } } #endif /* ** Change the value of the opcode, or P1, P2, P3, or P5 operands ** for a specific instruction. */ |
︙ | ︙ | |||
2252 2253 2254 2255 2256 2257 2258 | assert( p->rc==SQLITE_OK || p->rc==SQLITE_BUSY || p->rc==SQLITE_NOMEM ); /* Even though this opcode does not use dynamic strings for ** the result, result columns may become dynamic if the user calls ** sqlite3_column_text16(), causing a translation to UTF-16 encoding. */ releaseMemArray(pMem, 8); | < | 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 | assert( p->rc==SQLITE_OK || p->rc==SQLITE_BUSY || p->rc==SQLITE_NOMEM ); /* Even though this opcode does not use dynamic strings for ** the result, result columns may become dynamic if the user calls ** sqlite3_column_text16(), causing a translation to UTF-16 encoding. */ releaseMemArray(pMem, 8); if( p->rc==SQLITE_NOMEM ){ /* This happens if a malloc() inside a call to sqlite3_column_text() or ** sqlite3_column_text16() failed. */ sqlite3OomFault(db); return SQLITE_ERROR; } |
︙ | ︙ | |||
2309 2310 2311 2312 2313 2314 2315 | } #else sqlite3VdbeMemSetNull(pMem+7); #endif sqlite3VdbeMemSetStr(pMem+5, zP4, -1, SQLITE_UTF8, sqlite3_free); p->nResColumn = 8; } | | | 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 | } #else sqlite3VdbeMemSetNull(pMem+7); #endif sqlite3VdbeMemSetStr(pMem+5, zP4, -1, SQLITE_UTF8, sqlite3_free); p->nResColumn = 8; } p->pResultRow = pMem; if( db->mallocFailed ){ p->rc = SQLITE_NOMEM; rc = SQLITE_ERROR; }else{ p->rc = SQLITE_OK; rc = SQLITE_ROW; } |
︙ | ︙ | |||
2420 2421 2422 2423 2424 2425 2426 | } /* ** Rewind the VDBE back to the beginning in preparation for ** running it. */ void sqlite3VdbeRewind(Vdbe *p){ | | | 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 | } /* ** Rewind the VDBE back to the beginning in preparation for ** running it. */ void sqlite3VdbeRewind(Vdbe *p){ #if defined(SQLITE_DEBUG) int i; #endif assert( p!=0 ); assert( p->eVdbeState==VDBE_INIT_STATE || p->eVdbeState==VDBE_READY_STATE || p->eVdbeState==VDBE_HALT_STATE ); |
︙ | ︙ | |||
2449 2450 2451 2452 2453 2454 2455 | p->nChange = 0; p->cacheCtr = 1; p->minWriteFileFormat = 255; p->iStatement = 0; p->nFkConstraint = 0; #ifdef VDBE_PROFILE for(i=0; i<p->nOp; i++){ | | | | 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 | p->nChange = 0; p->cacheCtr = 1; p->minWriteFileFormat = 255; p->iStatement = 0; p->nFkConstraint = 0; #ifdef VDBE_PROFILE for(i=0; i<p->nOp; i++){ p->aOp[i].nExec = 0; p->aOp[i].nCycle = 0; } #endif } /* ** Prepare a virtual machine for execution for the first time after ** creating the virtual machine. This involves things such |
︙ | ︙ | |||
2559 2560 2561 2562 2563 2564 2565 | ** reduce the amount of memory held by a prepared statement. */ x.nNeeded = 0; p->aMem = allocSpace(&x, 0, nMem*sizeof(Mem)); p->aVar = allocSpace(&x, 0, nVar*sizeof(Mem)); p->apArg = allocSpace(&x, 0, nArg*sizeof(Mem*)); p->apCsr = allocSpace(&x, 0, nCursor*sizeof(VdbeCursor*)); | < < < < < < < < < | 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 | ** reduce the amount of memory held by a prepared statement. */ x.nNeeded = 0; p->aMem = allocSpace(&x, 0, nMem*sizeof(Mem)); p->aVar = allocSpace(&x, 0, nVar*sizeof(Mem)); p->apArg = allocSpace(&x, 0, nArg*sizeof(Mem*)); p->apCsr = allocSpace(&x, 0, nCursor*sizeof(VdbeCursor*)); if( x.nNeeded ){ x.pSpace = p->pFree = sqlite3DbMallocRawNN(db, x.nNeeded); x.nFree = x.nNeeded; if( !db->mallocFailed ){ p->aMem = allocSpace(&x, p->aMem, nMem*sizeof(Mem)); p->aVar = allocSpace(&x, p->aVar, nVar*sizeof(Mem)); p->apArg = allocSpace(&x, p->apArg, nArg*sizeof(Mem*)); p->apCsr = allocSpace(&x, p->apCsr, nCursor*sizeof(VdbeCursor*)); } } if( db->mallocFailed ){ p->nVar = 0; p->nCursor = 0; p->nMem = 0; }else{ p->nCursor = nCursor; p->nVar = (ynVar)nVar; initMemArray(p->aVar, nVar, db, MEM_Null); p->nMem = nMem; initMemArray(p->aMem, nMem, db, MEM_Undefined); memset(p->apCsr, 0, nCursor*sizeof(VdbeCursor*)); } sqlite3VdbeRewind(p); } /* ** Close a VDBE cursor and release all the resources that cursor ** happens to hold. |
︙ | ︙ | |||
2647 2648 2649 2650 2651 2652 2653 | ** Copy the values stored in the VdbeFrame structure to its Vdbe. This ** is used, for example, when a trigger sub-program is halted to restore ** control to the main program. */ int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){ Vdbe *v = pFrame->v; closeCursorsInFrame(v); | < < < | 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 | ** Copy the values stored in the VdbeFrame structure to its Vdbe. This ** is used, for example, when a trigger sub-program is halted to restore ** control to the main program. */ int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){ Vdbe *v = pFrame->v; closeCursorsInFrame(v); v->aOp = pFrame->aOp; v->nOp = pFrame->nOp; v->aMem = pFrame->aMem; v->nMem = pFrame->nMem; v->apCsr = pFrame->apCsr; v->nCursor = pFrame->nCursor; v->db->lastRowid = pFrame->lastRowid; |
︙ | ︙ | |||
3453 3454 3455 3456 3457 3458 3459 | for(i=0; i<p->nMem; i++) assert( p->aMem[i].flags==MEM_Undefined ); } #endif if( p->zErrMsg ){ sqlite3DbFree(db, p->zErrMsg); p->zErrMsg = 0; } | | | 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 | for(i=0; i<p->nMem; i++) assert( p->aMem[i].flags==MEM_Undefined ); } #endif if( p->zErrMsg ){ sqlite3DbFree(db, p->zErrMsg); p->zErrMsg = 0; } p->pResultRow = 0; #ifdef SQLITE_DEBUG p->nWrite = 0; #endif /* Save profiling information from this VDBE run. */ #ifdef VDBE_PROFILE |
︙ | ︙ | |||
3481 3482 3483 3484 3485 3486 3487 3488 | putc(c, out); pc = c; } if( pc!='\n' ) fprintf(out, "\n"); } for(i=0; i<p->nOp; i++){ char zHdr[100]; sqlite3_snprintf(sizeof(zHdr), zHdr, "%6u %12llu %8llu ", | > > | | | | 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 | putc(c, out); pc = c; } if( pc!='\n' ) fprintf(out, "\n"); } for(i=0; i<p->nOp; i++){ char zHdr[100]; i64 cnt = p->aOp[i].nExec; i64 cycles = p->aOp[i].nCycle; sqlite3_snprintf(sizeof(zHdr), zHdr, "%6u %12llu %8llu ", cnt, cycles, cnt>0 ? cycles/cnt : 0 ); fprintf(out, "%s", zHdr); sqlite3VdbePrintOp(out, i, &p->aOp[i]); } fclose(out); } } |
︙ | ︙ |
Changes to src/vdbemem.c.
︙ | ︙ | |||
672 673 674 675 676 677 678 | testcase( pMem->flags & MEM_IntReal ); if( pMem->flags & (MEM_Int|MEM_IntReal) ) return pMem->u.i!=0; if( pMem->flags & MEM_Null ) return ifNull; return sqlite3VdbeRealValue(pMem)!=0.0; } /* | | | > | < < < | | | | | | | | | | | | | | < | 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 | testcase( pMem->flags & MEM_IntReal ); if( pMem->flags & (MEM_Int|MEM_IntReal) ) return pMem->u.i!=0; if( pMem->flags & MEM_Null ) return ifNull; return sqlite3VdbeRealValue(pMem)!=0.0; } /* ** The MEM structure is already a MEM_Real. Try to also make it a ** MEM_Int if we can. */ void sqlite3VdbeIntegerAffinity(Mem *pMem){ i64 ix; assert( pMem!=0 ); assert( pMem->flags & MEM_Real ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); ix = doubleToInt64(pMem->u.r); /* Only mark the value as an integer if ** ** (1) the round-trip conversion real->int->real is a no-op, and ** (2) The integer is neither the largest nor the smallest ** possible integer (ticket #3922) ** ** The second and third terms in the following conditional enforces ** the second condition under the assumption that addition overflow causes ** values to wrap around. */ if( pMem->u.r==ix && ix>SMALLEST_INT64 && ix<LARGEST_INT64 ){ pMem->u.i = ix; MemSetTypeFlag(pMem, MEM_Int); } } /* ** Convert pMem to type integer. Invalidate any prior representations. */ int sqlite3VdbeMemIntegerify(Mem *pMem){ |
︙ | ︙ |
Changes to src/vdbevtab.c.
︙ | ︙ | |||
79 80 81 82 83 84 85 86 87 88 89 90 91 92 | "name TEXT," "wr INT," "subprog TEXT," "stmt HIDDEN" ");" }; rc = sqlite3_declare_vtab(db, azSchema[isTabUsed]); if( rc==SQLITE_OK ){ pNew = sqlite3_malloc( sizeof(*pNew) ); *ppVtab = (sqlite3_vtab*)pNew; if( pNew==0 ) return SQLITE_NOMEM; memset(pNew, 0, sizeof(*pNew)); pNew->db = db; | > > > | 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | "name TEXT," "wr INT," "subprog TEXT," "stmt HIDDEN" ");" }; (void)argc; (void)argv; (void)pzErr; rc = sqlite3_declare_vtab(db, azSchema[isTabUsed]); if( rc==SQLITE_OK ){ pNew = sqlite3_malloc( sizeof(*pNew) ); *ppVtab = (sqlite3_vtab*)pNew; if( pNew==0 ) return SQLITE_NOMEM; memset(pNew, 0, sizeof(*pNew)); pNew->db = db; |
︙ | ︙ | |||
314 315 316 317 318 319 320 321 322 323 324 325 326 327 | sqlite3_vtab_cursor *pVtabCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ bytecodevtab_cursor *pCur = (bytecodevtab_cursor *)pVtabCursor; bytecodevtab *pVTab = (bytecodevtab *)pVtabCursor->pVtab; int rc = SQLITE_OK; bytecodevtabCursorClear(pCur); pCur->iRowid = 0; pCur->iAddr = 0; pCur->showSubprograms = idxNum==0; assert( argc==1 ); if( sqlite3_value_type(argv[0])==SQLITE_TEXT ){ | > | 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 | sqlite3_vtab_cursor *pVtabCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ bytecodevtab_cursor *pCur = (bytecodevtab_cursor *)pVtabCursor; bytecodevtab *pVTab = (bytecodevtab *)pVtabCursor->pVtab; int rc = SQLITE_OK; (void)idxStr; bytecodevtabCursorClear(pCur); pCur->iRowid = 0; pCur->iAddr = 0; pCur->showSubprograms = idxNum==0; assert( argc==1 ); if( sqlite3_value_type(argv[0])==SQLITE_TEXT ){ |
︙ | ︙ |
Changes to src/where.c.
︙ | ︙ | |||
701 702 703 704 705 706 707 | ** structure. Used for testing and debugging only. If neither ** SQLITE_TEST or SQLITE_DEBUG are defined, then these routines ** are no-ops. */ #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(WHERETRACE_ENABLED) static void whereTraceIndexInfoInputs(sqlite3_index_info *p){ int i; | | | | 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 | ** structure. Used for testing and debugging only. If neither ** SQLITE_TEST or SQLITE_DEBUG are defined, then these routines ** are no-ops. */ #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(WHERETRACE_ENABLED) static void whereTraceIndexInfoInputs(sqlite3_index_info *p){ int i; if( (sqlite3WhereTrace & 0x10)==0 ) return; for(i=0; i<p->nConstraint; i++){ sqlite3DebugPrintf( " constraint[%d]: col=%d termid=%d op=%d usabled=%d collseq=%s\n", i, p->aConstraint[i].iColumn, p->aConstraint[i].iTermOffset, p->aConstraint[i].op, p->aConstraint[i].usable, sqlite3_vtab_collation(p,i)); } for(i=0; i<p->nOrderBy; i++){ sqlite3DebugPrintf(" orderby[%d]: col=%d desc=%d\n", i, p->aOrderBy[i].iColumn, p->aOrderBy[i].desc); } } static void whereTraceIndexInfoOutputs(sqlite3_index_info *p){ int i; if( (sqlite3WhereTrace & 0x10)==0 ) return; for(i=0; i<p->nConstraint; i++){ sqlite3DebugPrintf(" usage[%d]: argvIdx=%d omit=%d\n", i, p->aConstraintUsage[i].argvIndex, p->aConstraintUsage[i].omit); } sqlite3DebugPrintf(" idxNum=%d\n", p->idxNum); |
︙ | ︙ | |||
808 809 810 811 812 813 814 815 816 817 818 819 820 821 | testcase( pTerm->pExpr->op==TK_IS ); return 1; } #endif #ifndef SQLITE_OMIT_AUTOMATIC_INDEX /* ** Generate code to construct the Index object for an automatic index ** and to set up the WhereLevel object pLevel so that the code generator ** makes use of the automatic index. */ static SQLITE_NOINLINE void constructAutomaticIndex( Parse *pParse, /* The parsing context */ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 | testcase( pTerm->pExpr->op==TK_IS ); return 1; } #endif #ifndef SQLITE_OMIT_AUTOMATIC_INDEX #ifdef SQLITE_ENABLE_STMT_SCANSTATUS /* ** Argument pIdx represents an automatic index that the current statement ** will create and populate. Add an OP_Explain with text of the form: ** ** CREATE AUTOMATIC INDEX ON <table>(<cols>) [WHERE <expr>] ** ** This is only required if sqlite3_stmt_scanstatus() is enabled, to ** associate an SQLITE_SCANSTAT_NCYCLE and SQLITE_SCANSTAT_NLOOP ** values with. In order to avoid breaking legacy code and test cases, ** the OP_Explain is not added if this is an EXPLAIN QUERY PLAN command. */ static void explainAutomaticIndex( Parse *pParse, Index *pIdx, /* Automatic index to explain */ int bPartial, /* True if pIdx is a partial index */ int *pAddrExplain /* OUT: Address of OP_Explain */ ){ if( pParse->explain!=2 ){ Table *pTab = pIdx->pTable; const char *zSep = ""; char *zText = 0; int ii = 0; sqlite3_str *pStr = sqlite3_str_new(pParse->db); sqlite3_str_appendf(pStr,"CREATE AUTOMATIC INDEX ON %s(", pTab->zName); assert( pIdx->nColumn>1 ); assert( pIdx->aiColumn[pIdx->nColumn-1]==XN_ROWID ); for(ii=0; ii<(pIdx->nColumn-1); ii++){ const char *zName = 0; int iCol = pIdx->aiColumn[ii]; zName = pTab->aCol[iCol].zCnName; sqlite3_str_appendf(pStr, "%s%s", zSep, zName); zSep = ", "; } zText = sqlite3_str_finish(pStr); if( zText==0 ){ sqlite3OomFault(pParse->db); }else{ *pAddrExplain = sqlite3VdbeExplain( pParse, 0, "%s)%s", zText, (bPartial ? " WHERE <expr>" : "") ); sqlite3_free(zText); } } } #else # define explainAutomaticIndex(a,b,c,d) #endif /* ** Generate code to construct the Index object for an automatic index ** and to set up the WhereLevel object pLevel so that the code generator ** makes use of the automatic index. */ static SQLITE_NOINLINE void constructAutomaticIndex( Parse *pParse, /* The parsing context */ |
︙ | ︙ | |||
843 844 845 846 847 848 849 850 851 852 853 854 855 856 | Bitmask extraCols; /* Bitmap of additional columns */ u8 sentWarning = 0; /* True if a warnning has been issued */ Expr *pPartial = 0; /* Partial Index Expression */ int iContinue = 0; /* Jump here to skip excluded rows */ SrcItem *pTabItem; /* FROM clause term being indexed */ int addrCounter = 0; /* Address where integer counter is initialized */ int regBase; /* Array of registers where record is assembled */ /* Generate code to skip over the creation and initialization of the ** transient index on 2nd and subsequent iterations of the loop. */ v = pParse->pVdbe; assert( v!=0 ); addrInit = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); | > > > | 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 | Bitmask extraCols; /* Bitmap of additional columns */ u8 sentWarning = 0; /* True if a warnning has been issued */ Expr *pPartial = 0; /* Partial Index Expression */ int iContinue = 0; /* Jump here to skip excluded rows */ SrcItem *pTabItem; /* FROM clause term being indexed */ int addrCounter = 0; /* Address where integer counter is initialized */ int regBase; /* Array of registers where record is assembled */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS int addrExp = 0; /* Address of OP_Explain */ #endif /* Generate code to skip over the creation and initialization of the ** transient index on 2nd and subsequent iterations of the loop. */ v = pParse->pVdbe; assert( v!=0 ); addrInit = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); |
︙ | ︙ | |||
966 967 968 969 970 971 972 973 974 975 976 977 978 979 | } } assert( n==nKeyCol ); pIdx->aiColumn[n] = XN_ROWID; pIdx->azColl[n] = sqlite3StrBINARY; /* Create the automatic index */ assert( pLevel->iIdxCur>=0 ); pLevel->iIdxCur = pParse->nTab++; sqlite3VdbeAddOp2(v, OP_OpenAutoindex, pLevel->iIdxCur, nKeyCol+1); sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "for %s", pTable->zName)); if( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) ){ pLevel->regFilter = ++pParse->nMem; | > | 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 | } } assert( n==nKeyCol ); pIdx->aiColumn[n] = XN_ROWID; pIdx->azColl[n] = sqlite3StrBINARY; /* Create the automatic index */ explainAutomaticIndex(pParse, pIdx, pPartial!=0, &addrExp); assert( pLevel->iIdxCur>=0 ); pLevel->iIdxCur = pParse->nTab++; sqlite3VdbeAddOp2(v, OP_OpenAutoindex, pLevel->iIdxCur, nKeyCol+1); sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "for %s", pTable->zName)); if( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) ){ pLevel->regFilter = ++pParse->nMem; |
︙ | ︙ | |||
1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 | regBase = sqlite3GenerateIndexKey( pParse, pIdx, pLevel->iTabCur, regRecord, 0, 0, 0, 0 ); if( pLevel->regFilter ){ sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pLevel->regFilter, 0, regBase, pLoop->u.btree.nEq); } sqlite3VdbeAddOp2(v, OP_IdxInsert, pLevel->iIdxCur, regRecord); sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); if( pPartial ) sqlite3VdbeResolveLabel(v, iContinue); if( pTabItem->fg.viaCoroutine ){ sqlite3VdbeChangeP2(v, addrCounter, regBase+n); testcase( pParse->db->mallocFailed ); assert( pLevel->iIdxCur>0 ); translateColumnToCopy(pParse, addrTop, pLevel->iTabCur, pTabItem->regResult, pLevel->iIdxCur); sqlite3VdbeGoto(v, addrTop); pTabItem->fg.viaCoroutine = 0; }else{ sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1); VdbeCoverage(v); sqlite3VdbeChangeP5(v, SQLITE_STMTSTATUS_AUTOINDEX); } sqlite3VdbeJumpHere(v, addrTop); sqlite3ReleaseTempReg(pParse, regRecord); /* Jump here when skipping the initialization */ sqlite3VdbeJumpHere(v, addrInit); end_auto_index_create: sqlite3ExprDelete(pParse->db, pPartial); } #endif /* SQLITE_OMIT_AUTOMATIC_INDEX */ /* | > > | 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 | regBase = sqlite3GenerateIndexKey( pParse, pIdx, pLevel->iTabCur, regRecord, 0, 0, 0, 0 ); if( pLevel->regFilter ){ sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pLevel->regFilter, 0, regBase, pLoop->u.btree.nEq); } sqlite3VdbeScanStatusCounters(v, addrExp, addrExp, sqlite3VdbeCurrentAddr(v)); sqlite3VdbeAddOp2(v, OP_IdxInsert, pLevel->iIdxCur, regRecord); sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); if( pPartial ) sqlite3VdbeResolveLabel(v, iContinue); if( pTabItem->fg.viaCoroutine ){ sqlite3VdbeChangeP2(v, addrCounter, regBase+n); testcase( pParse->db->mallocFailed ); assert( pLevel->iIdxCur>0 ); translateColumnToCopy(pParse, addrTop, pLevel->iTabCur, pTabItem->regResult, pLevel->iIdxCur); sqlite3VdbeGoto(v, addrTop); pTabItem->fg.viaCoroutine = 0; }else{ sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1); VdbeCoverage(v); sqlite3VdbeChangeP5(v, SQLITE_STMTSTATUS_AUTOINDEX); } sqlite3VdbeJumpHere(v, addrTop); sqlite3ReleaseTempReg(pParse, regRecord); /* Jump here when skipping the initialization */ sqlite3VdbeJumpHere(v, addrInit); sqlite3VdbeScanStatusRange(v, addrExp, addrExp, -1); end_auto_index_create: sqlite3ExprDelete(pParse->db, pPartial); } #endif /* SQLITE_OMIT_AUTOMATIC_INDEX */ /* |
︙ | ︙ | |||
1738 1739 1740 1741 1742 1743 1744 | ** method (assume that the scan visits 1/64 of the rows) for estimating ** the number of rows visited. Otherwise, estimate the number of rows ** using the method described in the header comment for this function. */ if( nDiff!=1 || pUpper==0 || pLower==0 ){ int nAdjust = (sqlite3LogEst(p->nSample) - sqlite3LogEst(nDiff)); pLoop->nOut -= nAdjust; *pbDone = 1; | | | 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 | ** method (assume that the scan visits 1/64 of the rows) for estimating ** the number of rows visited. Otherwise, estimate the number of rows ** using the method described in the header comment for this function. */ if( nDiff!=1 || pUpper==0 || pLower==0 ){ int nAdjust = (sqlite3LogEst(p->nSample) - sqlite3LogEst(nDiff)); pLoop->nOut -= nAdjust; *pbDone = 1; WHERETRACE(0x20, ("range skip-scan regions: %u..%u adjust=%d est=%d\n", nLower, nUpper, nAdjust*-1, pLoop->nOut)); } }else{ assert( *pbDone==0 ); } |
︙ | ︙ | |||
1916 1917 1918 1919 1920 1921 1922 | if( iLwrIdx==iUprIdx ) nNew -= 20; assert( 20==sqlite3LogEst(4) ); }else{ nNew = 10; assert( 10==sqlite3LogEst(2) ); } if( nNew<nOut ){ nOut = nNew; } | | | 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 | if( iLwrIdx==iUprIdx ) nNew -= 20; assert( 20==sqlite3LogEst(4) ); }else{ nNew = 10; assert( 10==sqlite3LogEst(2) ); } if( nNew<nOut ){ nOut = nNew; } WHERETRACE(0x20, ("STAT4 range scan: %u..%u est=%d\n", (u32)iLower, (u32)iUpper, nOut)); } }else{ int bDone = 0; rc = whereRangeSkipScanEst(pParse, pLower, pUpper, pLoop, &bDone); if( bDone ) return rc; } |
︙ | ︙ | |||
1949 1950 1951 1952 1953 1954 1955 | } nOut -= (pLower!=0) + (pUpper!=0); if( nNew<10 ) nNew = 10; if( nNew<nOut ) nOut = nNew; #if defined(WHERETRACE_ENABLED) if( pLoop->nOut>nOut ){ | | | 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 | } nOut -= (pLower!=0) + (pUpper!=0); if( nNew<10 ) nNew = 10; if( nNew<nOut ) nOut = nNew; #if defined(WHERETRACE_ENABLED) if( pLoop->nOut>nOut ){ WHERETRACE(0x20,("Range scan lowers nOut from %d to %d\n", pLoop->nOut, nOut)); } #endif pLoop->nOut = (LogEst)nOut; return rc; } |
︙ | ︙ | |||
2014 2015 2016 2017 2018 2019 2020 | rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, 1, nEq-1, &bOk); pBuilder->pRec = pRec; if( rc!=SQLITE_OK ) return rc; if( bOk==0 ) return SQLITE_NOTFOUND; pBuilder->nRecValid = nEq; whereKeyStats(pParse, p, pRec, 0, a); | | | 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 | rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, 1, nEq-1, &bOk); pBuilder->pRec = pRec; if( rc!=SQLITE_OK ) return rc; if( bOk==0 ) return SQLITE_NOTFOUND; pBuilder->nRecValid = nEq; whereKeyStats(pParse, p, pRec, 0, a); WHERETRACE(0x20,("equality scan regions %s(%d): %d\n", p->zName, nEq-1, (int)a[1])); *pnRow = a[1]; return rc; } #endif /* SQLITE_ENABLE_STAT4 */ |
︙ | ︙ | |||
2064 2065 2066 2067 2068 2069 2070 | nRowEst += nEst; pBuilder->nRecValid = nRecValid; } if( rc==SQLITE_OK ){ if( nRowEst > nRow0 ) nRowEst = nRow0; *pnRow = nRowEst; | | | 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 | nRowEst += nEst; pBuilder->nRecValid = nRecValid; } if( rc==SQLITE_OK ){ if( nRowEst > nRow0 ) nRowEst = nRow0; *pnRow = nRowEst; WHERETRACE(0x20,("IN row estimate: est=%d\n", nRowEst)); } assert( pBuilder->nRecValid==nRecValid ); return rc; } #endif /* SQLITE_ENABLE_STAT4 */ |
︙ | ︙ | |||
2173 2174 2175 2176 2177 2178 2179 | } if( p->wsFlags & WHERE_SKIPSCAN ){ sqlite3DebugPrintf(" f %06x %d-%d", p->wsFlags, p->nLTerm,p->nSkip); }else{ sqlite3DebugPrintf(" f %06x N %d", p->wsFlags, p->nLTerm); } sqlite3DebugPrintf(" cost %d,%d,%d\n", p->rSetup, p->rRun, p->nOut); | | | 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 | } if( p->wsFlags & WHERE_SKIPSCAN ){ sqlite3DebugPrintf(" f %06x %d-%d", p->wsFlags, p->nLTerm,p->nSkip); }else{ sqlite3DebugPrintf(" f %06x N %d", p->wsFlags, p->nLTerm); } sqlite3DebugPrintf(" cost %d,%d,%d\n", p->rSetup, p->rRun, p->nOut); if( p->nLTerm && (sqlite3WhereTrace & 0x4000)!=0 ){ int i; for(i=0; i<p->nLTerm; i++){ sqlite3WhereTermPrint(p->aLTerm[i], i); } } } #endif |
︙ | ︙ | |||
3051 3052 3053 3054 3055 3056 3057 | if( nEq==1 /* TUNING: Mark terms as "low selectivity" if they seem likely ** to be true for half or more of the rows in the table. ** See tag-202002240-1 */ && pNew->nOut+10 > pProbe->aiRowLogEst[0] ){ #if WHERETRACE_ENABLED /* 0x01 */ | | | 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 | if( nEq==1 /* TUNING: Mark terms as "low selectivity" if they seem likely ** to be true for half or more of the rows in the table. ** See tag-202002240-1 */ && pNew->nOut+10 > pProbe->aiRowLogEst[0] ){ #if WHERETRACE_ENABLED /* 0x01 */ if( sqlite3WhereTrace & 0x20 ){ sqlite3DebugPrintf( "STAT4 determines term has low selectivity:\n"); sqlite3WhereTermPrint(pTerm, 999); } #endif pTerm->wtFlags |= TERM_HIGHTRUTH; if( pTerm->wtFlags & TERM_HEURTRUTH ){ |
︙ | ︙ | |||
3088 3089 3090 3091 3092 3093 3094 | } /* Set rCostIdx to the cost of visiting selected rows in index. Add ** it to pNew->rRun, which is currently set to the cost of the index ** seek only. Then, if this is a non-covering index, add the cost of ** visiting the rows in the main table. */ assert( pSrc->pTab->szTabRow>0 ); | > > > > > > > | > | | 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 | } /* Set rCostIdx to the cost of visiting selected rows in index. Add ** it to pNew->rRun, which is currently set to the cost of the index ** seek only. Then, if this is a non-covering index, add the cost of ** visiting the rows in the main table. */ assert( pSrc->pTab->szTabRow>0 ); if( pProbe->idxType==SQLITE_IDXTYPE_IPK ){ /* The pProbe->szIdxRow is low for an IPK table since the interior ** pages are small. Thuse szIdxRow gives a good estimate of seek cost. ** But the leaf pages are full-size, so pProbe->szIdxRow would badly ** under-estimate the scanning cost. */ rCostIdx = pNew->nOut + 16; }else{ rCostIdx = pNew->nOut + 1 + (15*pProbe->szIdxRow)/pSrc->pTab->szTabRow; } pNew->rRun = sqlite3LogEstAdd(rLogSize, rCostIdx); if( (pNew->wsFlags & (WHERE_IDX_ONLY|WHERE_IPK|WHERE_EXPRIDX))==0 ){ pNew->rRun = sqlite3LogEstAdd(pNew->rRun, pNew->nOut + 16); } ApplyCostMultiplier(pNew->rRun, pProbe->pTable->costMult); nOutUnadjusted = pNew->nOut; pNew->rRun += nInMul + nIn; pNew->nOut += nInMul + nIn; |
︙ | ︙ | |||
3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 | && (pTerm->wtFlags & TERM_VNULL)==0 ){ return 1; } } return 0; } /* ** Structure passed to the whereIsCoveringIndex Walker callback. */ struct CoveringIndexCheck { Index *pIdx; /* The index */ int iTabCur; /* Cursor number for the corresponding table */ }; /* | > > > > > > > > > > > > > > > > > > > > > > > | > > > | | | | > | > > > | | | | | | | | | | > > > > > > | > > | < > > | > | > | > > > > | | | | | > | | | | | | | | | > > > < > > > > > > > | | 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 | && (pTerm->wtFlags & TERM_VNULL)==0 ){ return 1; } } return 0; } /* ** pIdx is an index containing expressions. Check it see if any of the ** expressions in the index match the pExpr expression. */ static int exprIsCoveredByIndex( const Expr *pExpr, const Index *pIdx, int iTabCur ){ int i; for(i=0; i<pIdx->nColumn; i++){ if( pIdx->aiColumn[i]==XN_EXPR && sqlite3ExprCompare(0, pExpr, pIdx->aColExpr->a[i].pExpr, iTabCur)==0 ){ return 1; } } return 0; } /* ** Structure passed to the whereIsCoveringIndex Walker callback. */ typedef struct CoveringIndexCheck CoveringIndexCheck; struct CoveringIndexCheck { Index *pIdx; /* The index */ int iTabCur; /* Cursor number for the corresponding table */ u8 bExpr; /* Uses an indexed expression */ u8 bUnidx; /* Uses an unindexed column not within an indexed expr */ }; /* ** Information passed in is pWalk->u.pCovIdxCk. Call it pCk. ** ** If the Expr node references the table with cursor pCk->iTabCur, then ** make sure that column is covered by the index pCk->pIdx. We know that ** all columns less than 63 (really BMS-1) are covered, so we don't need ** to check them. But we do need to check any column at 63 or greater. ** ** If the index does not cover the column, then set pWalk->eCode to ** non-zero and return WRC_Abort to stop the search. ** ** If this node does not disprove that the index can be a covering index, ** then just return WRC_Continue, to continue the search. ** ** If pCk->pIdx contains indexed expressions and one of those expressions ** matches pExpr, then prune the search. */ static int whereIsCoveringIndexWalkCallback(Walker *pWalk, Expr *pExpr){ int i; /* Loop counter */ const Index *pIdx; /* The index of interest */ const i16 *aiColumn; /* Columns contained in the index */ u16 nColumn; /* Number of columns in the index */ CoveringIndexCheck *pCk; /* Info about this search */ pCk = pWalk->u.pCovIdxCk; pIdx = pCk->pIdx; if( (pExpr->op==TK_COLUMN || pExpr->op==TK_AGG_COLUMN) ){ /* if( pExpr->iColumn<(BMS-1) && pIdx->bHasExpr==0 ) return WRC_Continue;*/ if( pExpr->iTable!=pCk->iTabCur ) return WRC_Continue; pIdx = pWalk->u.pCovIdxCk->pIdx; aiColumn = pIdx->aiColumn; nColumn = pIdx->nColumn; for(i=0; i<nColumn; i++){ if( aiColumn[i]==pExpr->iColumn ) return WRC_Continue; } pCk->bUnidx = 1; return WRC_Abort; }else if( pIdx->bHasExpr && exprIsCoveredByIndex(pExpr, pIdx, pWalk->u.pCovIdxCk->iTabCur) ){ pCk->bExpr = 1; return WRC_Prune; } return WRC_Continue; } /* ** pIdx is an index that covers all of the low-number columns used by ** pWInfo->pSelect (columns from 0 through 62) or an index that has ** expressions terms. Hence, we cannot determine whether or not it is ** a covering index by using the colUsed bitmasks. We have to do a search ** to see if the index is covering. This routine does that search. ** ** The return value is one of these: ** ** 0 The index is definitely not a covering index ** ** WHERE_IDX_ONLY The index is definitely a covering index ** ** WHERE_EXPRIDX The index is likely a covering index, but it is ** difficult to determine precisely because of the ** expressions that are indexed. Score it as a ** covering index, but still keep the main table open ** just in case we need it. ** ** This routine is an optimization. It is always safe to return zero. ** But returning one of the other two values when zero should have been ** returned can lead to incorrect bytecode and assertion faults. */ static SQLITE_NOINLINE u32 whereIsCoveringIndex( WhereInfo *pWInfo, /* The WHERE clause context */ Index *pIdx, /* Index that is being tested */ int iTabCur /* Cursor for the table being indexed */ ){ int i, rc; struct CoveringIndexCheck ck; Walker w; if( pWInfo->pSelect==0 ){ /* We don't have access to the full query, so we cannot check to see ** if pIdx is covering. Assume it is not. */ return 0; } if( pIdx->bHasExpr==0 ){ for(i=0; i<pIdx->nColumn; i++){ if( pIdx->aiColumn[i]>=BMS-1 ) break; } if( i>=pIdx->nColumn ){ /* pIdx does not index any columns greater than 62, but we know from ** colMask that columns greater than 62 are used, so this is not a ** covering index */ return 0; } } ck.pIdx = pIdx; ck.iTabCur = iTabCur; ck.bExpr = 0; ck.bUnidx = 0; memset(&w, 0, sizeof(w)); w.xExprCallback = whereIsCoveringIndexWalkCallback; w.xSelectCallback = sqlite3SelectWalkNoop; w.u.pCovIdxCk = &ck; sqlite3WalkSelect(&w, pWInfo->pSelect); if( ck.bUnidx ){ rc = 0; }else if( ck.bExpr ){ rc = WHERE_EXPRIDX; }else{ rc = WHERE_IDX_ONLY; } return rc; } /* ** Add all WhereLoop objects for a single table of the join where the table ** is identified by pBuilder->pNew->iTab. That table is guaranteed to be ** a b-tree table, not a virtual table. ** |
︙ | ︙ | |||
3413 3414 3415 3416 3417 3418 3419 | memset(&sPk, 0, sizeof(Index)); sPk.nKeyCol = 1; sPk.nColumn = 1; sPk.aiColumn = &aiColumnPk; sPk.aiRowLogEst = aiRowEstPk; sPk.onError = OE_Replace; sPk.pTable = pTab; | | | 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 | memset(&sPk, 0, sizeof(Index)); sPk.nKeyCol = 1; sPk.nColumn = 1; sPk.aiColumn = &aiColumnPk; sPk.aiRowLogEst = aiRowEstPk; sPk.onError = OE_Replace; sPk.pTable = pTab; sPk.szIdxRow = 3; /* TUNING: Interior rows of IPK table are very small */ sPk.idxType = SQLITE_IDXTYPE_IPK; aiRowEstPk[0] = pTab->nRowLogEst; aiRowEstPk[1] = 0; pFirst = pSrc->pTab->pIndex; if( pSrc->fg.notIndexed==0 ){ /* The real indices of the table are only considered if the ** NOT INDEXED qualifier is omitted from the FROM clause */ |
︙ | ︙ | |||
3544 3545 3546 3547 3548 3549 3550 | whereLoopOutputAdjust(pWC, pNew, rSize); rc = whereLoopInsert(pBuilder, pNew); pNew->nOut = rSize; if( rc ) break; }else{ Bitmask m; if( pProbe->isCovering ){ | < > > | | > > > > > > > > > > > > > > > > > | > > > > > | > | 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 | whereLoopOutputAdjust(pWC, pNew, rSize); rc = whereLoopInsert(pBuilder, pNew); pNew->nOut = rSize; if( rc ) break; }else{ Bitmask m; if( pProbe->isCovering ){ m = 0; pNew->wsFlags = WHERE_IDX_ONLY | WHERE_INDEXED; }else{ m = pSrc->colUsed & pProbe->colNotIdxed; pNew->wsFlags = WHERE_INDEXED; if( m==TOPBIT || (pProbe->bHasExpr && !pProbe->bHasVCol && m!=0) ){ u32 isCov = whereIsCoveringIndex(pWInfo, pProbe, pSrc->iCursor); if( isCov==0 ){ WHERETRACE(0x200, ("-> %s is not a covering index" " according to whereIsCoveringIndex()\n", pProbe->zName)); assert( m!=0 ); }else{ m = 0; pNew->wsFlags |= isCov; if( isCov & WHERE_IDX_ONLY ){ WHERETRACE(0x200, ("-> %s is a covering expression index" " according to whereIsCoveringIndex()\n", pProbe->zName)); }else{ assert( isCov==WHERE_EXPRIDX ); WHERETRACE(0x200, ("-> %s might be a covering expression index" " according to whereIsCoveringIndex()\n", pProbe->zName)); } } }else if( m==0 ){ WHERETRACE(0x200, ("-> %s a covering index according to bitmasks\n", pProbe->zName, m==0 ? "is" : "is not")); pNew->wsFlags = WHERE_IDX_ONLY | WHERE_INDEXED; } } /* Full scan via index */ if( b || !HasRowid(pTab) || pProbe->pPartIdxWhere!=0 || pSrc->fg.isIndexedBy |
︙ | ︙ | |||
3724 3725 3726 3727 3728 3729 3730 | rc = vtabBestIndex(pParse, pSrc->pTab, pIdxInfo); if( rc ){ if( rc==SQLITE_CONSTRAINT ){ /* If the xBestIndex method returns SQLITE_CONSTRAINT, that means ** that the particular combination of parameters provided is unusable. ** Make no entries in the loop table. */ | | | 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 | rc = vtabBestIndex(pParse, pSrc->pTab, pIdxInfo); if( rc ){ if( rc==SQLITE_CONSTRAINT ){ /* If the xBestIndex method returns SQLITE_CONSTRAINT, that means ** that the particular combination of parameters provided is unusable. ** Make no entries in the loop table. */ WHERETRACE(0xffffffff, (" ^^^^--- non-viable plan rejected!\n")); return SQLITE_OK; } return rc; } mxTerm = -1; assert( pNew->nLSlot>=nConstraint ); |
︙ | ︙ | |||
3835 3836 3837 3838 3839 3840 3841 | pNew->wsFlags &= ~WHERE_ONEROW; } rc = whereLoopInsert(pBuilder, pNew); if( pNew->u.vtab.needFree ){ sqlite3_free(pNew->u.vtab.idxStr); pNew->u.vtab.needFree = 0; } | | | 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 | pNew->wsFlags &= ~WHERE_ONEROW; } rc = whereLoopInsert(pBuilder, pNew); if( pNew->u.vtab.needFree ){ sqlite3_free(pNew->u.vtab.idxStr); pNew->u.vtab.needFree = 0; } WHERETRACE(0xffffffff, (" bIn=%d prereqIn=%04llx prereqOut=%04llx\n", *pbIn, (sqlite3_uint64)mPrereq, (sqlite3_uint64)(pNew->prereq & ~mPrereq))); return rc; } /* |
︙ | ︙ | |||
4027 4028 4029 4030 4031 4032 4033 | if( whereLoopResize(pParse->db, pNew, nConstraint) ){ freeIndexInfo(pParse->db, p); return SQLITE_NOMEM_BKPT; } /* First call xBestIndex() with all constraints usable. */ WHERETRACE(0x800, ("BEGIN %s.addVirtual()\n", pSrc->pTab->zName)); | | | 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 | if( whereLoopResize(pParse->db, pNew, nConstraint) ){ freeIndexInfo(pParse->db, p); return SQLITE_NOMEM_BKPT; } /* First call xBestIndex() with all constraints usable. */ WHERETRACE(0x800, ("BEGIN %s.addVirtual()\n", pSrc->pTab->zName)); WHERETRACE(0x800, (" VirtualOne: all usable\n")); rc = whereLoopAddVirtualOne( pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn, &bRetry ); if( bRetry ){ assert( rc==SQLITE_OK ); rc = whereLoopAddVirtualOne( pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn, 0 |
︙ | ︙ | |||
4052 4053 4054 4055 4056 4057 4058 | int seenZeroNoIN = 0; /* Plan with no prereqs and no IN(...) seen */ Bitmask mPrev = 0; Bitmask mBestNoIn = 0; /* If the plan produced by the earlier call uses an IN(...) term, call ** xBestIndex again, this time with IN(...) terms disabled. */ if( bIn ){ | | | 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 | int seenZeroNoIN = 0; /* Plan with no prereqs and no IN(...) seen */ Bitmask mPrev = 0; Bitmask mBestNoIn = 0; /* If the plan produced by the earlier call uses an IN(...) term, call ** xBestIndex again, this time with IN(...) terms disabled. */ if( bIn ){ WHERETRACE(0x800, (" VirtualOne: all usable w/o IN\n")); rc = whereLoopAddVirtualOne( pBuilder, mPrereq, ALLBITS, WO_IN, p, mNoOmit, &bIn, 0); assert( bIn==0 ); mBestNoIn = pNew->prereq & ~mPrereq; if( mBestNoIn==0 ){ seenZero = 1; seenZeroNoIN = 1; |
︙ | ︙ | |||
4078 4079 4080 4081 4082 4083 4084 | pWC->a[p->aConstraint[i].iTermOffset].prereqRight & ~mPrereq ); if( mThis>mPrev && mThis<mNext ) mNext = mThis; } mPrev = mNext; if( mNext==ALLBITS ) break; if( mNext==mBest || mNext==mBestNoIn ) continue; | | | | | 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 | pWC->a[p->aConstraint[i].iTermOffset].prereqRight & ~mPrereq ); if( mThis>mPrev && mThis<mNext ) mNext = mThis; } mPrev = mNext; if( mNext==ALLBITS ) break; if( mNext==mBest || mNext==mBestNoIn ) continue; WHERETRACE(0x800, (" VirtualOne: mPrev=%04llx mNext=%04llx\n", (sqlite3_uint64)mPrev, (sqlite3_uint64)mNext)); rc = whereLoopAddVirtualOne( pBuilder, mPrereq, mNext|mPrereq, 0, p, mNoOmit, &bIn, 0); if( pNew->prereq==mPrereq ){ seenZero = 1; if( bIn==0 ) seenZeroNoIN = 1; } } /* If the calls to xBestIndex() in the above loop did not find a plan ** that requires no source tables at all (i.e. one guaranteed to be ** usable), make a call here with all source tables disabled */ if( rc==SQLITE_OK && seenZero==0 ){ WHERETRACE(0x800, (" VirtualOne: all disabled\n")); rc = whereLoopAddVirtualOne( pBuilder, mPrereq, mPrereq, 0, p, mNoOmit, &bIn, 0); if( bIn==0 ) seenZeroNoIN = 1; } /* If the calls to xBestIndex() have so far failed to find a plan ** that requires no source tables at all and does not use an IN(...) ** operator, make a final call to obtain one here. */ if( rc==SQLITE_OK && seenZeroNoIN==0 ){ WHERETRACE(0x800, (" VirtualOne: all disabled and w/o IN\n")); rc = whereLoopAddVirtualOne( pBuilder, mPrereq, mPrereq, WO_IN, p, mNoOmit, &bIn, 0); } } if( p->needToFreeIdxStr ) sqlite3_free(p->idxStr); freeIndexInfo(pParse->db, p); |
︙ | ︙ | |||
4158 4159 4160 4161 4162 4163 4164 | WhereTerm *pOrTerm; int once = 1; int i, j; sSubBuild = *pBuilder; sSubBuild.pOrSet = &sCur; | | | | | 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 | WhereTerm *pOrTerm; int once = 1; int i, j; sSubBuild = *pBuilder; sSubBuild.pOrSet = &sCur; WHERETRACE(0x400, ("Begin processing OR-clause %p\n", pTerm)); for(pOrTerm=pOrWC->a; pOrTerm<pOrWCEnd; pOrTerm++){ if( (pOrTerm->eOperator & WO_AND)!=0 ){ sSubBuild.pWC = &pOrTerm->u.pAndInfo->wc; }else if( pOrTerm->leftCursor==iCur ){ tempWC.pWInfo = pWC->pWInfo; tempWC.pOuter = pWC; tempWC.op = TK_AND; tempWC.nTerm = 1; tempWC.nBase = 1; tempWC.a = pOrTerm; sSubBuild.pWC = &tempWC; }else{ continue; } sCur.n = 0; #ifdef WHERETRACE_ENABLED WHERETRACE(0x400, ("OR-term %d of %p has %d subterms:\n", (int)(pOrTerm-pOrWC->a), pTerm, sSubBuild.pWC->nTerm)); if( sqlite3WhereTrace & 0x20000 ){ sqlite3WhereClausePrint(sSubBuild.pWC); } #endif #ifndef SQLITE_OMIT_VIRTUALTABLE if( IsVirtual(pItem->pTab) ){ rc = whereLoopAddVirtual(&sSubBuild, mPrereq, mUnusable); }else |
︙ | ︙ | |||
4239 4240 4241 4242 4243 4244 4245 | ** the planner may elect to "OR" together a full-table scan and an ** index lookup. And other similarly odd results. */ pNew->rRun = sSum.a[i].rRun + 1; pNew->nOut = sSum.a[i].nOut; pNew->prereq = sSum.a[i].prereq; rc = whereLoopInsert(pBuilder, pNew); } | | | 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 | ** the planner may elect to "OR" together a full-table scan and an ** index lookup. And other similarly odd results. */ pNew->rRun = sSum.a[i].rRun + 1; pNew->nOut = sSum.a[i].nOut; pNew->prereq = sSum.a[i].prereq; rc = whereLoopInsert(pBuilder, pNew); } WHERETRACE(0x400, ("End processing OR-clause %p\n", pTerm)); } } return rc; } /* ** Add all WhereLoop objects for all tables |
︙ | ︙ | |||
4587 4588 4589 4590 4591 4592 4593 | if( NEVER(pOBExpr==0) ) continue; if( (wctrlFlags & (WHERE_GROUPBY|WHERE_DISTINCTBY))==0 ) bOnce = 0; if( iColumn>=XN_ROWID ){ if( pOBExpr->op!=TK_COLUMN && pOBExpr->op!=TK_AGG_COLUMN ) continue; if( pOBExpr->iTable!=iCur ) continue; if( pOBExpr->iColumn!=iColumn ) continue; }else{ | | | | 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 | if( NEVER(pOBExpr==0) ) continue; if( (wctrlFlags & (WHERE_GROUPBY|WHERE_DISTINCTBY))==0 ) bOnce = 0; if( iColumn>=XN_ROWID ){ if( pOBExpr->op!=TK_COLUMN && pOBExpr->op!=TK_AGG_COLUMN ) continue; if( pOBExpr->iTable!=iCur ) continue; if( pOBExpr->iColumn!=iColumn ) continue; }else{ Expr *pIxExpr = pIndex->aColExpr->a[j].pExpr; if( sqlite3ExprCompareSkip(pOBExpr, pIxExpr, iCur) ){ continue; } } if( iColumn!=XN_ROWID ){ pColl = sqlite3ExprNNCollSeq(pWInfo->pParse, pOrderBy->a[i].pExpr); if( sqlite3StrICmp(pColl->zName, pIndex->azColl[j])!=0 ) continue; } |
︙ | ︙ | |||
4720 4721 4722 4723 4724 4725 4726 | /* ** Return the cost of sorting nRow rows, assuming that the keys have ** nOrderby columns and that the first nSorted columns are already in ** order. */ static LogEst whereSortingCost( | | | | | | | | | > > > > > | > > | | > > | | > > > > | > > > > > | > | 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 | /* ** Return the cost of sorting nRow rows, assuming that the keys have ** nOrderby columns and that the first nSorted columns are already in ** order. */ static LogEst whereSortingCost( WhereInfo *pWInfo, /* Query planning context */ LogEst nRow, /* Estimated number of rows to sort */ int nOrderBy, /* Number of ORDER BY clause terms */ int nSorted /* Number of initial ORDER BY terms naturally in order */ ){ /* Estimated cost of a full external sort, where N is ** the number of rows to sort is: ** ** cost = (K * N * log(N)). ** ** Or, if the order-by clause has X terms but only the last Y ** terms are out of order, then block-sorting will reduce the ** sorting cost to: ** ** cost = (K * N * log(N)) * (Y/X) ** ** The constant K is at least 2.0 but will be larger if there are a ** large number of columns to be sorted, as the sorting time is ** proportional to the amount of content to be sorted. The algorithm ** does not currently distinguish between fat columns (BLOBs and TEXTs) ** and skinny columns (INTs). It just uses the number of columns as ** an approximation for the row width. ** ** And extra factor of 2.0 or 3.0 is added to the sorting cost if the sort ** is built using OP_IdxInsert and OP_Sort rather than with OP_SorterInsert. */ LogEst rSortCost, nCol; assert( pWInfo->pSelect!=0 ); assert( pWInfo->pSelect->pEList!=0 ); /* TUNING: sorting cost proportional to the number of output columns: */ nCol = sqlite3LogEst((pWInfo->pSelect->pEList->nExpr+59)/30); rSortCost = nRow + nCol; if( nSorted>0 ){ /* Scale the result by (Y/X) */ rSortCost += sqlite3LogEst((nOrderBy-nSorted)*100/nOrderBy) - 66; } /* Multiple by log(M) where M is the number of output rows. ** Use the LIMIT for M if it is smaller. Or if this sort is for ** a DISTINCT operator, M will be the number of distinct output ** rows, so fudge it downwards a bit. */ if( (pWInfo->wctrlFlags & WHERE_USE_LIMIT)!=0 ){ rSortCost += 10; /* TUNING: Extra 2.0x if using LIMIT */ if( nSorted!=0 ){ rSortCost += 6; /* TUNING: Extra 1.5x if also using partial sort */ } if( pWInfo->iLimit<nRow ){ nRow = pWInfo->iLimit; } }else if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT) ){ /* TUNING: In the sort for a DISTINCT operator, assume that the DISTINCT ** reduces the number of output rows by a factor of 2 */ if( nRow>10 ){ nRow -= 10; assert( 10==sqlite3LogEst(2) ); } } rSortCost += estLog(nRow); return rSortCost; |
︙ | ︙ | |||
4902 4903 4904 4905 4906 4907 4908 | } if( isOrdered>=0 && isOrdered<nOrderBy ){ if( aSortCost[isOrdered]==0 ){ aSortCost[isOrdered] = whereSortingCost( pWInfo, nRowEst, nOrderBy, isOrdered ); } | | | | 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 | } if( isOrdered>=0 && isOrdered<nOrderBy ){ if( aSortCost[isOrdered]==0 ){ aSortCost[isOrdered] = whereSortingCost( pWInfo, nRowEst, nOrderBy, isOrdered ); } /* TUNING: Add a small extra penalty (3) to sorting as an ** extra encouragment to the query planner to select a plan ** where the rows emerge in the correct order without any sorting ** required. */ rCost = sqlite3LogEstAdd(rUnsorted, aSortCost[isOrdered]) + 3; WHERETRACE(0x002, ("---- sort cost=%-3d (%d/%d) increases cost %3d to %-3d\n", aSortCost[isOrdered], (nOrderBy-isOrdered), nOrderBy, rUnsorted, rCost)); }else{ rCost = rUnsorted; |
︙ | ︙ | |||
5254 5255 5256 5257 5258 5259 5260 | pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; } if( scan.iEquiv>1 ) pLoop->wsFlags |= WHERE_TRANSCONS; #ifdef SQLITE_DEBUG pLoop->cId = '0'; #endif #ifdef WHERETRACE_ENABLED | | | 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 | pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; } if( scan.iEquiv>1 ) pLoop->wsFlags |= WHERE_TRANSCONS; #ifdef SQLITE_DEBUG pLoop->cId = '0'; #endif #ifdef WHERETRACE_ENABLED if( sqlite3WhereTrace & 0x02 ){ sqlite3DebugPrintf("whereShortCut() used to compute solution\n"); } #endif return 1; } return 0; } |
︙ | ︙ | |||
5384 5385 5386 5387 5388 5389 5390 | || pTerm->pExpr->w.iJoin!=pItem->iCursor ){ break; } } } if( pTerm<pEnd ) continue; | | | 5547 5548 5549 5550 5551 5552 5553 5554 5555 5556 5557 5558 5559 5560 5561 | || pTerm->pExpr->w.iJoin!=pItem->iCursor ){ break; } } } if( pTerm<pEnd ) continue; WHERETRACE(0xffffffff, ("-> drop loop %c not used\n", pLoop->cId)); notReady &= ~pLoop->maskSelf; for(pTerm=pWInfo->sWC.a; pTerm<pEnd; pTerm++){ if( (pTerm->prereqAll & pLoop->maskSelf)!=0 ){ pTerm->wtFlags |= TERM_CODED; } } if( i!=pWInfo->nLevel-1 ){ |
︙ | ︙ | |||
5444 5445 5446 5447 5448 5449 5450 | pTab->tabFlags |= TF_StatsUsed; if( nSearch > pTab->nRowLogEst && (pTab->tabFlags & TF_HasStat1)!=0 ){ testcase( pItem->fg.jointype & JT_LEFT ); pLoop->wsFlags |= WHERE_BLOOMFILTER; pLoop->wsFlags &= ~WHERE_IDX_ONLY; | | | | | | | | | 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 5617 5618 5619 5620 5621 5622 5623 5624 5625 5626 5627 5628 5629 5630 5631 5632 5633 5634 5635 5636 5637 5638 5639 5640 5641 5642 5643 5644 5645 5646 5647 5648 5649 5650 5651 5652 5653 5654 5655 5656 5657 5658 | pTab->tabFlags |= TF_StatsUsed; if( nSearch > pTab->nRowLogEst && (pTab->tabFlags & TF_HasStat1)!=0 ){ testcase( pItem->fg.jointype & JT_LEFT ); pLoop->wsFlags |= WHERE_BLOOMFILTER; pLoop->wsFlags &= ~WHERE_IDX_ONLY; WHERETRACE(0xffffffff, ( "-> use Bloom-filter on loop %c because there are ~%.1e " "lookups into %s which has only ~%.1e rows\n", pLoop->cId, (double)sqlite3LogEstToInt(nSearch), pTab->zName, (double)sqlite3LogEstToInt(pTab->nRowLogEst))); } } nSearch += pLoop->nOut; } } /* ** This is an sqlite3ParserAddCleanup() callback that is invoked to ** free the Parse->pIdxEpr list when the Parse object is destroyed. */ static void whereIndexedExprCleanup(sqlite3 *db, void *pObject){ Parse *pParse = (Parse*)pObject; while( pParse->pIdxEpr!=0 ){ IndexedExpr *p = pParse->pIdxEpr; pParse->pIdxEpr = p->pIENext; sqlite3ExprDelete(db, p->pExpr); sqlite3DbFreeNN(db, p); } } /* ** The index pIdx is used by a query and contains one or more expressions. ** In other words pIdx is an index on an expression. iIdxCur is the cursor ** number for the index and iDataCur is the cursor number for the corresponding ** table. ** ** This routine adds IndexedExpr entries to the Parse->pIdxEpr field for ** each of the expressions in the index so that the expression code generator ** will know to replace occurrences of the indexed expression with ** references to the corresponding column of the index. */ static SQLITE_NOINLINE void whereAddIndexedExpr( Parse *pParse, /* Add IndexedExpr entries to pParse->pIdxEpr */ Index *pIdx, /* The index-on-expression that contains the expressions */ int iIdxCur, /* Cursor number for pIdx */ SrcItem *pTabItem /* The FROM clause entry for the table */ ){ int i; IndexedExpr *p; Table *pTab; |
︙ | ︙ | |||
5510 5511 5512 5513 5514 5515 5516 | bMaybeNullRow = 0; }else{ continue; } if( sqlite3ExprIsConstant(pExpr) ) continue; p = sqlite3DbMallocRaw(pParse->db, sizeof(IndexedExpr)); if( p==0 ) break; | | > > > > > > | | 5673 5674 5675 5676 5677 5678 5679 5680 5681 5682 5683 5684 5685 5686 5687 5688 5689 5690 5691 5692 5693 5694 5695 5696 5697 5698 5699 5700 5701 5702 | bMaybeNullRow = 0; }else{ continue; } if( sqlite3ExprIsConstant(pExpr) ) continue; p = sqlite3DbMallocRaw(pParse->db, sizeof(IndexedExpr)); if( p==0 ) break; p->pIENext = pParse->pIdxEpr; #ifdef WHERETRACE_ENABLED if( sqlite3WhereTrace & 0x200 ){ sqlite3DebugPrintf("New pParse->pIdxEpr term {%d,%d}\n", iIdxCur, i); if( sqlite3WhereTrace & 0x5000 ) sqlite3ShowExpr(pExpr); } #endif p->pExpr = sqlite3ExprDup(pParse->db, pExpr, 0); p->iDataCur = pTabItem->iCursor; p->iIdxCur = iIdxCur; p->iIdxCol = i; p->bMaybeNullRow = bMaybeNullRow; #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS p->zIdxName = pIdx->zName; #endif pParse->pIdxEpr = p; if( p->pIENext==0 ){ sqlite3ParserAddCleanup(pParse, whereIndexedExprCleanup, pParse); } } } /* |
︙ | ︙ | |||
5811 5812 5813 5814 5815 5816 5817 | pWInfo->wctrlFlags |= WHERE_DISTINCTBY; pWInfo->pOrderBy = pResultSet; } } /* Construct the WhereLoop objects */ #if defined(WHERETRACE_ENABLED) | | | < | | | > | | 5980 5981 5982 5983 5984 5985 5986 5987 5988 5989 5990 5991 5992 5993 5994 5995 5996 5997 5998 5999 6000 6001 6002 6003 6004 6005 6006 6007 6008 6009 6010 6011 6012 6013 6014 6015 6016 6017 6018 6019 6020 6021 6022 6023 6024 6025 6026 6027 6028 6029 | pWInfo->wctrlFlags |= WHERE_DISTINCTBY; pWInfo->pOrderBy = pResultSet; } } /* Construct the WhereLoop objects */ #if defined(WHERETRACE_ENABLED) if( sqlite3WhereTrace & 0xffffffff ){ sqlite3DebugPrintf("*** Optimizer Start *** (wctrlFlags: 0x%x",wctrlFlags); if( wctrlFlags & WHERE_USE_LIMIT ){ sqlite3DebugPrintf(", limit: %d", iAuxArg); } sqlite3DebugPrintf(")\n"); if( sqlite3WhereTrace & 0x8000 ){ Select sSelect; memset(&sSelect, 0, sizeof(sSelect)); sSelect.selFlags = SF_WhereBegin; sSelect.pSrc = pTabList; sSelect.pWhere = pWhere; sSelect.pOrderBy = pOrderBy; sSelect.pEList = pResultSet; sqlite3TreeViewSelect(0, &sSelect, 0); } if( sqlite3WhereTrace & 0x4000 ){ /* Display all WHERE clause terms */ sqlite3DebugPrintf("---- WHERE clause at start of analysis:\n"); sqlite3WhereClausePrint(sWLB.pWC); } } #endif if( nTabList!=1 || whereShortCut(&sWLB)==0 ){ rc = whereLoopAddAll(&sWLB); if( rc ) goto whereBeginError; #ifdef SQLITE_ENABLE_STAT4 /* If one or more WhereTerm.truthProb values were used in estimating ** loop parameters, but then those truthProb values were subsequently ** changed based on STAT4 information while computing subsequent loops, ** then we need to rerun the whole loop building process so that all ** loops will be built using the revised truthProb values. */ if( sWLB.bldFlags2 & SQLITE_BLDF2_2NDPASS ){ WHERETRACE_ALL_LOOPS(pWInfo, sWLB.pWC); WHERETRACE(0xffffffff, ("**** Redo all loop computations due to" " TERM_HIGHTRUTH changes ****\n")); while( pWInfo->pLoops ){ WhereLoop *p = pWInfo->pLoops; pWInfo->pLoops = p->pNextLoop; whereLoopDelete(db, p); } |
︙ | ︙ | |||
5932 5933 5934 5935 5936 5937 5938 | if( pWInfo->nLevel>=2 && OptimizationEnabled(db, SQLITE_BloomFilter) ){ whereCheckIfBloomFilterIsUseful(pWInfo); } #if defined(WHERETRACE_ENABLED) | | | | 6101 6102 6103 6104 6105 6106 6107 6108 6109 6110 6111 6112 6113 6114 6115 6116 6117 6118 6119 | if( pWInfo->nLevel>=2 && OptimizationEnabled(db, SQLITE_BloomFilter) ){ whereCheckIfBloomFilterIsUseful(pWInfo); } #if defined(WHERETRACE_ENABLED) if( sqlite3WhereTrace & 0x4000 ){ /* Display all terms of the WHERE clause */ sqlite3DebugPrintf("---- WHERE clause at end of analysis:\n"); sqlite3WhereClausePrint(sWLB.pWC); } WHERETRACE(0xffffffff,("*** Optimizer Finished ***\n")); #endif pWInfo->pParse->nQueryLoop += pWInfo->nRowOut; /* If the caller is an UPDATE or DELETE statement that is requesting ** to use a one-pass algorithm, determine if this is appropriate. ** ** A one-pass approach can be used if the caller has requested one |
︙ | ︙ | |||
6470 6471 6472 6473 6474 6475 6476 | ){ if( pWInfo->eOnePass==ONEPASS_OFF || !HasRowid(pIdx->pTable) ){ last = iEnd; }else{ last = pWInfo->iEndWhere; } if( pIdx->bHasExpr ){ | | > > > > > > > | 6639 6640 6641 6642 6643 6644 6645 6646 6647 6648 6649 6650 6651 6652 6653 6654 6655 6656 6657 6658 6659 6660 6661 6662 | ){ if( pWInfo->eOnePass==ONEPASS_OFF || !HasRowid(pIdx->pTable) ){ last = iEnd; }else{ last = pWInfo->iEndWhere; } if( pIdx->bHasExpr ){ IndexedExpr *p = pParse->pIdxEpr; while( p ){ if( p->iIdxCur==pLevel->iIdxCur ){ #ifdef WHERETRACE_ENABLED if( sqlite3WhereTrace & 0x200 ){ sqlite3DebugPrintf("Disable pParse->pIdxEpr term {%d,%d}\n", p->iIdxCur, p->iIdxCol); if( sqlite3WhereTrace & 0x5000 ) sqlite3ShowExpr(p->pExpr); } #endif p->iDataCur = -1; p->iIdxCur = -1; } p = p->pIENext; } } k = pLevel->addrBody + 1; |
︙ | ︙ |
Changes to src/whereInt.h.
︙ | ︙ | |||
630 631 632 633 634 635 636 637 638 | #define WHERE_BIGNULL_SORT 0x00080000 /* Column nEq of index is BIGNULL */ #define WHERE_IN_SEEKSCAN 0x00100000 /* Seek-scan optimization for IN */ #define WHERE_TRANSCONS 0x00200000 /* Uses a transitive constraint */ #define WHERE_BLOOMFILTER 0x00400000 /* Consider using a Bloom-filter */ #define WHERE_SELFCULL 0x00800000 /* nOut reduced by extra WHERE terms */ #define WHERE_OMIT_OFFSET 0x01000000 /* Set offset counter to zero */ #define WHERE_VIEWSCAN 0x02000000 /* A full-scan of a VIEW or subquery */ #endif /* !defined(SQLITE_WHEREINT_H) */ | > | 630 631 632 633 634 635 636 637 638 639 | #define WHERE_BIGNULL_SORT 0x00080000 /* Column nEq of index is BIGNULL */ #define WHERE_IN_SEEKSCAN 0x00100000 /* Seek-scan optimization for IN */ #define WHERE_TRANSCONS 0x00200000 /* Uses a transitive constraint */ #define WHERE_BLOOMFILTER 0x00400000 /* Consider using a Bloom-filter */ #define WHERE_SELFCULL 0x00800000 /* nOut reduced by extra WHERE terms */ #define WHERE_OMIT_OFFSET 0x01000000 /* Set offset counter to zero */ #define WHERE_VIEWSCAN 0x02000000 /* A full-scan of a VIEW or subquery */ #define WHERE_EXPRIDX 0x04000000 /* Uses an index-on-expressions */ #endif /* !defined(SQLITE_WHEREINT_H) */ |
Changes to src/wherecode.c.
︙ | ︙ | |||
266 267 268 269 270 271 272 273 274 275 276 277 278 279 | sqlite3_str_appendf(&str, "%s=?", z); } } sqlite3_str_append(&str, ")", 1); zMsg = sqlite3StrAccumFinish(&str); ret = sqlite3VdbeAddOp4(v, OP_Explain, sqlite3VdbeCurrentAddr(v), pParse->addrExplain, 0, zMsg,P4_DYNAMIC); return ret; } #endif /* SQLITE_OMIT_EXPLAIN */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS /* ** Configure the VM passed as the first argument with an | > > | 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 | sqlite3_str_appendf(&str, "%s=?", z); } } sqlite3_str_append(&str, ")", 1); zMsg = sqlite3StrAccumFinish(&str); ret = sqlite3VdbeAddOp4(v, OP_Explain, sqlite3VdbeCurrentAddr(v), pParse->addrExplain, 0, zMsg,P4_DYNAMIC); sqlite3VdbeScanStatus(v, sqlite3VdbeCurrentAddr(v)-1, 0, 0, 0, 0); return ret; } #endif /* SQLITE_OMIT_EXPLAIN */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS /* ** Configure the VM passed as the first argument with an |
︙ | ︙ | |||
288 289 290 291 292 293 294 | Vdbe *v, /* Vdbe to add scanstatus entry to */ SrcList *pSrclist, /* FROM clause pLvl reads data from */ WhereLevel *pLvl, /* Level to add scanstatus() entry for */ int addrExplain /* Address of OP_Explain (or 0) */ ){ const char *zObj = 0; WhereLoop *pLoop = pLvl->pWLoop; | > > > | > > > > > > > > > > | 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 | Vdbe *v, /* Vdbe to add scanstatus entry to */ SrcList *pSrclist, /* FROM clause pLvl reads data from */ WhereLevel *pLvl, /* Level to add scanstatus() entry for */ int addrExplain /* Address of OP_Explain (or 0) */ ){ const char *zObj = 0; WhereLoop *pLoop = pLvl->pWLoop; int wsFlags = pLoop->wsFlags; int viaCoroutine = 0; if( (wsFlags & WHERE_VIRTUALTABLE)==0 && pLoop->u.btree.pIndex!=0 ){ zObj = pLoop->u.btree.pIndex->zName; }else{ zObj = pSrclist->a[pLvl->iFrom].zName; viaCoroutine = pSrclist->a[pLvl->iFrom].fg.viaCoroutine; } sqlite3VdbeScanStatus( v, addrExplain, pLvl->addrBody, pLvl->addrVisit, pLoop->nOut, zObj ); if( viaCoroutine==0 ){ if( (wsFlags & (WHERE_MULTI_OR|WHERE_AUTO_INDEX))==0 ){ sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iTabCur); } if( wsFlags & WHERE_INDEXED ){ sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iIdxCur); } } } #endif /* ** Disable a term in the WHERE clause. Except, do not disable the term ** if it controls a LEFT OUTER JOIN and it did not originate in the ON |
︙ | ︙ | |||
355 356 357 358 359 360 361 | ){ if( nLoop && (pTerm->wtFlags & TERM_LIKE)!=0 ){ pTerm->wtFlags |= TERM_LIKECOND; }else{ pTerm->wtFlags |= TERM_CODED; } #ifdef WHERETRACE_ENABLED | | | 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 | ){ if( nLoop && (pTerm->wtFlags & TERM_LIKE)!=0 ){ pTerm->wtFlags |= TERM_LIKECOND; }else{ pTerm->wtFlags |= TERM_CODED; } #ifdef WHERETRACE_ENABLED if( (sqlite3WhereTrace & 0x4001)==0x4001 ){ sqlite3DebugPrintf("DISABLE-"); sqlite3WhereTermPrint(pTerm, (int)(pTerm - (pTerm->pWC->a))); } #endif if( pTerm->iParent<0 ) break; pTerm = &pTerm->pWC->a[pTerm->iParent]; assert( pTerm!=0 ); |
︙ | ︙ | |||
1342 1343 1344 1345 1346 1347 1348 | db = pParse->db; pLoop = pLevel->pWLoop; pTabItem = &pWInfo->pTabList->a[pLevel->iFrom]; iCur = pTabItem->iCursor; pLevel->notReady = notReady & ~sqlite3WhereGetMask(&pWInfo->sMaskSet, iCur); bRev = (pWInfo->revMask>>iLevel)&1; VdbeModuleComment((v, "Begin WHERE-loop%d: %s",iLevel,pTabItem->pTab->zName)); | | | > | | > | | 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 | db = pParse->db; pLoop = pLevel->pWLoop; pTabItem = &pWInfo->pTabList->a[pLevel->iFrom]; iCur = pTabItem->iCursor; pLevel->notReady = notReady & ~sqlite3WhereGetMask(&pWInfo->sMaskSet, iCur); bRev = (pWInfo->revMask>>iLevel)&1; VdbeModuleComment((v, "Begin WHERE-loop%d: %s",iLevel,pTabItem->pTab->zName)); #if WHERETRACE_ENABLED /* 0x4001 */ if( sqlite3WhereTrace & 0x1 ){ sqlite3DebugPrintf("Coding level %d of %d: notReady=%llx iFrom=%d\n", iLevel, pWInfo->nLevel, (u64)notReady, pLevel->iFrom); if( sqlite3WhereTrace & 0x1000 ){ sqlite3WhereLoopPrint(pLoop, pWC); } } if( (sqlite3WhereTrace & 0x4001)==0x4001 ){ if( iLevel==0 ){ sqlite3DebugPrintf("WHERE clause being coded:\n"); sqlite3TreeViewExpr(0, pWInfo->pWhere, 0); } sqlite3DebugPrintf("All WHERE-clause terms before coding:\n"); sqlite3WhereClausePrint(pWC); } |
︙ | ︙ | |||
2272 2273 2274 2275 2276 2277 2278 | } if( pAndExpr ){ pAndExpr->pLeft = pOrExpr; pOrExpr = pAndExpr; } /* Loop through table entries that match term pOrTerm. */ ExplainQueryPlan((pParse, 1, "INDEX %d", ii+1)); | | | 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 | } if( pAndExpr ){ pAndExpr->pLeft = pOrExpr; pOrExpr = pAndExpr; } /* Loop through table entries that match term pOrTerm. */ ExplainQueryPlan((pParse, 1, "INDEX %d", ii+1)); WHERETRACE(0xffffffff, ("Subplan for OR-clause:\n")); pSubWInfo = sqlite3WhereBegin(pParse, pOrTab, pOrExpr, 0, 0, 0, WHERE_OR_SUBCLAUSE, iCovCur); assert( pSubWInfo || pParse->nErr ); if( pSubWInfo ){ WhereLoop *pSubLoop; int addrExplain = sqlite3WhereExplainOneScan( pParse, pOrTab, &pSubWInfo->a[0], 0 |
︙ | ︙ | |||
2509 2510 2511 2512 2513 2514 2515 | if( x>0 ){ skipLikeAddr = sqlite3VdbeAddOp1(v, (x&1)?OP_IfNot:OP_If,(int)(x>>1)); VdbeCoverageIf(v, (x&1)==1); VdbeCoverageIf(v, (x&1)==0); } #endif } | | | | 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 | if( x>0 ){ skipLikeAddr = sqlite3VdbeAddOp1(v, (x&1)?OP_IfNot:OP_If,(int)(x>>1)); VdbeCoverageIf(v, (x&1)==1); VdbeCoverageIf(v, (x&1)==0); } #endif } #ifdef WHERETRACE_ENABLED /* 0xffffffff */ if( sqlite3WhereTrace ){ VdbeNoopComment((v, "WhereTerm[%d] (%p) priority=%d", pWC->nTerm-j, pTerm, iLoop)); } if( sqlite3WhereTrace & 0x4000 ){ sqlite3DebugPrintf("Coding auxiliary constraint:\n"); sqlite3WhereTermPrint(pTerm, pWC->nTerm-j); } #endif sqlite3ExprIfFalse(pParse, pE, addrCont, SQLITE_JUMPIFNULL); if( skipLikeAddr ) sqlite3VdbeJumpHere(v, skipLikeAddr); pTerm->wtFlags |= TERM_CODED; |
︙ | ︙ | |||
2543 2544 2545 2546 2547 2548 2549 | WhereTerm *pAlt; if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; if( (pTerm->eOperator & (WO_EQ|WO_IS))==0 ) continue; if( (pTerm->eOperator & WO_EQUIV)==0 ) continue; if( pTerm->leftCursor!=iCur ) continue; if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT) ) continue; pE = pTerm->pExpr; | | | | 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 | WhereTerm *pAlt; if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; if( (pTerm->eOperator & (WO_EQ|WO_IS))==0 ) continue; if( (pTerm->eOperator & WO_EQUIV)==0 ) continue; if( pTerm->leftCursor!=iCur ) continue; if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT) ) continue; pE = pTerm->pExpr; #ifdef WHERETRACE_ENABLED /* 0x4001 */ if( (sqlite3WhereTrace & 0x4001)==0x4001 ){ sqlite3DebugPrintf("Coding transitive constraint:\n"); sqlite3WhereTermPrint(pTerm, pWC->nTerm-j); } #endif assert( !ExprHasProperty(pE, EP_OuterON) ); assert( (pTerm->prereqRight & pLevel->notReady)!=0 ); assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); |
︙ | ︙ | |||
2659 2660 2661 2662 2663 2664 2665 | if( pTabItem->fg.jointype & JT_LTORJ ) continue; assert( pTerm->pExpr ); sqlite3ExprIfFalse(pParse, pTerm->pExpr, addrCont, SQLITE_JUMPIFNULL); pTerm->wtFlags |= TERM_CODED; } } | | | | | 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 | if( pTabItem->fg.jointype & JT_LTORJ ) continue; assert( pTerm->pExpr ); sqlite3ExprIfFalse(pParse, pTerm->pExpr, addrCont, SQLITE_JUMPIFNULL); pTerm->wtFlags |= TERM_CODED; } } #if WHERETRACE_ENABLED /* 0x4001 */ if( sqlite3WhereTrace & 0x4000 ){ sqlite3DebugPrintf("All WHERE-clause terms after coding level %d:\n", iLevel); sqlite3WhereClausePrint(pWC); } if( sqlite3WhereTrace & 0x1 ){ sqlite3DebugPrintf("End Coding level %d: notReady=%llx\n", iLevel, (u64)pLevel->notReady); } #endif return pLevel->notReady; } |
︙ | ︙ |
Changes to src/window.c.
︙ | ︙ | |||
1065 1066 1067 1068 1069 1070 1071 | sqlite3Expr(db, TK_INTEGER, "0") ); } pSub = sqlite3SelectNew( pParse, pSublist, pSrc, pWhere, pGroupBy, pHaving, pSort, 0, 0 ); | | | 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 | sqlite3Expr(db, TK_INTEGER, "0") ); } pSub = sqlite3SelectNew( pParse, pSublist, pSrc, pWhere, pGroupBy, pHaving, pSort, 0, 0 ); TREETRACE(0x40,pParse,pSub, ("New window-function subquery in FROM clause of (%u/%p)\n", p->selId, p)); p->pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); assert( pSub!=0 || p->pSrc==0 ); /* Due to db->mallocFailed test inside ** of sqlite3DbMallocRawNN() called from ** sqlite3SrcListAppend() */ if( p->pSrc ){ |
︙ | ︙ |
Changes to test/affinity3.test.
︙ | ︙ | |||
100 101 102 103 104 105 106 | INSERT INTO data VALUES('4','xyz'); CREATE VIEW idmap as SELECT * FROM map_integer UNION SELECT * FROM map_text; CREATE TABLE mzed AS SELECT * FROM idmap; } | | | | | | | | | 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | INSERT INTO data VALUES('4','xyz'); CREATE VIEW idmap as SELECT * FROM map_integer UNION SELECT * FROM map_text; CREATE TABLE mzed AS SELECT * FROM idmap; } do_execsql_test affinity3-210 { PRAGMA automatic_index=ON; SELECT * FROM data JOIN idmap USING(id); } {4 xyz e} do_execsql_test affinity3-220 { SELECT * FROM data JOIN mzed USING(id); } {4 xyz e} do_execsql_test affinity3-250 { PRAGMA automatic_index=OFF; SELECT * FROM data JOIN idmap USING(id); } {4 xyz e} do_execsql_test affinity3-260 { SELECT * FROM data JOIN mzed USING(id); } {4 xyz e} finish_test |
Added test/basexx1.test.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | # 2022 November 22 # # 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. # #*********************************************************************** # set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix basexx if {[catch {load_static_extension db basexx} error]} { puts "Skipping basexx tests, hit load error: $error" finish_test; return } # Empty blobs encode to empty strings. do_execsql_test 100 { SELECT base64(x'')||base85(x''); } {{}} # Empty strings decode to empty blobs. do_execsql_test 101 { SELECT hex(x'01'||base64('')||base85('')||x'02'); } {0102} # Basic base64 encoding do_execsql_test 102 { SELECT base64(x'000102030405'); SELECT base64(x'0001020304'); SELECT base64(x'00010203'); } {{AAECAwQF } {AAECAwQ= } {AAECAw== }} # Basic base64 decoding with pad chars do_execsql_test 103 { SELECT hex(base64('AAECAwQF')); SELECT hex(base64('AAECAwQ=')); SELECT hex(base64('AAECAw==')); } {000102030405 0001020304 00010203} # Basic base64 decoding without pad chars and with whitespace do_execsql_test 104 { SELECT hex(base64(' AAECAwQF ')); SELECT hex(base64(' AAECAwQ')); SELECT hex(base64('AAECAw ')); } {000102030405 0001020304 00010203} # Basic base85 encoding do_execsql_test 105 { SELECT base85(x'000102030405'); SELECT base85(x'0001020304'); SELECT base85(x'00010203'); } {{##/2,#2/ } {##/2,#* } {##/2, }} # Basic base85 decoding with and without whitespace do_execsql_test 106 { SELECT hex(base85('##/2,#2/')); SELECT hex(base85('##/2,#*')); SELECT hex(base85('##/2,')); SELECT hex(base85(' ##/2,#2/ ')); SELECT hex(base85(' ##/2,#*')); SELECT hex(base85('##/2, ')); } {000102030405 0001020304 00010203 000102030405 0001020304 00010203} # Round-trip some random blobs. do_execsql_test 107 { CREATE TEMP TABLE rb( len int, b blob ) STRICT; INSERT INTO rb(len) VALUES (1),(2),(3),(4),(5),(150),(151),(152),(153),(1054); UPDATE rb SET b = randomblob(len); SELECT len, base64(base64(b))=b, base85(base85(b))=b FROM rb ORDER BY len; } {1 1 1 2 1 1 3 1 1 4 1 1 5 1 1 150 1 1 151 1 1 152 1 1 153 1 1 1054 1 1} # Same round-trip but with space or junk prepended and/or appended or not. do_execsql_test 108 { CREATE TEMP TABLE junk(j text, rank int); INSERT INTO junk VALUES ('',0),(' ',1),('~',2); SELECT len, base64(j.j||base64(b)||j.j)=b, base85(j.j||base85(b)||j.j)=b FROM rb r, junk j WHERE j.rank=(r.len+r.len/25)%3 ORDER BY len; } {1 1 1 2 1 1 3 1 1 4 1 1 5 1 1 150 1 1 151 1 1 152 1 1 153 1 1 1054 1 1} # Exercise the fail-on-too-large result feature. set inLimit [sqlite3_limit db SQLITE_LIMIT_LENGTH -1] sqlite3_limit db SQLITE_LIMIT_LENGTH 1300 do_catchsql_test 109 { SELECT len, base64(b) FROM rb WHERE len>200; } {1 {blob expanded to base64 too big}} do_catchsql_test 110 { SELECT len, base85(b) FROM rb WHERE len>200; } {1 {blob expanded to base85 too big}} do_catchsql_test 111 { SELECT length(base85(b))=1335 FROM rb WHERE len=1054; } {1 {blob expanded to base85 too big}} sqlite3_limit db SQLITE_LIMIT_LENGTH $inLimit # Exercise is_base85(t) do_execsql_test 112 { SELECT is_base85(' '||base85(x'123456')||char(10)), is_base85('#$%&*+,-./0123456789:;<=>?@' ||'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ||'[\]^_`' ||'abcdefghijklmnopqrstuvwxyz'), is_base85('!'), is_base85('"'), is_base85(''''), is_base85('('), is_base85(')'), is_base85(char(123)), is_base85('|'), is_base85(char(125)), is_base85('~'), is_base85(char(127)); } {1 1 0 0 0 0 0 0 0 0 0 0} do_execsql_test 113 { SELECT is_base85(NULL) IS NULL; } {1} do_catchsql_test 114 { SELECT is_base85(1); } {1 {is_base85 accepts only text or NULL}} do_catchsql_test 115 { SELECT is_base85(1.1); } {1 {is_base85 accepts only text or NULL}} do_catchsql_test 116 { SELECT is_base85(x'00'); } {1 {is_base85 accepts only text or NULL}} # Round-trip many bigger random blobs. do_execsql_test 117 { CREATE TABLE bs(b blob, num); INSERT INTO bs SELECT randomblob(4000 + n%3), n FROM ( WITH RECURSIVE seq(n) AS ( VALUES(1) UNION ALL SELECT n+1 FROM seq WHERE n<100 ) SELECT n FROM seq); SELECT num FROM bs WHERE base64(base64(b))!=b; SELECT num FROM bs WHERE base85(base85(b))!=b; } {} finish_test |
Changes to test/cast.test.
︙ | ︙ | |||
477 478 479 480 481 482 483 | reset_db do_execsql_test cast-9.0 { CREATE TABLE t0(c0); INSERT INTO t0(c0) VALUES (0); CREATE VIEW v1(c0, c1) AS SELECT CAST(0.0 AS NUMERIC), COUNT(*) OVER () FROM t0; SELECT v1.c0 FROM v1, t0 WHERE v1.c0=0; | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 | reset_db do_execsql_test cast-9.0 { CREATE TABLE t0(c0); INSERT INTO t0(c0) VALUES (0); CREATE VIEW v1(c0, c1) AS SELECT CAST(0.0 AS NUMERIC), COUNT(*) OVER () FROM t0; SELECT v1.c0 FROM v1, t0 WHERE v1.c0=0; } {0.0} # Set the 2022-12-10 "reopen" of ticket [https://sqlite.org/src/tktview/57c47526c3] # do_execsql_test cast-9.1 { CREATE TABLE dual(dummy TEXT); INSERT INTO dual VALUES('X'); SELECT CAST(4 AS NUMERIC); } {4} do_execsql_test cast-9.2 { SELECT CAST(4.0 AS NUMERIC); } {4.0} do_execsql_test cast-9.3 { SELECT CAST(4.5 AS NUMERIC); } {4.5} do_execsql_test cast-9.4 { SELECT x, typeof(x) FROM (SELECT CAST(4 AS NUMERIC) AS x) JOIN dual; } {4 integer} do_execsql_test cast-9.5 { SELECT x, typeof(x) FROM dual CROSS JOIN (SELECT CAST(4 AS NUMERIC) AS x); } {4 integer} do_execsql_test cast-9.10 { SELECT x, typeof(x) FROM (SELECT CAST(4.0 AS NUMERIC) AS x) JOIN dual; } {4.0 real} do_execsql_test cast-9.11 { SELECT x, typeof(x) FROM dual CROSS JOIN (SELECT CAST(4.0 AS NUMERIC) AS x); } {4.0 real} do_execsql_test cast-9.12 { SELECT x, typeof(x) FROM (SELECT CAST(4.5 AS NUMERIC) AS x) JOIN dual; } {4.5 real} do_execsql_test cast-9.13 { SELECT x, typeof(x) FROM dual CROSS JOIN (SELECT CAST(4.5 AS NUMERIC) AS x); } {4.5 real} # 2022-12-15 dbsqlfuzz c9ee6f9a0a8b8fefb02cf69de2a8b67ca39525c8 # # Added a new SQLITE_AFF_FLEXNUM that does not try to convert int to real or # real to int. # do_execsql_test cast-10.1 { VALUES(CAST(44 AS REAL)),(55); } {44.0 55} do_execsql_test cast-10.2 { SELECT CAST(44 AS REAL) AS 'm' UNION ALL SELECT 55; } {44.0 55} do_execsql_test cast-10.3 { SELECT * FROM (VALUES(CAST(44 AS REAL)),(55)); } {44.0 55} do_execsql_test cast-10.4 { SELECT * FROM (SELECT CAST(44 AS REAL) AS 'm' UNION ALL SELECT 55); } {44.0 55} do_execsql_test cast-10.5 { SELECT * FROM dual CROSS JOIN (VALUES(CAST(44 AS REAL)),(55)); } {X 44.0 X 55} do_execsql_test cast-10.6 { SELECT * FROM dual CROSS JOIN (SELECT CAST(44 AS REAL) AS 'm' UNION ALL SELECT 55); } {X 44.0 X 55} do_execsql_test cast-10.7 { DROP VIEW v1; CREATE VIEW v1 AS SELECT CAST(44 AS REAL) AS 'm' UNION ALL SELECT 55; SELECT name, type FROM pragma_table_info('v1'); } {m NUM} do_execsql_test cast-10.8 { CREATE VIEW v2 AS VALUES(CAST(44 AS REAL)),(55); SELECT type FROM pragma_table_info('v2'); } {NUM} do_execsql_test cast-10.9 { SELECT * FROM v1; } {44.0 55} do_execsql_test cast-10.10 { SELECT * FROM v2; } {44.0 55} finish_test |
Changes to test/eqp.test.
︙ | ︙ | |||
90 91 92 93 94 95 96 | `--USE TEMP B-TREE FOR DISTINCT } do_eqp_test 1.7.1 { SELECT * FROM t3 JOIN (SELECT 1) } { QUERY PLAN | | | | | | | | | 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | `--USE TEMP B-TREE FOR DISTINCT } do_eqp_test 1.7.1 { SELECT * FROM t3 JOIN (SELECT 1) } { QUERY PLAN |--CO-ROUTINE (subquery-xxxxxx) | `--SCAN CONSTANT ROW |--SCAN (subquery-xxxxxx) `--SCAN t3 } do_eqp_test 1.7.2 { SELECT * FROM t3 JOIN (SELECT 1) AS v1 } { QUERY PLAN |--CO-ROUTINE v1 | `--SCAN CONSTANT ROW |--SCAN v1 `--SCAN t3 } do_eqp_test 1.7.3 { SELECT * FROM t3 AS xx JOIN (SELECT 1) AS yy } { QUERY PLAN |--CO-ROUTINE yy | `--SCAN CONSTANT ROW |--SCAN yy `--SCAN xx } do_eqp_test 1.8 { SELECT * FROM t3 JOIN (SELECT 1 UNION SELECT 2) } { QUERY PLAN |--CO-ROUTINE (subquery-xxxxxx) | `--COMPOUND QUERY | |--LEFT-MOST SUBQUERY | | `--SCAN CONSTANT ROW | `--UNION USING TEMP B-TREE | `--SCAN CONSTANT ROW |--SCAN (subquery-xxxxxx) `--SCAN t3 } do_eqp_test 1.9 { SELECT * FROM t3 JOIN (SELECT 1 EXCEPT SELECT a FROM t3 LIMIT 17) AS abc } { QUERY PLAN |--CO-ROUTINE abc | `--COMPOUND QUERY | |--LEFT-MOST SUBQUERY | | `--SCAN CONSTANT ROW | `--EXCEPT USING TEMP B-TREE | `--SCAN t3 |--SCAN abc `--SCAN t3 } do_eqp_test 1.10 { SELECT * FROM t3 JOIN (SELECT 1 INTERSECT SELECT a FROM t3 LIMIT 17) AS abc } { QUERY PLAN |--CO-ROUTINE abc | `--COMPOUND QUERY | |--LEFT-MOST SUBQUERY | | `--SCAN CONSTANT ROW | `--INTERSECT USING TEMP B-TREE | `--SCAN t3 |--SCAN abc `--SCAN t3 } do_eqp_test 1.11 { SELECT * FROM t3 JOIN (SELECT 1 UNION ALL SELECT a FROM t3 LIMIT 17) abc } { QUERY PLAN |--CO-ROUTINE abc | `--COMPOUND QUERY | |--LEFT-MOST SUBQUERY | | `--SCAN CONSTANT ROW | `--UNION ALL | `--SCAN t3 |--SCAN abc `--SCAN t3 |
︙ | ︙ | |||
291 292 293 294 295 296 297 | det 3.2.2 { SELECT * FROM (SELECT * FROM t1 ORDER BY x LIMIT 10) AS x1, (SELECT * FROM t2 ORDER BY x LIMIT 10) AS x2 ORDER BY x2.y LIMIT 5 } { QUERY PLAN | | | 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 | det 3.2.2 { SELECT * FROM (SELECT * FROM t1 ORDER BY x LIMIT 10) AS x1, (SELECT * FROM t2 ORDER BY x LIMIT 10) AS x2 ORDER BY x2.y LIMIT 5 } { QUERY PLAN |--CO-ROUTINE x1 | |--SCAN t1 | `--USE TEMP B-TREE FOR ORDER BY |--MATERIALIZE x2 | `--SCAN t2 USING INDEX t2i1 |--SCAN x1 |--SCAN x2 `--USE TEMP B-TREE FOR ORDER BY |
︙ | ︙ | |||
830 831 832 833 834 835 836 | substr(event.comment,instr(event.comment,':')+1) FROM thread, blob, event WHERE blob.rid=thread.last AND event.objid=thread.last ORDER BY 1; } { QUERY PLAN | | | 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 | substr(event.comment,instr(event.comment,':')+1) FROM thread, blob, event WHERE blob.rid=thread.last AND event.objid=thread.last ORDER BY 1; } { QUERY PLAN |--CO-ROUTINE thread | |--SCAN x USING INDEX forumthread | |--USING ROWID SEARCH ON TABLE private FOR IN-OPERATOR | |--CORRELATED SCALAR SUBQUERY xxxxxx | | |--SEARCH forumpost USING COVERING INDEX forumthread (froot=?) | | `--USING ROWID SEARCH ON TABLE private FOR IN-OPERATOR | `--USE TEMP B-TREE FOR ORDER BY |--SCAN thread |--SEARCH blob USING INTEGER PRIMARY KEY (rowid=?) |--SEARCH event USING INTEGER PRIMARY KEY (rowid=?) `--USE TEMP B-TREE FOR ORDER BY } finish_test |
Changes to test/fts3expr4.test.
︙ | ︙ | |||
46 47 48 49 50 51 52 | do_icu_expr_test 1.5 {(x OR y)} {OR {PHRASE 3 0 x} {PHRASE 3 0 y}} do_icu_expr_test 1.6 { "(x OR y)" } {PHRASE 3 0 ( x or y )} # In "col:word", if "col" is not the name of a column, the entire thing # is passed to the tokenizer. # do_icu_expr_test 1.7 {a:word} {PHRASE 0 0 word} | | > > > > > > > > > | 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | do_icu_expr_test 1.5 {(x OR y)} {OR {PHRASE 3 0 x} {PHRASE 3 0 y}} do_icu_expr_test 1.6 { "(x OR y)" } {PHRASE 3 0 ( x or y )} # In "col:word", if "col" is not the name of a column, the entire thing # is passed to the tokenizer. # do_icu_expr_test 1.7 {a:word} {PHRASE 0 0 word} # do_icu_expr_test 1.8 {d:word} {PHRASE 3 0 d:word} do_test 1.8 { set res [ db one {SELECT fts3_exprtest('icu en_US', 'd:word', 'a', 'b', 'c')} ] expr { $res=="PHRASE 3 0 d:word" || $res=="AND {AND {PHRASE 3 0 d} {PHRASE 3 0 :}} {PHRASE 3 0 word}" } } 1 set sqlite_fts3_enable_parentheses 0 do_icu_expr_test 2.1 { f (e NEAR/2 a) } {AND {AND {AND {PHRASE 3 0 f} {PHRASE 3 0 (}} {NEAR/2 {PHRASE 3 0 e} {PHRASE 3 0 a}}} {PHRASE 3 0 )}} |
︙ | ︙ |
Changes to test/fuzzdata8.db.
cannot compute difference between binary files
Changes to test/fuzzinvariants.c.
︙ | ︙ | |||
132 133 134 135 136 137 138 | *pbCorrupt = 1; sqlite3_finalize(pCk); sqlite3_finalize(pTestStmt); return SQLITE_CORRUPT; } sqlite3_finalize(pCk); | | | < < | < < > | < < | 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | *pbCorrupt = 1; sqlite3_finalize(pCk); sqlite3_finalize(pTestStmt); return SQLITE_CORRUPT; } sqlite3_finalize(pCk); if( sqlite3_strlike("%group%by%",sqlite3_sql(pStmt),0)==0 ){ /* ** If there is a GROUP BY clause, it might not cover every term in the ** output. And then non-covered terms can take on a value from any ** row in the result set. This can cause differing answers. */ goto not_a_fault; } if( sqlite3_strlike("%limit%)%order%by%", sqlite3_sql(pTestStmt),0)==0 ){ /* crash-89bd6a6f8c6166e9a4c5f47b3e70b225f69b76c6 ** Original statement is: |
︙ | ︙ | |||
232 233 234 235 236 237 238 | ** ORDER BY N ** */ static char *fuzz_invariant_sql(sqlite3_stmt *pStmt, int iCnt){ const char *zIn; size_t nIn; const char *zAnd = "WHERE"; | | | 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | ** ORDER BY N ** */ static char *fuzz_invariant_sql(sqlite3_stmt *pStmt, int iCnt){ const char *zIn; size_t nIn; const char *zAnd = "WHERE"; int i, j; sqlite3_str *pTest; sqlite3_stmt *pBase = 0; sqlite3 *db = sqlite3_db_handle(pStmt); int rc; int nCol = sqlite3_column_count(pStmt); int mxCnt; int bDistinct = 0; |
︙ | ︙ | |||
276 277 278 279 280 281 282 283 284 285 286 287 288 289 | if( zSuffix && isdigit(zSuffix[1]) && (zSuffix[1]>'3' || isdigit(zSuffix[2])) ){ /* This is a randomized column name and so cannot be used in the ** WHERE clause. */ continue; } if( iCnt==0 ) continue; if( iCnt>1 && i+2!=iCnt ) continue; if( zColName==0 ) continue; if( sqlite3_column_type(pStmt, i)==SQLITE_NULL ){ sqlite3_str_appendf(pTest, " %s \"%w\" ISNULL", zAnd, zColName); }else{ | > > > > > > > > | 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 | if( zSuffix && isdigit(zSuffix[1]) && (zSuffix[1]>'3' || isdigit(zSuffix[2])) ){ /* This is a randomized column name and so cannot be used in the ** WHERE clause. */ continue; } for(j=0; j<i; j++){ const char *zPrior = sqlite3_column_name(pBase, j); if( sqlite3_stricmp(zPrior, zColName)==0 ) break; } if( j<i ){ /* Duplicate column name */ continue; } if( iCnt==0 ) continue; if( iCnt>1 && i+2!=iCnt ) continue; if( zColName==0 ) continue; if( sqlite3_column_type(pStmt, i)==SQLITE_NULL ){ sqlite3_str_appendf(pTest, " %s \"%w\" ISNULL", zAnd, zColName); }else{ |
︙ | ︙ |
Changes to test/indexexpr1.test.
︙ | ︙ | |||
416 417 418 419 420 421 422 | REPLACE INTO t1500(a,b) VALUES(1,3); -- formerly caused assertion fault SELECT * FROM t1500; } {1 3} # 2018-01-03 OSSFuzz discovers another test case for the same problem # above. # | | | | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 | REPLACE INTO t1500(a,b) VALUES(1,3); -- formerly caused assertion fault SELECT * FROM t1500; } {1 3} # 2018-01-03 OSSFuzz discovers another test case for the same problem # above. # do_execsql_test indexexpr1-1510 { DROP TABLE IF EXISTS t1; CREATE TABLE t1(a PRIMARY KEY,b UNIQUE); REPLACE INTO t1 VALUES(2, 1); REPLACE INTO t1 SELECT 6,1; CREATE INDEX t1aa ON t1(a-a); REPLACE INTO t1 SELECT a, randomblob(a) FROM t1 } {} # 2018-01-31 https://www.sqlite.org/src/tktview/343634942dd54ab57b702411 # When an index on an expression depends on the string representation of # a numeric table column, trouble can arise since there are multiple # string that can map to the same numeric value. (Ex: 123, 0123, 000123). # do_execsql_test indexexpr1-1600 { DROP TABLE IF EXISTS t1; CREATE TABLE t1 (a INTEGER, b); CREATE INDEX idx1 ON t1 (lower(a)); INSERT INTO t1 VALUES('0001234',3); PRAGMA integrity_check; } {ok} do_execsql_test indexexpr1-1610 { INSERT INTO t1 VALUES('1234',0),('001234',2),('01234',1); SELECT b FROM t1 WHERE lower(a)='1234' ORDER BY +b; } {0 1 2 3} do_execsql_test indexexpr1-1620 { SELECT b FROM t1 WHERE lower(a)='01234' ORDER BY +b; } {} # 2019-08-09 https://www.sqlite.org/src/info/9080b6227fabb466 # ExprImpliesExpr theorem prover bug: # "(NULL IS FALSE) IS FALSE" does not imply "NULL IS NULL" # do_execsql_test indexexpr1-1700 { DROP TABLE IF EXISTS t0; CREATE TABLE t0(c0); INSERT INTO t0(c0) VALUES (0); CREATE INDEX i0 ON t0(NULL > c0) WHERE (NULL NOT NULL); SELECT * FROM t0 WHERE ((NULL IS FALSE) IS FALSE); } {0} # 2019-09-02 https://www.sqlite.org/src/tktview/57af00b6642ecd6848 # When the expression of an an index-on-expression references a # table column of type REAL that is actually holding an MEM_IntReal # value, be sure to use the REAL value and not the INT value when # computing the expression. # ifcapable like_match_blobs { do_execsql_test indexexpr1-1800 { DROP TABLE IF EXISTS t0; CREATE TABLE t0(c0 REAL, c1 TEXT); CREATE INDEX i0 ON t0(+c0, c0); INSERT INTO t0(c0) VALUES(0); SELECT CAST(+ t0.c0 AS BLOB) LIKE 0 FROM t0; } {0} do_execsql_test indexexpr1-1810 { SELECT CAST(+ t0.c0 AS BLOB) LIKE '0.0' FROM t0; } {1} do_execsql_test indexexpr1-1820 { DROP TABLE IF EXISTS t1; CREATE TABLE t1(x REAL); CREATE INDEX t1x ON t1(x, +x); INSERT INTO t1(x) VALUES(2); SELECT +x FROM t1 WHERE x=2; } {2.0} } # 2022-04-30 https://sqlite.org/forum/info/7efabf4b03328e57 # Assertion fault during a DELETE INDEXED BY. # reset_db do_execsql_test indexexpr1-1900 { CREATE TABLE t1(x TEXT PRIMARY KEY, y TEXT, z INT); INSERT INTO t1(x,y,z) VALUES('alpha','ALPHA',1),('bravo','charlie',1); CREATE INDEX i1 ON t1(+y COLLATE NOCASE); SELECT * FROM t1; } {alpha ALPHA 1 bravo charlie 1} do_execsql_test indexexpr1-1910 { DELETE FROM t1 INDEXED BY i1 WHERE x IS +y COLLATE NOCASE IN (SELECT z FROM t1) RETURNING *; } {alpha ALPHA 1} do_execsql_test indexexpr1-1920 { SELECT * FROM t1; } {bravo charlie 1} # 2022-11-28 Ticket 695a1a53de # Improved ability to recognize that an index on an expression is a # covering index. # reset_db do_execsql_test indexexpr1-2000 { CREATE TABLE t1(a INT, b TEXT); INSERT INTO t1(a,b) VALUES (10, '{"one":5,"two":6}'), (10, '{"one":50,"two":60}'), (10, '{"three":99}'), (11, '{"one":100,"two":200}'); CREATE INDEX t1_one ON t1(a, b->>'one'); CREATE INDEX t1_two ON t1(a, b->>'two'); } do_execsql_test indexexpr1-2010 { EXPLAIN QUERY PLAN SELECT sum(b->>'one') FROM t1 WHERE a=10; /* Query AA */ } {/.* t1_one .*/} do_execsql_test indexexpr1-2011 { SELECT sum(b->>'one') FROM t1 WHERE a=10; /* Query AA */ } {55} do_execsql_test indexexpr1-2020 { EXPLAIN QUERY PLAN SELECT sum(b->>'two') FROM t1 WHERE a=10; /* Query BB */ } {/.* t1_two .*/} do_execsql_test indexexpr1-2021 { SELECT sum(b->>'two') FROM t1 WHERE a=10; /* Query BB */ } {66} do_execsql_test indexexpr1-2030 { DROP TABLE t1; CREATE TABLE t1(a INT, b TEXT, c INT, d INT); INSERT INTO t1(a,b,c,d) VALUES (1, '{"x":1}', 12, 3), (1, '{"x":2}', 4, 5), (1, '{"x":1}', 6, 11), (2, '{"x":1}', 22, 3), (2, '{"x":2}', 4, 5), (3, '{"x":1}', 6, 7); CREATE INDEX t1x ON t1(d, a, b->>'x', c); } do_execsql_test indexexpr1-2030 { SELECT a, SUM(1) AS t1, SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, SUM(c) AS t3, SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 FROM t1; } {1 6 4 54 46} do_execsql_test indexexpr1-2030 { explain query plan SELECT a, SUM(1) AS t1, SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, SUM(c) AS t3, SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 FROM t1; } {/.*SCAN t1 USING INDEX t1x.*/} finish_test |
Changes to test/intreal.test.
︙ | ︙ | |||
91 92 93 94 95 96 97 | CREATE UNIQUE INDEX i0 ON t0(c1, 0 | c0); INSERT INTO t0(c0) VALUES (4750228396194493326), (0); UPDATE OR REPLACE t0 SET c0 = 'a', c1 = ''; SELECT * FROM t0 ORDER BY t0.c1; PRAGMA integrity_check; } {a {} ok} | < < < < < < < < < < < < < < < < < < | 91 92 93 94 95 96 97 98 | CREATE UNIQUE INDEX i0 ON t0(c1, 0 | c0); INSERT INTO t0(c0) VALUES (4750228396194493326), (0); UPDATE OR REPLACE t0 SET c0 = 'a', c1 = ''; SELECT * FROM t0 ORDER BY t0.c1; PRAGMA integrity_check; } {a {} ok} finish_test |
Added test/memdb2.test.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | # 2022-12-05 # # The author disclaims copyright to this source code. In place of # a legal notice, here is a blessing: # # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** # This file implements regression tests for SQLite library. The # focus of this file is the "memdb" VFS # set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix memdb2 do_not_use_codec ifcapable !deserialize { finish_test return } db close #------------------------------------------------------------------------- # Test that when using a memdb database, it is not possible to upgrade # to an EXCLUSIVE lock if some other client is holding SHARED. # foreach {tn fname} { 1 file:/test.db?vfs=memdb 2 file:\\test.db?vfs=memdb } { if {$tn==2} breakpoint sqlite3 db $fname -uri 1 sqlite3 db2 $fname -uri 1 do_execsql_test 1.$tn.1 { CREATE TABLE t1(x, y); INSERT INTO t1 VALUES(1, 2); } do_execsql_test -db db2 1.$tn.2 { BEGIN; SELECT * FROM t1; } {1 2} do_execsql_test 1.$tn.3 { BEGIN; INSERT INTO t1 VALUES(3, 4); } do_catchsql_test 1.$tn.4 { COMMIT } {1 {database is locked}} do_execsql_test -db db2 1.$tn.5 { SELECT * FROM t1; END; } {1 2} do_execsql_test 1.$tn.6 { COMMIT } {} do_execsql_test -db db2 1.$tn.7 { SELECT * FROM t1 } {1 2 3 4} db close db2 close } finish_test |
Changes to test/misc1.test.
︙ | ︙ | |||
601 602 603 604 605 606 607 | SELECT * FROM t19; } {1 2 3} do_execsql_test misc1-19.2 { CREATE TABLE t19b AS SELECT 4 AS '', 5 AS '', 6 AS ''; SELECT * FROM t19b; } {4 5 6} | | > > > | | | | 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 | SELECT * FROM t19; } {1 2 3} do_execsql_test misc1-19.2 { CREATE TABLE t19b AS SELECT 4 AS '', 5 AS '', 6 AS ''; SELECT * FROM t19b; } {4 5 6} # 2015-05-20: CREATE TABLE AS should not store INT value in a TEXT # column. # # 2022-12-14: Change: The column is not TEXT if the AS SELECT is # a compound with different types on each arm. # do_execsql_test misc1-19.3 { CREATE TABLE t19c(x TEXT); CREATE TABLE t19d AS SELECT * FROM t19c UNION ALL SELECT 1234; SELECT x, typeof(x) FROM t19d; } {1234 integer} # 2014-05-16: Tests for the SQLITE_TESTCTRL_FAULT_INSTALL feature. # unset -nocomplain fault_callbacks set fault_callbacks {} proc fault_callback {n} { lappend ::fault_callbacks $n return 0 } do_test misc1-19.11 { sqlite3_test_control_fault_install fault_callback set fault_callbacks } {0} do_test misc1-19.12 { sqlite3_test_control_fault_install set fault_callbacks } {0} # 2015-01-26: Valgrind-detected over-read. # Reported on sqlite-users@sqlite.org by Michal Zalewski. Found by afl-fuzz # presumably. |
︙ | ︙ |
Changes to test/multiplex3.test.
︙ | ︙ | |||
78 79 80 81 82 83 84 85 86 87 88 89 90 91 | } do_test 1.0 { setup_and_save_db } {} do_faultsim_test 1 -prep { multiplex_restore_db sqlite3 db file:test.db?8_3_names=1 sqlite3_multiplex_control db main chunk_size [expr 256*1024] } -body { execsql { UPDATE t1 SET a=randomblob(12), b=randomblob(1500) WHERE (rowid%32)=0 } } -test { faultsim_test_result {0 {}} if {$testrc!=0} { | > > | 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | } do_test 1.0 { setup_and_save_db } {} do_faultsim_test 1 -prep { multiplex_restore_db sqlite3 db file:test.db?8_3_names=1 sqlite3_multiplex_control db main chunk_size [expr 256*1024] execsql { PRAGMA journal_mode = truncate } execsql { PRAGMA synchronous = off } } -body { execsql { UPDATE t1 SET a=randomblob(12), b=randomblob(1500) WHERE (rowid%32)=0 } } -test { faultsim_test_result {0 {}} if {$testrc!=0} { |
︙ | ︙ |
Changes to test/orderby1.test.
︙ | ︙ | |||
39 40 41 42 43 44 45 46 47 48 49 50 51 52 | INSERT INTO track VALUES (NULL, 1, 1, 'one-a'), (NULL, 2, 2, 'two-b'), (NULL, 3, 3, 'three-c'), (NULL, 1, 3, 'one-c'), (NULL, 2, 1, 'two-a'), (NULL, 3, 1, 'three-a'); COMMIT; } } {} do_test 1.1a { db eval { SELECT name FROM album JOIN track USING (aid) ORDER BY title, tn } | > | 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | INSERT INTO track VALUES (NULL, 1, 1, 'one-a'), (NULL, 2, 2, 'two-b'), (NULL, 3, 3, 'three-c'), (NULL, 1, 3, 'one-c'), (NULL, 2, 1, 'two-a'), (NULL, 3, 1, 'three-a'); ANALYZE; COMMIT; } } {} do_test 1.1a { db eval { SELECT name FROM album JOIN track USING (aid) ORDER BY title, tn } |
︙ | ︙ | |||
176 177 178 179 180 181 182 183 184 185 186 187 188 189 | INSERT INTO track VALUES (1, 1, 'one-a'), (20, 2, 'two-b'), (3, 3, 'three-c'), (1, 3, 'one-c'), (20, 1, 'two-a'), (3, 1, 'three-a'); COMMIT; } } {} do_test 2.1a { db eval { SELECT name FROM album JOIN track USING (aid) ORDER BY title, tn } | > | 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | INSERT INTO track VALUES (1, 1, 'one-a'), (20, 2, 'two-b'), (3, 3, 'three-c'), (1, 3, 'one-c'), (20, 1, 'two-a'), (3, 1, 'three-a'); ANALYZE; COMMIT; } } {} do_test 2.1a { db eval { SELECT name FROM album JOIN track USING (aid) ORDER BY title, tn } |
︙ | ︙ | |||
323 324 325 326 327 328 329 330 331 332 333 334 335 336 | INSERT INTO track VALUES (NULL, 1, 1, 'one-a'), (NULL, 2, 2, 'two-b'), (NULL, 3, 3, 'three-c'), (NULL, 1, 3, 'one-c'), (NULL, 2, 1, 'two-a'), (NULL, 3, 1, 'three-a'); COMMIT; } } {} do_test 3.1a { db eval { SELECT name FROM album CROSS JOIN track USING (aid) ORDER BY title, tn DESC } | > | 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 | INSERT INTO track VALUES (NULL, 1, 1, 'one-a'), (NULL, 2, 2, 'two-b'), (NULL, 3, 3, 'three-c'), (NULL, 1, 3, 'one-c'), (NULL, 2, 1, 'two-a'), (NULL, 3, 1, 'three-a'); ANALYZE; COMMIT; } } {} do_test 3.1a { db eval { SELECT name FROM album CROSS JOIN track USING (aid) ORDER BY title, tn DESC } |
︙ | ︙ |
Changes to test/pushdown.test.
︙ | ︙ | |||
82 83 84 85 86 87 88 | SELECT * FROM u1 WHERE 123=( SELECT x FROM u2 WHERE x=a AND f('two') ) AND f('three')=123 } set L } {three} | > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | SELECT * FROM u1 WHERE 123=( SELECT x FROM u2 WHERE x=a AND f('two') ) AND f('three')=123 } set L } {three} # 2022-11-25 dbsqlfuzz crash-3a548de406a50e896c1bf7142692d35d339d697f # Disable the push-down optimization for compound subqueries if any # arm of the compound has an incompatible affinity. # reset_db do_execsql_test 3.1 { CREATE TABLE t0(c0 INT); INSERT INTO t0 VALUES(0); CREATE TABLE t1_a(a INTEGER PRIMARY KEY, b TEXT); INSERT INTO t1_a VALUES(1,'one'); CREATE TABLE t1_b(c INTEGER PRIMARY KEY, d TEXT); INSERT INTO t1_b VALUES(2,'two'); CREATE VIEW v0 AS SELECT CAST(t0.c0 AS INTEGER) AS c0 FROM t0; CREATE VIEW v1(a,b) AS SELECT a, b FROM t1_a UNION ALL SELECT c, 0 FROM t1_b; SELECT v1.a, quote(v1.b), t0.c0 AS cd FROM t0 LEFT JOIN v0 ON v0.c0!=0,v1; } { 1 'one' 0 2 0 0 } do_execsql_test 3.2 { SELECT a, quote(b), cd FROM ( SELECT v1.a, v1.b, t0.c0 AS cd FROM t0 LEFT JOIN v0 ON v0.c0!=0, v1 ) WHERE a=2 AND b='0' AND cd=0; } {} do_execsql_test 3.3 { SELECT a, quote(b), cd FROM ( SELECT v1.a, v1.b, t0.c0 AS cd FROM t0 LEFT JOIN v0 ON v0.c0!=0, v1 ) WHERE a=1 AND b='one' AND cd=0; } {1 'one' 0} do_execsql_test 3.4 { SELECT a, quote(b), cd FROM ( SELECT v1.a, v1.b, t0.c0 AS cd FROM t0 LEFT JOIN v0 ON v0.c0!=0, v1 ) WHERE a=2 AND b=0 AND cd=0; } { 2 0 0 } finish_test |
Changes to test/scanstatus.test.
︙ | ︙ | |||
32 33 34 35 36 37 38 | proc do_scanstatus_test {tn res} { set stmt [db version -last-stmt-ptr] set idx 0 set ret [list] while {1} { set r [sqlite3_stmt_scanstatus $stmt $idx] if {[llength $r]==0} break | > | > | 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | proc do_scanstatus_test {tn res} { set stmt [db version -last-stmt-ptr] set idx 0 set ret [list] while {1} { set r [sqlite3_stmt_scanstatus $stmt $idx] if {[llength $r]==0} break foreach v {nLoop nVisit nEst zName zExplain} { lappend ret $v [dict get $r $v] } incr idx } uplevel [list do_test $tn [list set {} $ret] [list {*}$res]] } do_execsql_test 1.1 { SELECT count(*) FROM t1, t2; } 6 |
︙ | ︙ | |||
308 309 310 311 312 313 314 | ANALYZE; } do_execsql_test 5.1.1 { SELECT count(*) FROM t1 WHERE a IN (SELECT b FROM t1 AS ii) } {2} do_scanstatus_test 5.1.2 { | | | | 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 | ANALYZE; } do_execsql_test 5.1.1 { SELECT count(*) FROM t1 WHERE a IN (SELECT b FROM t1 AS ii) } {2} do_scanstatus_test 5.1.2 { nLoop 1 nVisit 10 nEst 10.0 zName t1 zExplain {SCAN ii} nLoop 1 nVisit 2 nEst 8.0 zName sqlite_autoindex_t1_1 zExplain {SEARCH t1 USING COVERING INDEX sqlite_autoindex_t1_1 (a=?)} } do_execsql_test 5.2.1 { SELECT count(*) FROM t1 WHERE a IN (0, 1) } {2} |
︙ | ︙ | |||
337 338 339 340 341 342 343 | {SEARCH t2 USING COVERING INDEX t2xy (ANY(x) AND y=?)} } do_eqp_test 5.4.1 { SELECT count(*) FROM t1, t2 WHERE y = c; } { QUERY PLAN | | | | | 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 | {SEARCH t2 USING COVERING INDEX t2xy (ANY(x) AND y=?)} } do_eqp_test 5.4.1 { SELECT count(*) FROM t1, t2 WHERE y = c; } { QUERY PLAN |--SCAN t1 `--SEARCH t2 USING COVERING INDEX t2xy (ANY(x) AND y=?) } do_execsql_test 5.4.2 { SELECT count(*) FROM t1, t2 WHERE y = c; } {200} do_scanstatus_test 5.4.3 { nLoop 1 nVisit 10 nEst 10.0 zName t1 zExplain {SCAN t1} nLoop 10 nVisit 200 nEst 56.0 zName t2xy zExplain {SEARCH t2 USING COVERING INDEX t2xy (ANY(x) AND y=?)} } do_eqp_test 5.5.1 { SELECT count(*) FROM t1, t3 WHERE y = c; } { |
︙ | ︙ |
Added test/scanstatus2.test.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | # 2022 December 5 # # 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. # #*********************************************************************** # set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix scanstatus2 ifcapable !scanstatus { finish_test return } do_execsql_test 1.0 { CREATE TABLE t1(a, b); CREATE TABLE t2(x, y); INSERT INTO t1 VALUES(1, 2); INSERT INTO t1 VALUES(3, 4); INSERT INTO t2 VALUES('a', 'b'); INSERT INTO t2 VALUES('c', 'd'); INSERT INTO t2 VALUES('e', 'f'); } proc do_zexplain_test {v2 tn sql res} { db eval $sql set stmt [db version -last-stmt-ptr] set idx 0 set ret [list] set cmd sqlite3_stmt_scanstatus set f [list] if {$v2} { lappend f complex } while {1} { set r [sqlite3_stmt_scanstatus -flags $f $stmt $idx] if {[llength $r]==0} break lappend ret [dict get $r zExplain] incr idx } uplevel [list do_test $tn [list set {} $ret] [list {*}$res]] } proc get_cycles {stmt} { set r [sqlite3_stmt_scanstatus $stmt -1] dict get $r nCycle } proc foreach_scan {varname stmt body} { upvar $varname var for {set ii 0} {1} {incr ii} { set r [sqlite3_stmt_scanstatus -flags complex $stmt $ii] if {[llength $r]==0} break array set var $r uplevel $body } } proc get_eqp_graph {stmt iPar nIndent} { set res "" foreach_scan A $stmt { if {$A(iParentId)==$iPar} { set txt $A(zExplain) if {$A(nCycle)>=0} { append txt " (nCycle=$A(nCycle))" } append res "[string repeat - $nIndent]$txt\n" append res [get_eqp_graph $stmt $A(iSelectId) [expr $nIndent+2]] } } set res } proc get_graph {stmt} { set nCycle [get_cycles $stmt] set res "QUERY (nCycle=$nCycle)\n" append res [get_eqp_graph $stmt 0 2] } proc do_graph_test {tn sql res} { db eval $sql set stmt [db version -last-stmt-ptr] set graph [string trim [get_graph $stmt]] set graph [regsub -all {nCycle=[0-9]+} $graph nCycle=nnn] uplevel [list do_test $tn [list set {} $graph] [string trim $res]] } proc puts_graph {sql} { db eval $sql set stmt [db version -last-stmt-ptr] puts [string trim [get_graph $stmt]] } do_zexplain_test 0 1.1 { SELECT (SELECT a FROM t1 WHERE b=x) FROM t2 WHERE y=2 } { {SCAN t2} {SCAN t1} } do_zexplain_test 1 1.2 { SELECT (SELECT a FROM t1 WHERE b=x) FROM t2 WHERE y=2 } { {SCAN t2} {CORRELATED SCALAR SUBQUERY 1} {SCAN t1} } do_graph_test 1.3 { SELECT (SELECT a FROM t1 WHERE b=x) FROM t2 WHERE y=2 } { QUERY (nCycle=nnn) --SCAN t2 (nCycle=nnn) --CORRELATED SCALAR SUBQUERY 1 (nCycle=nnn) ----SCAN t1 (nCycle=nnn) } do_graph_test 1.4 { WITH v2(x,y) AS MATERIALIZED ( SELECT x,y FROM t2 ) SELECT * FROM t1, v2 ORDER BY y; } { QUERY (nCycle=nnn) --MATERIALIZE v2 (nCycle=nnn) ----SCAN t2 (nCycle=nnn) --SCAN v2 (nCycle=nnn) --SCAN t1 (nCycle=nnn) --USE TEMP B-TREE FOR ORDER BY (nCycle=nnn) } #------------------------------------------------------------------------- reset_db do_execsql_test 2.0 { CREATE VIRTUAL TABLE ft USING fts5(a); INSERT INTO ft VALUES('abc'); INSERT INTO ft VALUES('def'); INSERT INTO ft VALUES('ghi'); } do_graph_test 2.1 { SELECT * FROM ft('def') } { QUERY (nCycle=nnn) --SCAN ft VIRTUAL TABLE INDEX 0:M1 (nCycle=nnn) } #------------------------------------------------------------------------- reset_db do_execsql_test 3.0 { CREATE TABLE x1(a, b); CREATE TABLE x2(c, d); WITH s(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000) INSERT INTO x1 SELECT i, i FROM s; INSERT INTO x2 SELECT a, b FROM x1; } do_graph_test 2.1 { SELECT * FROM x1, x2 WHERE c=+a; } { QUERY (nCycle=nnn) --SCAN x1 (nCycle=nnn) --CREATE AUTOMATIC INDEX ON x2(c, d) (nCycle=nnn) --SEARCH x2 USING AUTOMATIC COVERING INDEX (c=?) (nCycle=nnn) } #------------------------------------------------------------------------- reset_db do_execsql_test 4.0 { CREATE TABLE rt1 (id INTEGER PRIMARY KEY, x1, x2); CREATE TABLE rt2 (id, x1, x2); } do_graph_test 4.1 { SELECT * FROM rt1, rt2 WHERE rt1.id%2 AND rt2.x1=rt1.x1; } { QUERY (nCycle=nnn) --SCAN rt1 (nCycle=nnn) --CREATE AUTOMATIC INDEX ON rt2(x1, id, x2) (nCycle=nnn) --SEARCH rt2 USING AUTOMATIC COVERING INDEX (x1=?) (nCycle=nnn) } do_graph_test 4.2 { SELECT rt2.id FROM rt1, rt2 WHERE rt1.id%2 AND rt2.x1=rt1.x1; } { QUERY (nCycle=nnn) --SCAN rt1 (nCycle=nnn) --CREATE AUTOMATIC INDEX ON rt2(x1, id) (nCycle=nnn) --SEARCH rt2 USING AUTOMATIC COVERING INDEX (x1=?) (nCycle=nnn) } do_graph_test 4.3 { SELECT rt2.id FROM rt1, rt2 WHERE rt1.id%2 AND (rt2.x1+1)=(rt1.x1+1); } { QUERY (nCycle=nnn) --SCAN rt1 (nCycle=nnn) --SCAN rt2 (nCycle=nnn) } do_graph_test 4.4 { SELECT rt2.id FROM rt1, rt2 WHERE rt1.id%2 AND rt2.x1=(rt1.x1+1) AND rt2.id>5; } { QUERY (nCycle=nnn) --SCAN rt1 (nCycle=nnn) --CREATE AUTOMATIC INDEX ON rt2(x1, id) WHERE <expr> (nCycle=nnn) --SEARCH rt2 USING AUTOMATIC PARTIAL COVERING INDEX (x1=?) (nCycle=nnn) } do_graph_test 4.5 { SELECT v1.cnt FROM rt1, ( SELECT count(*) AS cnt, rt2.x1 AS x1 FROM rt2 GROUP BY x1 ) AS v1 WHERE rt1.x1=v1.x1 } { QUERY (nCycle=nnn) --CO-ROUTINE v1 ----SCAN rt2 (nCycle=nnn) ----USE TEMP B-TREE FOR GROUP BY --SCAN rt1 (nCycle=nnn) --CREATE AUTOMATIC INDEX ON v1(x1, cnt) (nCycle=nnn) --SEARCH v1 USING AUTOMATIC COVERING INDEX (x1=?) (nCycle=nnn) } finish_test |
Changes to test/sessionfuzz.c.
︙ | ︙ | |||
695 696 697 698 699 700 701 | " sessionfuzz run SQLAR ... -- Run against all files in the SQL Archive\n" ; #include <stdio.h> #include <string.h> #include <assert.h> #ifndef OMIT_ZLIB | | | 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 | " sessionfuzz run SQLAR ... -- Run against all files in the SQL Archive\n" ; #include <stdio.h> #include <string.h> #include <assert.h> #ifndef OMIT_ZLIB #include <zlib.h> #endif /* ** Implementation of the "sqlar_uncompress(X,SZ)" SQL function ** ** Parameter SZ is interpreted as an integer. If it is less than or ** equal to zero, then this function returns a copy of X. Or, if |
︙ | ︙ |
Changes to test/shell1.test.
︙ | ︙ | |||
1252 1253 1254 1255 1256 1257 1258 1259 1260 | .mode csv --x select 2,1; select 3,4; } } {0 {1,2 2,1 3,4}} finish_test | > > > > > > > > > > > > | 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 | .mode csv --x select 2,1; select 3,4; } } {0 {1,2 2,1 3,4}} #---------------------------------------------------------------------------- # Test cases shell1-10.*: Test that certain static extensions are there. # do_test shell1-10.1 { catchcmd :memory: { .mode list .header off select base64(base64(cast('digity-doo' as blob))), base85(base85(cast('digity-doo' as blob))); } } {0 digity-doo|digity-doo} finish_test |
Changes to test/shell4.test.
︙ | ︙ | |||
120 121 122 123 124 125 126 127 128 129 130 131 132 133 | } {/^0 {PRAGMA.*}$/} do_test shell4-2.4 { catchcmd ":memory:" ".trace stdout\nCREATE TABLE t1(x);SELECT * FROM t1;" } {0 {CREATE TABLE t1(x); SELECT * FROM t1;}} do_test shell4-2.5 { catchcmd ":memory:" "CREATE TABLE t1(x);\n.trace stdout\nSELECT * FROM t1;" } {0 {SELECT * FROM t1;}} } do_test shell4-3.1 { set fd [open t1.txt wb] puts $fd "SELECT 'squirrel';" close $fd | > > > > > > | 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | } {/^0 {PRAGMA.*}$/} do_test shell4-2.4 { catchcmd ":memory:" ".trace stdout\nCREATE TABLE t1(x);SELECT * FROM t1;" } {0 {CREATE TABLE t1(x); SELECT * FROM t1;}} do_test shell4-2.5 { catchcmd ":memory:" "CREATE TABLE t1(x);\n.trace stdout\nSELECT * FROM t1;" } {0 {SELECT * FROM t1;}} do_test shell4-2.6 { catchcmd ":memory:" { CREATE TABLE t1(x); .trace --stmt stdout SELECT * FROM t1;} } {0 {SELECT * FROM t1;}} } do_test shell4-3.1 { set fd [open t1.txt wb] puts $fd "SELECT 'squirrel';" close $fd |
︙ | ︙ |
Changes to test/sort.test.
︙ | ︙ | |||
591 592 593 594 595 596 597 598 | } {1 {UNIQUE constraint failed: t1.b, t1.a, t1.c}} reset_db do_execsql_test 17.1 { SELECT * FROM sqlite_master ORDER BY sql; } {} finish_test | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 | } {1 {UNIQUE constraint failed: t1.b, t1.a, t1.c}} reset_db do_execsql_test 17.1 { SELECT * FROM sqlite_master ORDER BY sql; } {} # 2022-12-03 Ticket e8b674241947eb3b # Improve estimates for the cost of sorting relative # to the cost of doing an index lookup, so as to get # a better query plan. See the ticket for a deetailed # example. # reset_db do_execsql_test 18.1 { CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<50) -- increase to 5000 for actual test data ----^^ INSERT INTO t1(a,b,c) SELECT x, random()%5000, random()%5000 FROM c; CREATE TABLE t2(d,e,f); WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<500) -- increase to 50000 for actual test data -----^^^ INSERT INTO t2(d,e,f) SELECT NULLIF(0, random()%2), random()%5000, random()%5000 FROM c; ANALYZE; UPDATE sqlite_stat1 SET stat='50000' WHERE tbl='t2'; UPDATE sqlite_stat1 SET stat='5000' WHERE tbl='t1'; ANALYZE sqlite_schema; } {} do_execsql_test 18.2 { EXPLAIN QUERY PLAN SELECT a FROM t1 JOIN t2 WHERE a IN (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20) AND a=CASE WHEN d IS NOT NULL THEN e ELSE f END ORDER BY a; } {/.*SCAN t2.*SEARCH t1.*/} # ^^^^^^^--^^^^^^^^^--- t2 should be the outer loop. finish_test |
Added test/tkt-99378177930f87bd.test.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | # 2022-11-23 # # The author disclaims copyright to this source code. In place of # a legal notice, here is a blessing: # # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** # This file implements regression tests for SQLite library. # # This file implements tests to verify that the enhancement # request documented by ticket 99378177930f87bd is working. # # The enhancement is that if an aggregate query with a GROUP BY clause # uses subexpressions in the arguments to aggregate functions that are # also columns of an index, then the values are pulled from the index # rather than being recomputed. This has the potential to make some # indexed queries works as if the index were covering. # set testdir [file dirname $argv0] source $testdir/tester.tcl do_execsql_test tkt-99378-100 { CREATE TABLE t1(a INT, b TEXT, c INT, d INT); INSERT INTO t1(a,b,c,d) VALUES (1, '{"x":1}', 12, 3), (1, '{"x":2}', 4, 5), (1, '{"x":1}', 6, 11), (2, '{"x":1}', 22, 3), (2, '{"x":2}', 4, 5), (3, '{"x":1}', 6, 7); CREATE INDEX t1x ON t1(d, a, b->>'x', c); } {} do_execsql_test tkt-99378-110 { SELECT a, SUM(1) AS t1, SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, SUM(c) AS t3, SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 FROM t1 WHERE d BETWEEN 0 and 10 GROUP BY a; } { 1 2 1 16 12 2 2 1 26 22 3 1 1 6 6 } # The proof that the index on the expression is being used is in the # fact that the byte code contains no "Function" opcodes. In other words, # the ->> operator (which is implemented by a function) is never invoked. # Instead, the b->>'x' value is pulled out of the index. # do_execsql_test tkt-99378-120 { EXPLAIN SELECT a, SUM(1) AS t1, SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, SUM(c) AS t3, SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 FROM t1 WHERE d BETWEEN 0 and 10 GROUP BY a; } {~/Function/} do_execsql_test tkt-99378-130 { SELECT a, SUM(1) AS t1, SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, SUM(c) AS t3, SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 FROM t1 WHERE d BETWEEN 0 and 10 GROUP BY +a; } { 1 2 1 16 12 2 2 1 26 22 3 1 1 6 6 } do_execsql_test tkt-99378-140 { EXPLAIN SELECT a, SUM(1) AS t1, SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, SUM(c) AS t3, SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 FROM t1 WHERE d BETWEEN 0 and 10 GROUP BY +a; } {~/Function/} do_execsql_test tkt-99378-200 { DROP INDEX t1x; CREATE INDEX t1x ON t1(a, d, b->>'x', c); } do_execsql_test tkt-99378-210 { SELECT a, SUM(1) AS t1, SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, SUM(c) AS t3, SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 FROM t1 WHERE d BETWEEN 0 and 10 GROUP BY a; } { 1 2 1 16 12 2 2 1 26 22 3 1 1 6 6 } do_execsql_test tkt-99378-220 { EXPLAIN SELECT a, SUM(1) AS t1, SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, SUM(c) AS t3, SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 FROM t1 WHERE d BETWEEN 0 and 10 GROUP BY a; } {~/Function/} do_execsql_test tkt-99378-230 { SELECT a, SUM(1) AS t1, SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, SUM(c) AS t3, SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 FROM t1 WHERE d BETWEEN 0 and 10 GROUP BY a; } { 1 2 1 16 12 2 2 1 26 22 3 1 1 6 6 } do_execsql_test tkt-99378-240 { EXPLAIN SELECT a, SUM(1) AS t1, SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2, SUM(c) AS t3, SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4 FROM t1 WHERE d BETWEEN 0 and 10 GROUP BY a; } {~/Function/} # 2022-12-20 dbsqlfuzz a644e70d7683a7ca59c71861a153c1dccf8850b9 # do_execsql_test tkt-99378-300 { DROP TABLE IF EXISTS t1; CREATE TABLE t1(a INT); CREATE INDEX i1 ON t1(a,a=a); INSERT INTO t1 VALUES(1),(2),(3),(4); SELECT * FROM t1 NATURAL JOIN t1 WHERE a==1 OR ( (SELECT avg( (SELECT sum((SELECT 1 FROM t1 NATURAL RIGHT JOIN t1 WHERE a=a)))) AS xyz ) AND a==2 ); } {1 2} do_execsql_test tkt-99378-310 { DROP INDEX i1; SELECT * FROM t1 NATURAL JOIN t1 WHERE a==1 OR ( (SELECT avg( (SELECT sum((SELECT 1 FROM t1 NATURAL RIGHT JOIN t1 WHERE a=a)))) AS xyz ) AND a==2 ); } {1 2} finish_test |
Changes to test/unionall.test.
︙ | ︙ | |||
404 405 406 407 408 409 410 | INSERT INTO t1_c VALUES(3,'three'); INSERT INTO t1_c VALUES(6,'six'); CREATE VIEW v0(c0) AS SELECT CAST(t0.c0 AS INTEGER) FROM t0; CREATE VIEW t1 AS SELECT a, b FROM t1_a UNION ALL SELECT c, c FROM t1_b UNION ALL SELECT e, f FROM t1_c; | > > | | | | | | | | 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 | INSERT INTO t1_c VALUES(3,'three'); INSERT INTO t1_c VALUES(6,'six'); CREATE VIEW v0(c0) AS SELECT CAST(t0.c0 AS INTEGER) FROM t0; CREATE VIEW t1 AS SELECT a, b FROM t1_a UNION ALL SELECT c, c FROM t1_b UNION ALL SELECT e, f FROM t1_c; SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1; } {1 one 0 {} 4 four 0 {} 2 2 0 {} 5 5 0 {} 3 three 0 {} 6 six 0 {}} optimization_control db all 1 do_execsql_test 8.2 { SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b=2; } {2 2 0 {}} do_execsql_test 8.3 { SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b=2.0; } {2 2 0 {}} do_execsql_test 8.4 { SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b='2'; } {} optimization_control db query-flattener,push-down 0 do_execsql_test 8.5 { SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b=2; } {2 2 0 {}} do_execsql_test 8.6 { SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b=2.0; } {2 2 0 {}} do_execsql_test 8.7 { SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b='2'; } {} optimization_control db all 0 do_execsql_test 8.8 { SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b=2; } {2 2 0 {}} do_execsql_test 8.9 { SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b=2.0; } {2 2 0 {}} do_execsql_test 8.10 { SELECT * FROM (SELECT t1.a, t1.b, t0.c0 AS c, v0.c0 AS d FROM t0 LEFT JOIN v0 ON v0.c0>'0',t1) WHERE b='2'; } {} finish_test |
Changes to test/view.test.
︙ | ︙ | |||
119 120 121 122 123 124 125 | CREATE VIEW v9a AS SELECT x FROM t9; CREATE VIEW v9b AS SELECT * FROM t9; CREATE VIEW v9c(x) AS SELECT x FROM t9; CREATE VIEW v9d(x) AS SELECT * FROM t9; } {} do_execsql_test view-1.11 { PRAGMA table_info(v9a); | | | | | | 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | CREATE VIEW v9a AS SELECT x FROM t9; CREATE VIEW v9b AS SELECT * FROM t9; CREATE VIEW v9c(x) AS SELECT x FROM t9; CREATE VIEW v9d(x) AS SELECT * FROM t9; } {} do_execsql_test view-1.11 { PRAGMA table_info(v9a); } {0 x INT 0 {} 0} do_execsql_test view-1.12 { PRAGMA table_info(v9b); } {0 x INT 0 {} 0} do_execsql_test view-1.13 { PRAGMA table_info(v9c); } {0 x INT 0 {} 0} do_execsql_test view-1.14 { PRAGMA table_info(v9d); } {0 x INT 0 {} 0} do_test view-2.1 { execsql { CREATE VIEW v2 AS SELECT * FROM t1 WHERE a>5 }; # No semicolon execsql2 { SELECT * FROM v2; |
︙ | ︙ | |||
771 772 773 774 775 776 777 | CREATE VIEW IF NOT EXISTS IF AS SELECT null; } {1 {malformed database schema (IF) - near "AS": syntax error}} do_catchsql_test view-29.1 { CREATE TABLE t2(c,d,e); SELECT name FROM sqlite_schema ORDER BY name; } {0 {t1 t2}} | > > | > > > > > > > > > > > > > > > > > > > > > > > | 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 | CREATE VIEW IF NOT EXISTS IF AS SELECT null; } {1 {malformed database schema (IF) - near "AS": syntax error}} do_catchsql_test view-29.1 { CREATE TABLE t2(c,d,e); SELECT name FROM sqlite_schema ORDER BY name; } {0 {t1 t2}} #------------------------------------------------------------------------- # 2022-12-11. https://sqlite.org/src/info/679ed6a2 # # 2022-12-14 change: If the AS SELECT of a VIEW is a compound where # the datatypes on each arm of the compound are different, then the # datatype of the overall column is BLOB (ANY). # reset_db do_execsql_test view-30.0 { CREATE TABLE t0(a INT, b TEXT); INSERT INTO t0 VALUES(1,'one'); CREATE VIEW t1 AS SELECT a, b FROM t0 UNION ALL SELECT 2, 2; CREATE VIEW t2(a,b) AS SELECT a, b FROM t0 UNION ALL SELECT 2, 2; } ifcapable schema_pragmas { do_execsql_test view-30.1 { PRAGMA table_info = t1; } { 0 a INT 0 {} 0 1 b BLOB 0 {} 0 } do_execsql_test view-30.2 { PRAGMA table_info = t2; } { 0 a INT 0 {} 0 1 b BLOB 0 {} 0 } } finish_test |
Changes to test/where.test.
︙ | ︙ | |||
541 542 543 544 545 546 547 548 549 550 551 552 553 554 | execsql { CREATE TABLE t3(a,b,c); CREATE INDEX t3a ON t3(a); CREATE INDEX t3bc ON t3(b,c); CREATE INDEX t3acb ON t3(a,c,b); INSERT INTO t3 SELECT w, 101-w, y FROM t1; SELECT count(*), sum(a), sum(b), sum(c) FROM t3; } } {100 5050 5050 348550} do_test where-6.2 { cksort { SELECT * FROM t3 ORDER BY a LIMIT 3 } } {1 100 4 2 99 9 3 98 16 nosort} | > | 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 | execsql { CREATE TABLE t3(a,b,c); CREATE INDEX t3a ON t3(a); CREATE INDEX t3bc ON t3(b,c); CREATE INDEX t3acb ON t3(a,c,b); INSERT INTO t3 SELECT w, 101-w, y FROM t1; SELECT count(*), sum(a), sum(b), sum(c) FROM t3; ANALYZE; } } {100 5050 5050 348550} do_test where-6.2 { cksort { SELECT * FROM t3 ORDER BY a LIMIT 3 } } {1 100 4 2 99 9 3 98 16 nosort} |
︙ | ︙ | |||
1615 1616 1617 1618 1619 1620 1621 | 1 1 15 999 19 5 } # 2022-12-07 Yong Heng [https://sqlite.org/forum/forumpost/dfe8084751] # | < | | | | | | | | | | | | | < | 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 | 1 1 15 999 19 5 } # 2022-12-07 Yong Heng [https://sqlite.org/forum/forumpost/dfe8084751] # do_execsql_test where-29.1 { SELECT DISTINCT 'xyz' FROM pragma_cache_size WHERE rowid OR abs(0) ORDER BY 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1; } {xyz} finish_test |
Changes to test/with3.test.
︙ | ︙ | |||
85 86 87 88 89 90 91 | } do_eqp_test 3.1.2 { WITH cnt(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM cnt LIMIT 1) SELECT * FROM cnt, y1 WHERE i=a } [string map {"\n " \n} { QUERY PLAN | | | | 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | } do_eqp_test 3.1.2 { WITH cnt(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM cnt LIMIT 1) SELECT * FROM cnt, y1 WHERE i=a } [string map {"\n " \n} { QUERY PLAN |--CO-ROUTINE cnt | |--SETUP | | `--SCAN CONSTANT ROW | `--RECURSIVE STEP | `--SCAN cnt |--SCAN cnt `--SEARCH y1 USING INDEX y1a (a=?) }] do_eqp_test 3.1.3 { WITH cnt(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM cnt LIMIT 1000000) SELECT * FROM cnt, y1 WHERE i=a } [string map {"\n " \n} { QUERY PLAN |--CO-ROUTINE cnt | |--SETUP | | `--SCAN CONSTANT ROW | `--RECURSIVE STEP | `--SCAN cnt |--SCAN y1 `--SEARCH cnt USING AUTOMATIC COVERING INDEX (i=?) }] |
︙ | ︙ | |||
121 122 123 124 125 126 127 | do_eqp_test 3.2.2 { WITH RECURSIVE c(w,id) AS (SELECT 0, (SELECT pk FROM w2 LIMIT 1) UNION ALL SELECT c.w + 1, x FROM w1, c LIMIT 1) SELECT * FROM c, w2, w1 WHERE c.id=w2.pk AND c.id=w1.pk; } { QUERY PLAN | | | 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | do_eqp_test 3.2.2 { WITH RECURSIVE c(w,id) AS (SELECT 0, (SELECT pk FROM w2 LIMIT 1) UNION ALL SELECT c.w + 1, x FROM w1, c LIMIT 1) SELECT * FROM c, w2, w1 WHERE c.id=w2.pk AND c.id=w1.pk; } { QUERY PLAN |--CO-ROUTINE c | |--SETUP | | |--SCAN CONSTANT ROW | | `--SCALAR SUBQUERY xxxxxx | | `--SCAN w2 | `--RECURSIVE STEP | |--SCAN c | `--SCAN w1 |
︙ | ︙ |
Changes to test/with6.test.
︙ | ︙ | |||
83 84 85 86 87 88 89 | WITH c(x) AS NOT MATERIALIZED (VALUES(0),(1)) SELECT c1.x||c2.x||c3.x FROM (SELECT x FROM c LIMIT 5) AS c1, (SELECT x FROM c LIMIT 5) AS c2, (SELECT x FROM c LIMIT 5) AS c3; } { QUERY PLAN | | | 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | WITH c(x) AS NOT MATERIALIZED (VALUES(0),(1)) SELECT c1.x||c2.x||c3.x FROM (SELECT x FROM c LIMIT 5) AS c1, (SELECT x FROM c LIMIT 5) AS c2, (SELECT x FROM c LIMIT 5) AS c3; } { QUERY PLAN |--CO-ROUTINE c1 | |--CO-ROUTINE c | | `--SCAN 2 CONSTANT ROWS | `--SCAN c |--MATERIALIZE c2 | |--CO-ROUTINE c | | `--SCAN 2 CONSTANT ROWS | `--SCAN c |
︙ | ︙ | |||
119 120 121 122 123 124 125 | WITH c(x) AS MATERIALIZED (VALUES(0),(1)) SELECT c1.x||c2.x||c3.x FROM (SELECT x FROM c LIMIT 5) AS c1, (SELECT x FROM c LIMIT 6) AS c2, (SELECT x FROM c LIMIT 7) AS c3; } { QUERY PLAN | | | 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | WITH c(x) AS MATERIALIZED (VALUES(0),(1)) SELECT c1.x||c2.x||c3.x FROM (SELECT x FROM c LIMIT 5) AS c1, (SELECT x FROM c LIMIT 6) AS c2, (SELECT x FROM c LIMIT 7) AS c3; } { QUERY PLAN |--CO-ROUTINE c1 | |--MATERIALIZE c | | `--SCAN 2 CONSTANT ROWS | `--SCAN c |--MATERIALIZE c2 | `--SCAN c |--MATERIALIZE c3 | `--SCAN c |
︙ | ︙ | |||
147 148 149 150 151 152 153 | WITH c(x) AS (VALUES(0),(1)) SELECT c1.x||c2.x||c3.x FROM (SELECT x FROM c LIMIT 5) AS c1, (SELECT x FROM c LIMIT 6) AS c2, (SELECT x FROM c LIMIT 7) AS c3; } { QUERY PLAN | | | 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | WITH c(x) AS (VALUES(0),(1)) SELECT c1.x||c2.x||c3.x FROM (SELECT x FROM c LIMIT 5) AS c1, (SELECT x FROM c LIMIT 6) AS c2, (SELECT x FROM c LIMIT 7) AS c3; } { QUERY PLAN |--CO-ROUTINE c1 | |--MATERIALIZE c | | `--SCAN 2 CONSTANT ROWS | `--SCAN c |--MATERIALIZE c2 | `--SCAN c |--MATERIALIZE c3 | `--SCAN c |
︙ | ︙ | |||
224 225 226 227 228 229 230 | (SELECT z FROM c LIMIT 5) AS c2; SELECT y FROM t2 ORDER BY y; } {40404 40405 40406 40504 40505 40506 40604 40605 40606} do_eqp_test 211 { SELECT y FROM t2 ORDER BY y; } { QUERY PLAN | | | 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 | (SELECT z FROM c LIMIT 5) AS c2; SELECT y FROM t2 ORDER BY y; } {40404 40405 40406 40504 40505 40506 40604 40605 40606} do_eqp_test 211 { SELECT y FROM t2 ORDER BY y; } { QUERY PLAN |--CO-ROUTINE c1 | |--CO-ROUTINE c | | `--SCAN 3 CONSTANT ROWS | `--SCAN c |--MATERIALIZE c2 | |--CO-ROUTINE c | | `--SCAN 3 CONSTANT ROWS | `--SCAN c |
︙ | ︙ |
Changes to test/zipfile.test.
︙ | ︙ | |||
849 850 851 852 853 854 855 | VALUES(9223372036854775807), (-9223372036854775808), (9223372036854775806), (-9223372036854775807) ) SELECT DISTINCT typeof(zipfile(0,0,x,0)) FROM vlist; } {blob} | | < < < < < < < < | 849 850 851 852 853 854 855 856 857 858 | VALUES(9223372036854775807), (-9223372036854775808), (9223372036854775806), (-9223372036854775807) ) SELECT DISTINCT typeof(zipfile(0,0,x,0)) FROM vlist; } {blob} finish_test |
Changes to tool/mkopcodeh.tcl.
︙ | ︙ | |||
82 83 84 85 86 87 88 89 90 91 92 93 94 95 | set group($name) 0 set jump($name) 0 set in1($name) 0 set in2($name) 0 set in3($name) 0 set out2($name) 0 set out3($name) 0 for {set i 3} {$i<[llength $line]-1} {incr i} { switch [string trim [lindex $line $i] ,] { same { incr i if {[lindex $line $i]=="as"} { incr i set sym [string trim [lindex $line $i] ,] | > | 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | set group($name) 0 set jump($name) 0 set in1($name) 0 set in2($name) 0 set in3($name) 0 set out2($name) 0 set out3($name) 0 set ncycle($name) 0 for {set i 3} {$i<[llength $line]-1} {incr i} { switch [string trim [lindex $line $i] ,] { same { incr i if {[lindex $line $i]=="as"} { incr i set sym [string trim [lindex $line $i] ,] |
︙ | ︙ | |||
103 104 105 106 107 108 109 110 111 112 113 114 115 116 | group {set group($name) 1} jump {set jump($name) 1} in1 {set in1($name) 1} in2 {set in2($name) 1} in3 {set in3($name) 1} out2 {set out2($name) 1} out3 {set out3($name) 1} } } if {$group($name)} { set newGroup 0 if {[info exists groups($nGroup)]} { if {$prevName=="" || !$group($prevName)} { set newGroup 1 | > | 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | group {set group($name) 1} jump {set jump($name) 1} in1 {set in1($name) 1} in2 {set in2($name) 1} in3 {set in3($name) 1} out2 {set out2($name) 1} out3 {set out3($name) 1} ncycle {set ncycle($name) 1} } } if {$group($name)} { set newGroup 0 if {[info exists groups($nGroup)]} { if {$prevName=="" || !$group($prevName)} { set newGroup 1 |
︙ | ︙ | |||
136 137 138 139 140 141 142 143 144 145 146 147 148 149 | foreach name {OP_Noop OP_Explain OP_Abortable} { set jump($name) 0 set in1($name) 0 set in2($name) 0 set in3($name) 0 set out2($name) 0 set out3($name) 0 set op($name) -1 set order($nOp) $name incr nOp } # The following are the opcodes that receive special processing in the # resolveP2Values() routine. Update this list whenever new cases are | > | 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | foreach name {OP_Noop OP_Explain OP_Abortable} { set jump($name) 0 set in1($name) 0 set in2($name) 0 set in3($name) 0 set out2($name) 0 set out3($name) 0 set ncycle($name) 0 set op($name) -1 set order($nOp) $name incr nOp } # The following are the opcodes that receive special processing in the # resolveP2Values() routine. Update this list whenever new cases are |
︙ | ︙ | |||
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 | if {[string match OP_NotUsed* $name]==0} { if {$jump($name)} {incr x 1} if {$in1($name)} {incr x 2} if {$in2($name)} {incr x 4} if {$in3($name)} {incr x 8} if {$out2($name)} {incr x 16} if {$out3($name)} {incr x 32} } set bv($i) $x } puts "" puts "/* Properties such as \"out2\" or \"jump\" that are specified in" puts "** comments following the \"case\" for each opcode in the vdbe.c" puts "** are encoded into bitvectors as follows:" puts "*/" puts "#define OPFLG_JUMP 0x01 /* jump: P2 holds jmp target */" puts "#define OPFLG_IN1 0x02 /* in1: P1 is an input */" puts "#define OPFLG_IN2 0x04 /* in2: P2 is an input */" puts "#define OPFLG_IN3 0x08 /* in3: P3 is an input */" puts "#define OPFLG_OUT2 0x10 /* out2: P2 is an output */" puts "#define OPFLG_OUT3 0x20 /* out3: P3 is an output */" puts "#define OPFLG_INITIALIZER \173\\" for {set i 0} {$i<=$max} {incr i} { if {$i%8==0} { puts -nonewline [format "/* %3d */" $i] } puts -nonewline [format " 0x%02x," $bv($i)] if {$i%8==7} { | > > | 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 | if {[string match OP_NotUsed* $name]==0} { if {$jump($name)} {incr x 1} if {$in1($name)} {incr x 2} if {$in2($name)} {incr x 4} if {$in3($name)} {incr x 8} if {$out2($name)} {incr x 16} if {$out3($name)} {incr x 32} if {$ncycle($name)} {incr x 64} } set bv($i) $x } puts "" puts "/* Properties such as \"out2\" or \"jump\" that are specified in" puts "** comments following the \"case\" for each opcode in the vdbe.c" puts "** are encoded into bitvectors as follows:" puts "*/" puts "#define OPFLG_JUMP 0x01 /* jump: P2 holds jmp target */" puts "#define OPFLG_IN1 0x02 /* in1: P1 is an input */" puts "#define OPFLG_IN2 0x04 /* in2: P2 is an input */" puts "#define OPFLG_IN3 0x08 /* in3: P3 is an input */" puts "#define OPFLG_OUT2 0x10 /* out2: P2 is an output */" puts "#define OPFLG_OUT3 0x20 /* out3: P3 is an output */" puts "#define OPFLG_NCYCLE 0x40 /* ncycle:Cycles count against P1 */" puts "#define OPFLG_INITIALIZER \173\\" for {set i 0} {$i<=$max} {incr i} { if {$i%8==0} { puts -nonewline [format "/* %3d */" $i] } puts -nonewline [format " 0x%02x," $bv($i)] if {$i%8==7} { |
︙ | ︙ |
Changes to tool/mkshellc.tcl.
︙ | ︙ | |||
51 52 53 54 55 56 57 | set cfile [lindex $lx 1] puts $out "/************************* Begin $cfile ******************/" # puts $out "#line 1 \"$cfile\"" set in2 [open $topdir/src/$cfile] fconfigure $in2 -translation binary while {![eof $in2]} { set lx [omit_redundant_typedefs [gets $in2]] | | | 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | set cfile [lindex $lx 1] puts $out "/************************* Begin $cfile ******************/" # puts $out "#line 1 \"$cfile\"" set in2 [open $topdir/src/$cfile] fconfigure $in2 -translation binary while {![eof $in2]} { set lx [omit_redundant_typedefs [gets $in2]] if {[regexp {^# *include "sqlite} $lx]} { set lx "/* $lx */" } if {[regexp {^# *include "test_windirent.h"} $lx]} { set lx "/* $lx */" } set lx [string map [list __declspec(dllexport) {}] $lx] puts $out $lx |
︙ | ︙ |
Changes to tool/mksqlite3c.tcl.
︙ | ︙ | |||
36 37 38 39 40 41 42 43 44 45 46 | # from in this file. The version number is needed to generate the header # comment of the amalgamation. # set addstatic 1 set linemacros 0 set useapicall 0 set srcdir tsrc for {set i 0} {$i<[llength $argv]} {incr i} { set x [lindex $argv $i] | > > > | | 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | # from in this file. The version number is needed to generate the header # comment of the amalgamation. # set addstatic 1 set linemacros 0 set useapicall 0 set enable_recover 0 set srcdir tsrc for {set i 0} {$i<[llength $argv]} {incr i} { set x [lindex $argv $i] if {[regexp {^-?-enable-recover$} $x]} { set enable_recover 1 } elseif {[regexp {^-?-nostatic$} $x]} { set addstatic 0 } elseif {[regexp {^-?-linemacros(?:=([01]))?$} $x ma ulm]} { if {$ulm == ""} {set ulm 1} set linemacros $ulm } elseif {[regexp {^-?-useapicall$} $x]} { set useapicall 1 } elseif {[regexp {^-?-srcdir$} $x]} { |
︙ | ︙ | |||
74 75 76 77 78 79 80 | regexp {#define\s+SQLITE_VERSION\s+"(.*)"} $line all VERSION } close $in # Open the output file and write a header comment at the beginning # of the file. # | > > | | 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | regexp {#define\s+SQLITE_VERSION\s+"(.*)"} $line all VERSION } close $in # Open the output file and write a header comment at the beginning # of the file. # set fname sqlite3.c if {$enable_recover} { set fname sqlite3r.c } set out [open $fname w] # Force the output to use unix line endings, even on Windows. fconfigure $out -translation lf set today [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S UTC" -gmt 1] puts $out [subst \ {/****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite ** version $VERSION. By combining all the individual C code files into this |
︙ | ︙ | |||
158 159 160 161 162 163 164 165 166 167 168 169 170 171 | sqliteInt.h sqliteLimit.h vdbe.h vdbeInt.h vxworks.h wal.h whereInt.h } { set available_hdr($hdr) 1 } set available_hdr(sqliteInt.h) 0 set available_hdr(os_common.h) 0 set available_hdr(sqlite3session.h) 0 | > | 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | sqliteInt.h sqliteLimit.h vdbe.h vdbeInt.h vxworks.h wal.h whereInt.h sqlite3recover.h } { set available_hdr($hdr) 1 } set available_hdr(sqliteInt.h) 0 set available_hdr(os_common.h) 0 set available_hdr(sqlite3session.h) 0 |
︙ | ︙ | |||
321 322 323 324 325 326 327 | } # Process the source files. Process files containing commonly # used subroutines first in order to help the compiler find # inlining opportunities. # | | | 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 | } # Process the source files. Process files containing commonly # used subroutines first in order to help the compiler find # inlining opportunities. # set flist { sqliteInt.h os_common.h ctime.c global.c status.c date.c |
︙ | ︙ | |||
437 438 439 440 441 442 443 | fts3_icu.c sqlite3rbu.c dbstat.c dbpage.c sqlite3session.c fts5.c stmt.c | > | > > > | 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 | fts3_icu.c sqlite3rbu.c dbstat.c dbpage.c sqlite3session.c fts5.c stmt.c } if {$enable_recover} { lappend flist sqlite3recover.c dbdata.c } foreach file $flist { copy_file $srcdir/$file } puts $out \ "/* Return the source-id for this library */ SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; }" puts $out \ "/************************** End of sqlite3.c ******************************/" close $out |
Changes to tool/mksqlite3h.tcl.
︙ | ︙ | |||
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | # Get the source tree root directory from the command-line # set TOP [lindex $argv 0] # Enable use of SQLITE_APICALL macros at the right points? # set useapicall 0 if {[lsearch -regexp [lrange $argv 1 end] {^-+useapicall}] != -1} { set useapicall 1 } # Get the SQLite version number (ex: 3.6.18) from the $TOP/VERSION file. # set in [open $TOP/VERSION] set zVersion [string trim [read $in]] close $in | > > > > > > > | 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | # Get the source tree root directory from the command-line # set TOP [lindex $argv 0] # Enable use of SQLITE_APICALL macros at the right points? # set useapicall 0 # Include sqlite3recover.h? # set enable_recover 0 if {[lsearch -regexp [lrange $argv 1 end] {^-+useapicall}] != -1} { set useapicall 1 } if {[lsearch -regexp [lrange $argv 1 end] {^-+enable-recover}] != -1} { set enable_recover 1 } # Get the SQLite version number (ex: 3.6.18) from the $TOP/VERSION file. # set in [open $TOP/VERSION] set zVersion [string trim [read $in]] close $in |
︙ | ︙ | |||
80 81 82 83 84 85 86 87 88 89 90 91 92 93 | set filelist [subst { $TOP/src/sqlite.h.in $TOP/ext/rtree/sqlite3rtree.h $TOP/ext/session/sqlite3session.h $TOP/ext/fts5/fts5.h }] # These are the functions that accept a variable number of arguments. They # always need to use the "cdecl" calling convention even when another calling # convention (e.g. "stcall") is being used for the rest of the library. set cdecllist { sqlite3_config sqlite3_db_config | > > > | 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | set filelist [subst { $TOP/src/sqlite.h.in $TOP/ext/rtree/sqlite3rtree.h $TOP/ext/session/sqlite3session.h $TOP/ext/fts5/fts5.h }] if {$enable_recover} { lappend filelist "$TOP/ext/recover/sqlite3recover.h" } # These are the functions that accept a variable number of arguments. They # always need to use the "cdecl" calling convention even when another calling # convention (e.g. "stcall") is being used for the rest of the library. set cdecllist { sqlite3_config sqlite3_db_config |
︙ | ︙ |
Changes to tool/vdbe_profile.tcl.
︙ | ︙ | |||
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | } close $in foreach stmt $allstmt { puts "********************************************************************" puts [string trim $sql($stmt)] puts "Execution count: $cnt($stmt)" for {set i 0} {[info exists stat($stmt,$i)]} {incr i} { foreach {cx tx detail} $stat($stmt,$i) break if {$cx==0} { set ax 0 } else { set ax [expr {$tx/$cx}] } puts [format {%8d %12d %12d %4d %s} $cx $tx $ax $i $detail] } } puts "********************************************************************" puts "OPCODES:" foreach op [lsort [array names opcnt]] { set cx $opcnt($op) set tx $opcycle($op) if {$cx==0} { | > > > > > > | 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | } close $in foreach stmt $allstmt { puts "********************************************************************" puts [string trim $sql($stmt)] puts "Execution count: $cnt($stmt)" set tcx 0 set ttx 0 for {set i 0} {[info exists stat($stmt,$i)]} {incr i} { foreach {cx tx detail} $stat($stmt,$i) break if {$cx==0} { set ax 0 } else { set ax [expr {$tx/$cx}] } puts [format {%8d %12d %12d %4d %s} $cx $tx $ax $i $detail] incr tcx $cx incr ttx $tx } set tax [expr {$tcx>0?$ttx/$tcx:0}] puts [format {%8d %12d %12d TOTAL} $tcx $ttx $tax] } puts "********************************************************************" puts "OPCODES:" foreach op [lsort [array names opcnt]] { set cx $opcnt($op) set tx $opcycle($op) if {$cx==0} { |
︙ | ︙ |