SHA1 Hash: | 1c2d878d12ee5ffb9f36a01ac69ed693a7dd39dc |
---|---|
Date: | 2009-12-13 01:16:13 |
User: | btheado |
Comment: | Merge with trunk |
Timelines: | ancestors | descendants | both | sql-func |
Other Links: | files | ZIP archive | manifest |
- branch=sql-func inherited from [f41358e7ca]
- sym-sql-func inherited from [f41358e7ca]
Modified BUILD.txt from [334b80c575] to [4f7988767e].
@@ -2,16 +2,43 @@ But there is a lot of generated code, so you will probably want to use the Makefile. To do a complete build, just type: make -That should work out-of-the-box on Macs and Linux systems. If -you have trouble, or you want to do something fancy, just edit -the Makefile in the top-level folder. There are 5 things you -might want to change in the Makefile. All 5 things are well -commented. The complete Makefile is only a few dozen lines long. -Do not be intimidated. +That should work out-of-the-box on Macs and Linux systems. If you are +building on a Windows box, install MinGW as well as MinGW's make (or +MSYS). You can then type: + + make -f Makefile.w32 + +If you have trouble, or you want to do something fancy, just look at +top level makefile. There are 5 configuration options that are all well +commented. Instead of editing the Makefile, create a new file named +config.mak (for Macs and Linux systems) or config.w32 (for Windows) and +override any settings you wish there. + +Out of source builds? +-------------------------------------------------------------------------- + +An out of source build is pretty easy: + + 1. Make a new directory to do the builds in. + 2. Create a config.mak (or .w32 ... explained above) and add something + along the lines of: + + SRCDIR=../src + + 3. From that directory, type: + + Macs and Linux: + $ make -f ../Makefile + + Windows: + C:\fossil\build> make -f ../Makefile.w32 + +This will now keep all generates files seperate from the maintained +source code. -------------------------------------------------------------------------- Here are some notes on what is happening behind the scenes:
Modified Makefile from [c357142045] to [21a564c8f5].
@@ -43,9 +43,13 @@ #### Tcl shell for use in running the fossil testsuite. # TCLSH = tclsh + +#### Include a configuration file that can override any one of these settings. +# +-include config.mak # You should not need to change anything below this line ############################################################################### include $(SRCDIR)/main.mk
Modified Makefile.w32 from [900e354562] to [a8929670d9].
@@ -41,9 +41,13 @@ LIB = -lmingwex -lz -lws2_32 #### Tcl shell for use in running the fossil testsuite. # TCLSH = tclsh + +#### Include a configuration file that can override any one of these settings. +# +-include config.w32 # You should not need to change anything below this line ############################################################################### include $(SRCDIR)/main.mk
Modified src/add.c from [c094792c50] to [d5685d9fd3].
@@ -198,10 +198,11 @@ if( !db_exists( "SELECT 1 FROM vfile WHERE pathname=%Q AND NOT deleted", zPath) ){ fossil_fatal("not in the repository: %s", zName); }else{ db_multi_exec("UPDATE vfile SET deleted=1 WHERE pathname=%Q", zPath); + printf("DELETED %s\n", zPath); } blob_reset(&pathname); free(zName); } db_multi_exec("DELETE FROM vfile WHERE deleted AND rid=0");
Modified src/branch.c from [3ef4b504fa] to [80adbdb5e4].
@@ -132,11 +132,11 @@ md5sum_blob(&branch, &mcksum); blob_appendf(&branch, "Z %b\n", &mcksum); if( !noSign && clearsign(&branch, &branch) ){ Blob ans; blob_zero(&ans); - prompt_user("unable to sign manifest. continue [y/N]? ", &ans); + prompt_user("unable to sign manifest. continue (y/N)? ", &ans); if( blob_str(&ans)[0]!='y' ){ db_end_transaction(1); exit(1); } }
Modified src/checkin.c from [b32786b578] to [c1819242e8].
@@ -237,11 +237,11 @@ while( db_step(&q)==SQLITE_ROW ){ if( allFlag ){ unlink(db_column_text(&q, 0)); }else{ Blob ans; - char *prompt = mprintf("remove unmanaged file \"%s\" [y/N]? ", + char *prompt = mprintf("remove unmanaged file \"%s\" (y/N)? ", db_column_text(&q, 0)); blob_zero(&ans); prompt_user(prompt, &ans); if( blob_str(&ans)[0]=='y' ){ unlink(db_column_text(&q, 0)); @@ -257,22 +257,23 @@ ** the VISUAL or EDITOR environment variable. ** ** Store the final commit comment in pComment. pComment is assumed ** to be uninitialized - any prior content is overwritten. */ -static void prepare_commit_comment(Blob *pComment){ +static void prepare_commit_comment(Blob *pComment, char *zInit){ const char *zEditor; char *zCmd; char *zFile; Blob text, line; char *zComment; int i; - blob_set(&text, + blob_init(&text, zInit, -1); + blob_append(&text, "\n" "# Enter comments on this check-in. Lines beginning with # are ignored.\n" "# The check-in comment follows wiki formatting rules.\n" - "#\n" + "#\n", -1 ); if( g.markPrivate ){ blob_append(&text, "# PRIVATE BRANCH: This check-in will be private and will not sync to\n" "# repositories.\n" @@ -388,15 +389,22 @@ ** ** Usage: %fossil commit ?OPTIONS? ?FILE...? ** ** Create a new version containing all of the changes in the current ** checkout. You will be prompted to enter a check-in comment unless -** the "-m" option is used to specify a comment line. You will be -** prompted for your GPG passphrase in order to sign the new manifest -** unless the "--nosign" options is used. All files that have -** changed will be committed unless some subset of files is specified -** on the command line. +** one of the "-m" or "-M" options are used to specify a comment. +** "-m" takes a single string for the commit message and "-M" requires +** a filename from which to read the commit message. If neither "-m" +** nor "-M" are specified then the editor defined in the "editor" +** fossil option (see %fossil help set) will be used, or from the +** "VISUAL" or "EDITOR" environment variables (in that order) if no +** editor is set. +** +** You will be prompted for your GPG passphrase in order to sign the +** new manifest unless the "--nosign" options is used. All files that +** have changed will be committed unless some subset of files is +** specified on the command line. ** ** The --branch option followed by a branch name cases the new check-in ** to be placed in the named branch. The --bgcolor option can be followed ** by a color name (ex: '#ffc0c0') to specify the background color of ** entries in the new branch when shown in the web timeline interface. @@ -413,10 +421,11 @@ ** --branch NEW-BRANCH-NAME ** --bgcolor COLOR ** --nosign ** --force|-f ** --private +** --message-file|-M COMMENT-FILE ** */ void commit_cmd(void){ int rc; int vid, nrid, nvid; @@ -432,10 +441,11 @@ int nBasename; /* Length of "g.zLocalRoot/" */ const char *zBranch; /* Create a new branch with this name */ const char *zBgColor; /* Set background color when branching */ const char *zDateOvrd; /* Override date string */ const char *zUserOvrd; /* Override user name */ + const char *zCommentFile; /* Read commit message from this file */ Blob filename; /* complete filename */ Blob manifest; Blob muuid; /* Manifest uuid */ Blob mcksum; /* Self-checksum on the manifest */ Blob cksum1, cksum2; /* Before and after commit checksums */ @@ -445,10 +455,11 @@ noSign = find_option("nosign",0,0)!=0; zComment = find_option("comment","m",1); forceFlag = find_option("force", "f", 0)!=0; zBranch = find_option("branch","b",1); zBgColor = find_option("bgcolor",0,1); + zCommentFile = find_option("message-file", "M", 1); if( find_option("private",0,0) ){ g.markPrivate = 1; if( zBranch==0 ) zBranch = "private"; if( zBgColor==0 ) zBgColor = "#fec084"; /* Orange */ } @@ -537,21 +548,30 @@ vfile_aggregate_checksum_disk(vid, &cksum1); if( zComment ){ blob_zero(&comment); blob_append(&comment, zComment, -1); + }else if( zCommentFile ){ + blob_zero(&comment); + blob_read_from_file(&comment, zCommentFile); }else{ - prepare_commit_comment(&comment); - if( blob_size(&comment)==0 ){ - Blob ans; - blob_zero(&ans); - prompt_user("empty check-in comment. continue [y/N]? ", &ans); - if( blob_str(&ans)[0]!='y' ){ - db_end_transaction(1); - exit(1); - } + char *zInit = db_text(0, "SELECT value FROM vvar WHERE name='ci-comment'"); + prepare_commit_comment(&comment, zInit); + free(zInit); + } + if( blob_size(&comment)==0 ){ + Blob ans; + blob_zero(&ans); + prompt_user("empty check-in comment. continue (y/N)? ", &ans); + if( blob_str(&ans)[0]!='y' ){ + db_end_transaction(1); + exit(1); } + }else{ + db_multi_exec("REPLACE INTO vvar VALUES('ci-comment',%B)", &comment); + db_end_transaction(0); + db_begin_transaction(); } /* Step 1: Insert records for all modified files into the blob ** table. If there were arguments passed to this command, only ** the identified fils are inserted (if they have been modified). @@ -667,11 +687,11 @@ blob_appendf(&manifest, "Z %b\n", &mcksum); zManifestFile = mprintf("%smanifest", g.zLocalRoot); if( !noSign && !g.markPrivate && clearsign(&manifest, &manifest) ){ Blob ans; blob_zero(&ans); - prompt_user("unable to sign manifest. continue [y/N]? ", &ans); + prompt_user("unable to sign manifest. continue (y/N)? ", &ans); if( blob_str(&ans)[0]!='y' ){ db_end_transaction(1); exit(1); } } @@ -735,10 +755,11 @@ /* Clear the undo/redo stack */ undo_reset(); /* Commit */ + db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'"); db_end_transaction(0); if( !g.markPrivate ){ autosync(AUTOSYNC_PUSH); }
Modified src/db.c from [afc733b37d] to [6a9e0f8839].
@@ -1468,10 +1468,16 @@ ** values. With just a property name it shows the value of that property. ** With a value argument it changes the property for the current repository. ** ** The "unset" command clears a property setting. ** +** +** anon-login-enable-captcha-filler +** If enabled, the Login page will provide a button +** which uses JavaScript to fill out the captcha for +** the user. (Most bots cannot use JavaScript.) +** ** autosync If enabled, automatically pull prior to ** commit or update and automatically push ** after commit or tag or branch creation. ** ** diff-command External command to run when performing a diff. @@ -1513,10 +1519,11 @@ ** Defaults to "start" on windows, "open" on Mac, ** and "firefox" on Unix. */ void setting_cmd(void){ static const char *azName[] = { + "anon-login-enable-captcha-filler", "autosync", "diff-command", "dont-push", "editor", "gdiff-command",
Modified src/login.c from [3b52c138db] to [646f690c52].
@@ -225,11 +225,11 @@ @ <td><input type="text" id="u" name="u" value="" size=30></td> } @ </tr> @ <tr> @ <td align="right">Password:</td> - @ <td><input type="password" name="p" value="" size=30></td> + @ <td><input type="password" id="p" name="p" value="" size=30></td> @ </tr> if( g.zLogin==0 ){ zAnonPw = db_text(0, "SELECT pw FROM user" " WHERE login='anonymous'" " AND cap!=''"); @@ -250,18 +250,24 @@ @ "Login" button. Your user name will be stored in a browser cookie. @ You must configure your web browser to accept cookies in order for @ the login to take.</p> if( zAnonPw ){ unsigned int uSeed = captcha_seed(); - char *zCaptcha = captcha_render(captcha_decode(uSeed)); - - @ <input type="hidden" name="cs" value="%u(uSeed)"> + char const * zDecoded = captcha_decode(uSeed); + int iAllowPasswordFill = db_get_boolean( "anon-login-enable-captcha-filler", 0 ); + char *zCaptcha = captcha_render(zDecoded); + + @ <input type="hidden" name="cs" value="%u(uSeed)"/> @ <p>Visitors may enter <b>anonymous</b> as the user-ID with @ the 8-character hexadecimal password shown below:</p> @ <center><table border="1" cellpadding="10"><tr><td><pre> @ %s(zCaptcha) - @ </pre></td></tr></table></center> + @ </pre></td></tr></table> + if( iAllowPasswordFill ) { + @ <input type="button" value="Fill out captcha" onclick="document.getElementById('u').value='anonymous'; document.getElementById('p').value='%s(zDecoded)';"/> + } + @ </center> free(zCaptcha); } if( g.zLogin ){ @ <br clear="both"><hr> @ <p>To log off the system (and delete your login cookie)
Modified src/rebuild.c from [fa901199b7] to [ab6ed5de1a].
@@ -371,11 +371,11 @@ if( !bForce ){ Blob ans; blob_zero(&ans); prompt_user("Scrubbing the repository will permanently remove user\n" "passwords and other information. Changes cannot be undone.\n" - "Continue [y/N]? ", &ans); + "Continue (y/N)? ", &ans); if( blob_str(&ans)[0]!='y' ){ exit(1); } } db_begin_transaction();
Modified src/rss.c from [945efe289e] to [cc90868801].
@@ -26,27 +26,13 @@ #include "config.h" #include "rss.h" #include <assert.h> #include <time.h> -time_t rss_datetime_to_time_t(const char *dt){ - struct tm the_tm; - - the_tm.tm_year = atoi(dt)-1900; - the_tm.tm_mon = atoi(&dt[5])-1; - the_tm.tm_mday = atoi(&dt[8]); - the_tm.tm_hour = atoi(&dt[11]); - the_tm.tm_min = atoi(&dt[14]); - the_tm.tm_sec = atoi(&dt[17]); - - return mktime(&the_tm); -} - /* ** WEBPAGE: timeline.rss */ - void page_timeline_rss(void){ Stmt q; int nLine=0; char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0; Blob bSQL; @@ -53,11 +39,11 @@ const char *zType = PD("y","all"); /* Type of events. All if NULL */ const char zSQL1[] = @ SELECT @ blob.rid, @ uuid, - @ datetime(event.mtime), + @ event.mtime, @ coalesce(ecomment,comment), @ coalesce(euser,user), @ (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim), @ (SELECT count(*) FROM plink WHERE cid=blob.rid) @ FROM event, blob @@ -96,18 +82,20 @@ @ <generator>Fossil version %s(MANIFEST_VERSION) %s(MANIFEST_DATE)</generator> db_prepare(&q, blob_buffer(&bSQL)); blob_reset( &bSQL ); while( db_step(&q)==SQLITE_ROW && nLine<=20 ){ const char *zId = db_column_text(&q, 1); - const char *zDate = db_column_text(&q, 2); const char *zCom = db_column_text(&q, 3); const char *zAuthor = db_column_text(&q, 4); char *zPrefix = ""; + char *zDate; int nChild = db_column_int(&q, 5); int nParent = db_column_int(&q, 6); + time_t ts; - zDate = cgi_rfc822_datestamp(rss_datetime_to_time_t(zDate)); + ts = (time_t)((db_column_double(&q,2) - 2440587.5)*86400.0); + zDate = cgi_rfc822_datestamp(ts); if( nParent>1 && nChild>1 ){ zPrefix = "*MERGE/FORK* "; }else if( nParent>1 ){ zPrefix = "*MERGE* "; @@ -121,10 +109,11 @@ @ <description>%s(zPrefix)%s(zCom)</description> @ <pubDate>%s(zDate)</pubDate> @ <author>%s(zAuthor)</author> @ <guid>%s(g.zBaseURL)/ci/%s(zId)</guid> @ </item> + free(zDate); nLine++; } db_finalize(&q); @ </channel>
Modified src/setup.c from [41b6907a33] to [800f3104a6].
@@ -269,10 +269,11 @@ ** modified user record. After writing the user record, redirect ** to the page that displays a list of users. */ doWrite = cgi_all("login","info","pw") && !higherUser; if( doWrite ){ + char const * anonLoginCheckedbox = PD("anonymousEnableAutofill",0); char zCap[50]; int i = 0; int aa = P("aa")!=0; int ad = P("ad")!=0; int ae = P("ae")!=0; @@ -327,20 +328,26 @@ ){ style_header("User Creation Error"); @ <font color="red">Login "%h(zLogin)" is already used by a different @ user.</font> @ - @ <p><a href="setup_uedit?id=%d(uid))>[Bummer]</a></p> + @ <p><a href="setup_uedit?id=%d(uid)">[Bummer]</a></p> style_footer(); return; } login_verify_csrf_secret(); db_multi_exec( "REPLACE INTO user(uid,login,info,pw,cap) " "VALUES(nullif(%d,0),%Q,%Q,%Q,'%s')", uid, P("login"), P("info"), zPw, zCap ); + if( anonLoginCheckedbox && (*anonLoginCheckedbox) ){ + db_set( "anon-login-enable-captcha-filler", "on", 0 ); + } + else{ + db_set( "anon-login-enable-captcha-filler", "off", 0 ); + } cgi_redirect("setup_ulist"); return; } /* Load the existing information about the user, if any @@ -445,39 +452,43 @@ @ <tr> @ <td align="right" valign="top">Capabilities:</td> @ <td> #define B(x) inherit[x] if( g.okSetup ){ - @ <input type="checkbox" name="as"%s(oas)>%s(B('s'))Setup</input><br> - } - @ <input type="checkbox" name="aa"%s(oaa)>%s(B('a'))Admin</input><br> - @ <input type="checkbox" name="ad"%s(oad)>%s(B('d'))Delete</input><br> - @ <input type="checkbox" name="ae"%s(oae)>%s(B('e'))Email</input><br> - @ <input type="checkbox" name="ap"%s(oap)>%s(B('p'))Password</input><br> - @ <input type="checkbox" name="ai"%s(oai)>%s(B('i'))Check-In</input><br> - @ <input type="checkbox" name="ao"%s(oao)>%s(B('o'))Check-Out</input><br> - @ <input type="checkbox" name="ah"%s(oah)>%s(B('h'))History</input><br> - @ <input type="checkbox" name="au"%s(oau)>%s(B('u'))Reader</input><br> - @ <input type="checkbox" name="av"%s(oav)>%s(B('v'))Developer</input><br> - @ <input type="checkbox" name="ag"%s(oag)>%s(B('g'))Clone</input><br> - @ <input type="checkbox" name="aj"%s(oaj)>%s(B('j'))Read Wiki</input><br> - @ <input type="checkbox" name="af"%s(oaf)>%s(B('f'))New Wiki</input><br> - @ <input type="checkbox" name="am"%s(oam)>%s(B('m'))Append Wiki</input><br> - @ <input type="checkbox" name="ak"%s(oak)>%s(B('k'))Write Wiki</input><br> - @ <input type="checkbox" name="ar"%s(oar)>%s(B('r'))Read Tkt</input><br> - @ <input type="checkbox" name="an"%s(oan)>%s(B('n'))New Tkt</input><br> - @ <input type="checkbox" name="ac"%s(oac)>%s(B('c'))Append Tkt</input><br> - @ <input type="checkbox" name="aw"%s(oaw)>%s(B('w'))Write Tkt</input><br> - @ <input type="checkbox" name="at"%s(oat)>%s(B('t'))Tkt Report</input><br> - @ <input type="checkbox" name="az"%s(oaz)>%s(B('z'))Download Zip</input> + @ <input type="checkbox" name="as"%s(oas)/>%s(B('s'))Setup<br> + } + @ <input type="checkbox" name="aa"%s(oaa)/>%s(B('a'))Admin<br> + @ <input type="checkbox" name="ad"%s(oad)/>%s(B('d'))Delete<br> + @ <input type="checkbox" name="ae"%s(oae)/>%s(B('e'))Email<br> + @ <input type="checkbox" name="ap"%s(oap)/>%s(B('p'))Password<br> + @ <input type="checkbox" name="ai"%s(oai)/>%s(B('i'))Check-In<br> + @ <input type="checkbox" name="ao"%s(oao)/>%s(B('o'))Check-Out<br> + @ <input type="checkbox" name="ah"%s(oah)/>%s(B('h'))History<br> + @ <input type="checkbox" name="au"%s(oau)/>%s(B('u'))Reader<br> + @ <input type="checkbox" name="av"%s(oav)/>%s(B('v'))Developer<br> + @ <input type="checkbox" name="ag"%s(oag)/>%s(B('g'))Clone<br> + @ <input type="checkbox" name="aj"%s(oaj)/>%s(B('j'))Read Wiki<br> + @ <input type="checkbox" name="af"%s(oaf)/>%s(B('f'))New Wiki<br> + @ <input type="checkbox" name="am"%s(oam)/>%s(B('m'))Append Wiki<br> + @ <input type="checkbox" name="ak"%s(oak)/>%s(B('k'))Write Wiki<br> + @ <input type="checkbox" name="ar"%s(oar)/>%s(B('r'))Read Tkt<br> + @ <input type="checkbox" name="an"%s(oan)/>%s(B('n'))New Tkt<br> + @ <input type="checkbox" name="ac"%s(oac)/>%s(B('c'))Append Tkt<br> + @ <input type="checkbox" name="aw"%s(oaw)/>%s(B('w'))Write Tkt<br> + @ <input type="checkbox" name="at"%s(oat)/>%s(B('t'))Tkt Report<br> + @ <input type="checkbox" name="az"%s(oaz)/>%s(B('z'))Download Zip @ </td> @ </tr> @ <tr> @ <td align="right">Password:</td> if( strcmp(zLogin, "anonymous")==0 ){ + int enabled = db_get_boolean( "anon-login-enable-captcha-filler", 0 ); + char const * checked = enabled ? "checked=\"checked\"" : ""; /* User the password for "anonymous" as cleartext */ - @ <td><input type="text" name="pw" value="%h(zPw)"></td> + @ <td><input type="text" name="pw" value="%h(zPw)"/> + @ <br/>Enable password-filler button for anonymous login? <input type="checkbox" name="anonymousEnableAutofill" %s(checked)/><br/> + @ </td> }else if( zPw[0] ){ /* Obscure the password for all other users */ @ <td><input type="password" name="pw" value="**********"></td> }else{ /* Show an empty password as an empty input field */ @@ -570,11 +581,11 @@ @ </p></li> @ @ <li><p> @ Users with the <b>Password</b> privilege are allowed to change their @ own password. Recommended ON for most users but OFF for special - @ users "developer, "anonymous", and "nobody". + @ users "developer", "anonymous", and "nobody". @ </p></li> @ @ <li><p> @ The <b>EMail</b> privilege allows the display of sensitive information @ such as the email address of users and contact information on tickets. @@ -618,11 +629,11 @@ @ with check-in privileges. When adding new trusted users, simply @ select the <b>Developer</b> privilege to cause the new user to inherit @ all privileges of the "developer" user. Similarly, the "<b>reader</b>" @ user is a template for users who are allowed more access than anonymous, @ but less than a developer. - @ </li></p> + @ </p></li> @ </ul> @ </form> style_footer(); }
Modified src/sqlite3.c from [9b458b28f3] to [bc27f16f36].
@@ -643,11 +643,11 @@ ** ** Requirements: [H10011] [H10014] */ #define SQLITE_VERSION "3.6.21" #define SQLITE_VERSION_NUMBER 3006021 -#define SQLITE_SOURCE_ID "2009-11-25 21:05:09 5086bf8e838c824accda531afeb56a51dd40d795" +#define SQLITE_SOURCE_ID "2009-12-07 16:39:13 1ed88e9d01e9eda5cbc622e7614277f29bcc551c" /* ** CAPI3REF: Run-Time Library Version Numbers {H10020} <S60100> ** KEYWORDS: sqlite3_version ** @@ -1817,10 +1817,13 @@ ** the return value of this interface. ** ** For the purposes of this routine, an [INSERT] is considered to ** be successful even if it is subsequently rolled back. ** +** This function is accessible to SQL statements via the +** [last_insert_rowid() SQL function]. +** ** Requirements: ** [H12221] [H12223] ** ** If a separate thread performs a new [INSERT] on the same ** database connection while the [sqlite3_last_insert_rowid()] @@ -1874,12 +1877,12 @@ ** changes in the most recently completed INSERT, UPDATE, or DELETE ** statement within the body of the same trigger. ** However, the number returned does not include changes ** caused by subtriggers since those have their own context. ** -** See also the [sqlite3_total_changes()] interface and the -** [count_changes pragma]. +** See also the [sqlite3_total_changes()] interface, the +** [count_changes pragma], and the [changes() SQL function]. ** ** Requirements: ** [H12241] [H12243] ** ** If a separate thread makes changes on the same database connection @@ -1902,12 +1905,12 @@ ** are counted. ** The changes are counted as soon as the statement that makes them is ** completed (when the statement handle is passed to [sqlite3_reset()] or ** [sqlite3_finalize()]). ** -** See also the [sqlite3_changes()] interface and the -** [count_changes pragma]. +** See also the [sqlite3_changes()] interface, the +** [count_changes pragma], and the [total_changes() SQL function]. ** ** Requirements: ** [H12261] [H12263] ** ** If a separate thread makes changes on the same database connection @@ -4664,10 +4667,12 @@ ** should free this memory by calling [sqlite3_free()]. ** ** {H12606} Extension loading must be enabled using ** [sqlite3_enable_load_extension()] prior to calling this API, ** otherwise an error will be returned. +** +** See also the [load_extension() SQL function]. */ SQLITE_API int sqlite3_load_extension( sqlite3 *db, /* Load the extension into this database connection */ const char *zFile, /* Name of the shared library containing extension */ const char *zProc, /* Entry point. Derived from zFile if 0 */ @@ -6750,13 +6755,23 @@ ** Round down to the nearest multiple of 8 */ #define ROUNDDOWN8(x) ((x)&~7) /* -** Assert that the pointer X is aligned to an 8-byte boundary. -*/ -#define EIGHT_BYTE_ALIGNMENT(X) ((((char*)(X) - (char*)0)&7)==0) +** Assert that the pointer X is aligned to an 8-byte boundary. This +** macro is used only within assert() to verify that the code gets +** all alignment restrictions correct. +** +** Except, if SQLITE_4_BYTE_ALIGNED_MALLOC is defined, then the +** underlying malloc() implemention 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) ((((char*)(X) - (char*)0)&3)==0) +#else +# define EIGHT_BYTE_ALIGNMENT(X) ((((char*)(X) - (char*)0)&7)==0) +#endif /* ** An instance of the following structure is used to store the busy-handler ** callback for a given sqlite handle. @@ -9662,19 +9677,20 @@ ** TriggerPrg.pTrigger, assuming a default ON CONFLICT clause of ** TriggerPrg.orconf, is stored in the TriggerPrg.pProgram variable. ** The Parse.pTriggerPrg list never contains two entries with the same ** values for both pTrigger and orconf. ** -** The TriggerPrg.oldmask variable is set to a mask of old.* columns +** The TriggerPrg.aColmask[0] variable is set to a mask of old.* columns ** accessed (or set to 0 for triggers fired as a result of INSERT -** statements). +** statements). Similarly, the TriggerPrg.aColmask[1] variable is set to +** a mask of new.* columns used by the program. */ struct TriggerPrg { Trigger *pTrigger; /* Trigger this program was coded from */ int orconf; /* Default ON CONFLICT policy */ SubProgram *pProgram; /* Program implementing pTrigger/orconf */ - u32 oldmask; /* Mask of old.* columns accessed */ + u32 aColmask[2]; /* Masks of old.*, new.* columns accessed */ TriggerPrg *pNext; /* Next entry in Parse.pTriggerPrg list */ }; /* ** An SQL parser context. A copy of this structure is passed through @@ -9742,10 +9758,11 @@ /* Information used while coding trigger programs. */ Parse *pToplevel; /* Parse structure for main program (or NULL) */ Table *pTriggerTab; /* Table triggers are being coded for */ u32 oldmask; /* Mask of old.* columns referenced */ + u32 newmask; /* Mask of new.* columns referenced */ u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */ u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */ u8 disableTriggers; /* True to disable triggers */ /* Above is constant between recursions. Below is reset before and after @@ -10339,11 +10356,11 @@ ExprList*,Select*,u8); SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep(sqlite3*,Token*,ExprList*, Expr*, u8); SQLITE_PRIVATE TriggerStep *sqlite3TriggerDeleteStep(sqlite3*,Token*, Expr*); SQLITE_PRIVATE void sqlite3DeleteTrigger(sqlite3*, Trigger*); SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTrigger(sqlite3*,int,const char*); -SQLITE_PRIVATE u32 sqlite3TriggerOldmask(Parse*,Trigger*,ExprList*,Table*,int); +SQLITE_PRIVATE u32 sqlite3TriggerColmask(Parse*,Trigger*,ExprList*,int,int,Table*,int); # define sqlite3ParseToplevel(p) ((p)->pToplevel ? (p)->pToplevel : (p)) #else # define sqlite3TriggersExist(B,C,D,E,F) 0 # define sqlite3DeleteTrigger(A,B) # define sqlite3DropTriggerPtr(A,B) @@ -10350,11 +10367,11 @@ # define sqlite3UnlinkAndDeleteTrigger(A,B,C) # define sqlite3CodeRowTrigger(A,B,C,D,E,F,G,H,I) # define sqlite3CodeRowTriggerDirect(A,B,C,D,E,F) # define sqlite3TriggerList(X, Y) 0 # define sqlite3ParseToplevel(p) p -# define sqlite3TriggerOldmask(A,B,C,D,E) 0 +# define sqlite3TriggerColmask(A,B,C,D,E,F,G) 0 #endif SQLITE_PRIVATE int sqlite3JoinType(Parse*, Token*, Token*, Token*); SQLITE_PRIVATE void sqlite3CreateForeignKey(Parse*, ExprList*, Token*, ExprList*, int); SQLITE_PRIVATE void sqlite3DeferForeignKey(Parse*, int); @@ -10564,10 +10581,11 @@ SQLITE_PRIVATE int sqlite3VtabCallConnect(Parse*, Table*); SQLITE_PRIVATE int sqlite3VtabCallDestroy(sqlite3*, int, const char *); SQLITE_PRIVATE int sqlite3VtabBegin(sqlite3 *, VTable *); SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction(sqlite3 *,FuncDef*, int nArg, Expr*); SQLITE_PRIVATE void sqlite3InvalidFunction(sqlite3_context*,int,sqlite3_value**); +SQLITE_PRIVATE int sqlite3VdbeParameterIndex(Vdbe*, const char*, int); SQLITE_PRIVATE int sqlite3TransferBindings(sqlite3_stmt *, sqlite3_stmt *); SQLITE_PRIVATE int sqlite3Reprepare(Vdbe*); SQLITE_PRIVATE void sqlite3ExprListCheckLength(Parse*, ExprList*, const char*); SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq(Parse *, Expr *, Expr *); SQLITE_PRIVATE int sqlite3TempInMemory(const sqlite3*); @@ -14914,10 +14932,43 @@ assert( p->id==SQLITE_MUTEX_FAST || p->id==SQLITE_MUTEX_RECURSIVE ); DosCloseMutexSem( p->mutex ); sqlite3_free( p ); } +#ifdef SQLITE_DEBUG +/* +** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routine are +** intended for use inside assert() statements. +*/ +static int os2MutexHeld(sqlite3_mutex *p){ + TID tid; + PID pid; + ULONG ulCount; + PTIB ptib; + if( p!=0 ) { + DosQueryMutexSem(p->mutex, &pid, &tid, &ulCount); + } else { + DosGetInfoBlocks(&ptib, NULL); + tid = ptib->tib_ptib2->tib2_ultid; + } + return p==0 || (p->nRef!=0 && p->owner==tid); +} +static int os2MutexNotheld(sqlite3_mutex *p){ + TID tid; + PID pid; + ULONG ulCount; + PTIB ptib; + if( p!= 0 ) { + DosQueryMutexSem(p->mutex, &pid, &tid, &ulCount); + } else { + DosGetInfoBlocks(&ptib, NULL); + tid = ptib->tib_ptib2->tib2_ultid; + } + return p==0 || p->nRef==0 || p->owner!=tid; +} +#endif + /* ** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt ** to enter a mutex. If another thread is already within the mutex, ** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return ** SQLITE_BUSY. The sqlite3_mutex_try() interface returns SQLITE_OK @@ -14973,43 +15024,10 @@ assert( p->owner==tid ); p->nRef--; assert( p->nRef==0 || p->id==SQLITE_MUTEX_RECURSIVE ); DosReleaseMutexSem(p->mutex); } - -#ifdef SQLITE_DEBUG -/* -** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routine are -** intended for use inside assert() statements. -*/ -static int os2MutexHeld(sqlite3_mutex *p){ - TID tid; - PID pid; - ULONG ulCount; - PTIB ptib; - if( p!=0 ) { - DosQueryMutexSem(p->mutex, &pid, &tid, &ulCount); - } else { - DosGetInfoBlocks(&ptib, NULL); - tid = ptib->tib_ptib2->tib2_ultid; - } - return p==0 || (p->nRef!=0 && p->owner==tid); -} -static int os2MutexNotheld(sqlite3_mutex *p){ - TID tid; - PID pid; - ULONG ulCount; - PTIB ptib; - if( p!= 0 ) { - DosQueryMutexSem(p->mutex, &pid, &tid, &ulCount); - } else { - DosGetInfoBlocks(&ptib, NULL); - tid = ptib->tib_ptib2->tib2_ultid; - } - return p==0 || p->nRef==0 || p->owner!=tid; -} -#endif SQLITE_PRIVATE sqlite3_mutex_methods *sqlite3DefaultMutex(void){ static sqlite3_mutex_methods sMutex = { os2MutexInit, os2MutexEnd, @@ -22701,11 +22719,11 @@ } } #endif unixLeaveMutex(); - OSTRACE4("TEST WR-LOCK %d %d %d\n", pFile->h, rc, reserved); + OSTRACE4("TEST WR-LOCK %d %d %d (unix)\n", pFile->h, rc, reserved); *pResOut = reserved; return rc; } @@ -22834,20 +22852,20 @@ struct flock lock; int s = 0; int tErrno; assert( pFile ); - OSTRACE7("LOCK %d %s was %s(%s,%d) pid=%d\n", pFile->h, + OSTRACE7("LOCK %d %s was %s(%s,%d) pid=%d (unix)\n", pFile->h, locktypeName(locktype), locktypeName(pFile->locktype), locktypeName(pLock->locktype), pLock->cnt , getpid()); /* If there is already a lock of this type or more restrictive on the ** unixFile, do nothing. Don't use the end_lock: exit path, as ** unixEnterMutex() hasn't been called yet. */ if( pFile->locktype>=locktype ){ - OSTRACE3("LOCK %d %s ok (already held)\n", pFile->h, + OSTRACE3("LOCK %d %s ok (already held) (unix)\n", pFile->h, locktypeName(locktype)); return SQLITE_OK; } /* Make sure the locking sequence is correct. @@ -23013,11 +23031,11 @@ pLock->locktype = PENDING_LOCK; } end_lock: unixLeaveMutex(); - OSTRACE4("LOCK %d %s %s\n", pFile->h, locktypeName(locktype), + OSTRACE4("LOCK %d %s %s (unix)\n", pFile->h, locktypeName(locktype), rc==SQLITE_OK ? "ok" : "failed"); return rc; } /* @@ -23077,11 +23095,11 @@ int rc = SQLITE_OK; /* Return code from this interface */ int h; /* The underlying file descriptor */ int tErrno; /* Error code from system call errors */ assert( pFile ); - OSTRACE7("UNLOCK %d %d was %d(%d,%d) pid=%d\n", pFile->h, locktype, + OSTRACE7("UNLOCK %d %d was %d(%d,%d) pid=%d (unix)\n", pFile->h, locktype, pFile->locktype, pFile->pLock->locktype, pFile->pLock->cnt, getpid()); assert( locktype<=SHARED_LOCK ); if( pFile->locktype<=locktype ){ return SQLITE_OK; @@ -23358,11 +23376,11 @@ }else{ /* The lock is held if and only if the lockfile exists */ const char *zLockFile = (const char*)pFile->lockingContext; reserved = access(zLockFile, 0)==0; } - OSTRACE4("TEST WR-LOCK %d %d %d\n", pFile->h, rc, reserved); + OSTRACE4("TEST WR-LOCK %d %d %d (dotlock)\n", pFile->h, rc, reserved); *pResOut = reserved; return rc; } /* @@ -23448,11 +23466,11 @@ static int dotlockUnlock(sqlite3_file *id, int locktype) { unixFile *pFile = (unixFile*)id; char *zLockFile = (char *)pFile->lockingContext; assert( pFile ); - OSTRACE5("UNLOCK %d %d was %d pid=%d\n", pFile->h, locktype, + OSTRACE5("UNLOCK %d %d was %d pid=%d (dotlock)\n", pFile->h, locktype, pFile->locktype, getpid()); assert( locktype<=SHARED_LOCK ); /* no-op if possible */ if( pFile->locktype==locktype ){ @@ -23562,11 +23580,11 @@ pFile->lastErrno = tErrno; rc = lrc; } } } - OSTRACE4("TEST WR-LOCK %d %d %d\n", pFile->h, rc, reserved); + OSTRACE4("TEST WR-LOCK %d %d %d (flock)\n", pFile->h, rc, reserved); #ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS if( (rc & SQLITE_IOERR) == SQLITE_IOERR ){ rc = SQLITE_OK; reserved=1; @@ -23629,11 +23647,11 @@ } } else { /* got it, set the type and return ok */ pFile->locktype = locktype; } - OSTRACE4("LOCK %d %s %s\n", pFile->h, locktypeName(locktype), + OSTRACE4("LOCK %d %s %s (flock)\n", pFile->h, locktypeName(locktype), rc==SQLITE_OK ? "ok" : "failed"); #ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS if( (rc & SQLITE_IOERR) == SQLITE_IOERR ){ rc = SQLITE_BUSY; } @@ -23651,11 +23669,11 @@ */ static int flockUnlock(sqlite3_file *id, int locktype) { unixFile *pFile = (unixFile*)id; assert( pFile ); - OSTRACE5("UNLOCK %d %d was %d pid=%d\n", pFile->h, locktype, + OSTRACE5("UNLOCK %d %d was %d pid=%d (flock)\n", pFile->h, locktype, pFile->locktype, getpid()); assert( locktype<=SHARED_LOCK ); /* no-op if possible */ if( pFile->locktype==locktype ){ @@ -23753,11 +23771,11 @@ }else{ /* we could have it if we want it */ sem_post(pSem); } } - OSTRACE4("TEST WR-LOCK %d %d %d\n", pFile->h, rc, reserved); + OSTRACE4("TEST WR-LOCK %d %d %d (sem)\n", pFile->h, rc, reserved); *pResOut = reserved; return rc; } @@ -23828,11 +23846,11 @@ unixFile *pFile = (unixFile*)id; sem_t *pSem = pFile->pOpen->pSem; assert( pFile ); assert( pSem ); - OSTRACE5("UNLOCK %d %d was %d pid=%d\n", pFile->h, locktype, + OSTRACE5("UNLOCK %d %d was %d pid=%d (sem)\n", pFile->h, locktype, pFile->locktype, getpid()); assert( locktype<=SHARED_LOCK ); /* no-op if possible */ if( pFile->locktype==locktype ){ @@ -23998,11 +24016,11 @@ if( IS_LOCK_ERROR(lrc) ){ rc=lrc; } } - OSTRACE4("TEST WR-LOCK %d %d %d\n", pFile->h, rc, reserved); + OSTRACE4("TEST WR-LOCK %d %d %d (afp)\n", pFile->h, rc, reserved); *pResOut = reserved; return rc; } @@ -24034,19 +24052,19 @@ int rc = SQLITE_OK; unixFile *pFile = (unixFile*)id; afpLockingContext *context = (afpLockingContext *) pFile->lockingContext; assert( pFile ); - OSTRACE5("LOCK %d %s was %s pid=%d\n", pFile->h, + OSTRACE5("LOCK %d %s was %s pid=%d (afp)\n", pFile->h, locktypeName(locktype), locktypeName(pFile->locktype), getpid()); /* If there is already a lock of this type or more restrictive on the ** unixFile, do nothing. Don't use the afp_end_lock: exit path, as ** unixEnterMutex() hasn't been called yet. */ if( pFile->locktype>=locktype ){ - OSTRACE3("LOCK %d %s ok (already held)\n", pFile->h, + OSTRACE3("LOCK %d %s ok (already held) (afp)\n", pFile->h, locktypeName(locktype)); return SQLITE_OK; } /* Make sure the locking sequence is correct @@ -24161,11 +24179,11 @@ pFile->locktype = PENDING_LOCK; } afp_end_lock: unixLeaveMutex(); - OSTRACE4("LOCK %d %s %s\n", pFile->h, locktypeName(locktype), + OSTRACE4("LOCK %d %s %s (afp)\n", pFile->h, locktypeName(locktype), rc==SQLITE_OK ? "ok" : "failed"); return rc; } /* @@ -24179,11 +24197,11 @@ int rc = SQLITE_OK; unixFile *pFile = (unixFile*)id; afpLockingContext *pCtx = (afpLockingContext *) pFile->lockingContext; assert( pFile ); - OSTRACE5("UNLOCK %d %d was %d pid=%d\n", pFile->h, locktype, + OSTRACE5("UNLOCK %d %d was %d pid=%d (afp)\n", pFile->h, locktype, pFile->locktype, getpid()); assert( locktype<=SHARED_LOCK ); if( pFile->locktype<=locktype ){ return SQLITE_OK; @@ -32430,10 +32448,11 @@ pager_reset(pPager); } pPager->changeCountDone = 0; pPager->state = PAGER_UNLOCK; + pPager->dbModified = 0; } } /* ** This function should be called when an IOERR, CORRUPT or FULL error @@ -33806,12 +33825,15 @@ /* The OS lock values must be the same as the Pager lock values */ assert( PAGER_SHARED==SHARED_LOCK ); assert( PAGER_RESERVED==RESERVED_LOCK ); assert( PAGER_EXCLUSIVE==EXCLUSIVE_LOCK ); - /* If the file is currently unlocked then the size must be unknown */ + /* If the file is currently unlocked then the size must be unknown. It + ** must not have been modified at this point. + */ assert( pPager->state>=PAGER_SHARED || pPager->dbSizeValid==0 ); + assert( pPager->state>=PAGER_SHARED || pPager->dbModified==0 ); /* Check that this is either a no-op (because the requested lock is ** already held, or one of the transistions that the busy-handler ** may be invoked during, according to the comment above ** sqlite3PagerSetBusyhandler(). @@ -38763,18 +38785,20 @@ u8 * const data = pPage->aData; /* Local cache of pPage->aData */ int nFrag; /* Number of fragmented bytes on pPage */ int top; /* First byte of cell content area */ int gap; /* First byte of gap between cell pointers and cell content */ int rc; /* Integer return code */ + int usableSize; /* Usable size of the page */ assert( sqlite3PagerIswriteable(pPage->pDbPage) ); assert( pPage->pBt ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); assert( nByte>=0 ); /* Minimum cell size is 4 */ assert( pPage->nFree>=nByte ); assert( pPage->nOverflow==0 ); - assert( nByte<pPage->pBt->usableSize-8 ); + usableSize = pPage->pBt->usableSize; + assert( nByte < usableSize-8 ); nFrag = data[hdr+7]; assert( pPage->cellOffset == hdr + 12 - 4*pPage->leaf ); gap = pPage->cellOffset + 2*pPage->nCell; top = get2byte(&data[hdr+5]); @@ -38793,20 +38817,26 @@ ** the request. The allocation is made from the first free slot in ** the list that is large enough to accomadate it. */ int pc, addr; for(addr=hdr+1; (pc = get2byte(&data[addr]))>0; addr=pc){ - int size = get2byte(&data[pc+2]); /* Size of free slot */ + int size; /* Size of the free slot */ + if( pc>usableSize-4 || pc<addr+4 ){ + return SQLITE_CORRUPT_BKPT; + } + size = get2byte(&data[pc+2]); if( size>=nByte ){ int x = size - nByte; testcase( x==4 ); testcase( x==3 ); if( x<4 ){ /* Remove the slot from the free-list. Update the number of ** fragmented bytes within the page. */ memcpy(&data[addr], &data[pc], 2); data[hdr+7] = (u8)(nFrag + x); + }else if( size+pc > usableSize ){ + return SQLITE_CORRUPT_BKPT; }else{ /* The slot remains on the free-list. Reduce its size to account ** for the portion used by the new allocation. */ put2byte(&data[pc+2], x); } @@ -39226,11 +39256,10 @@ ** Release a MemPage. This should be called once for each prior ** call to btreeGetPage. */ static void releasePage(MemPage *pPage){ if( pPage ){ - assert( pPage->nOverflow==0 || sqlite3PagerPageRefcount(pPage->pDbPage)>1 ); assert( pPage->aData ); assert( pPage->pBt ); assert( sqlite3PagerGetExtra(pPage->pDbPage) == (void*)pPage ); assert( sqlite3PagerGetData(pPage->pDbPage)==pPage->aData ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); @@ -39966,15 +39995,12 @@ unsigned char *data; int rc; int nPage; assert( sqlite3_mutex_held(pBt->mutex) ); - /* The database size has already been measured and cached, so failure - ** is impossible here. If the original size measurement failed, then - ** processing aborts before entering this routine. */ rc = sqlite3PagerPagecount(pBt->pPager, &nPage); - if( NEVER(rc!=SQLITE_OK) || nPage>0 ){ + if( rc!=SQLITE_OK || nPage>0 ){ return rc; } pP1 = pBt->pPage1; assert( pP1!=0 ); data = pP1->aData; @@ -42918,12 +42944,17 @@ if( *pRC ) return; assert( i>=0 && i<=pPage->nCell+pPage->nOverflow ); assert( pPage->nCell<=MX_CELL(pPage->pBt) && MX_CELL(pPage->pBt)<=5460 ); assert( pPage->nOverflow<=ArraySize(pPage->aOvfl) ); - assert( sz==cellSizePtr(pPage, pCell) ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + /* The cell should normally be sized correctly. However, when moving a + ** malformed cell from a leaf page to an interior page, if the cell size + ** wanted to be less than 4 but got rounded up to 4 on the leaf, then size + ** might be less than 8 (leaf-size + pointer) on the interior node. Hence + ** the term after the || in the following assert(). */ + assert( sz==cellSizePtr(pPage, pCell) || (sz==8 && iChild>0) ); if( pPage->nOverflow || sz+2>pPage->nFree ){ if( pTemp ){ memcpy(pTemp+nSkip, pCell+nSkip, sz-nSkip); pCell = pTemp; } @@ -43198,11 +43229,11 @@ BtShared * const pBt = pFrom->pBt; u8 * const aFrom = pFrom->aData; u8 * const aTo = pTo->aData; int const iFromHdr = pFrom->hdrOffset; int const iToHdr = ((pTo->pgno==1) ? 100 : 0); - TESTONLY(int rc;) + int rc; int iData; assert( pFrom->isInit ); assert( pFrom->nFree>=iToHdr ); @@ -43212,15 +43243,20 @@ iData = get2byte(&aFrom[iFromHdr+5]); memcpy(&aTo[iData], &aFrom[iData], pBt->usableSize-iData); memcpy(&aTo[iToHdr], &aFrom[iFromHdr], pFrom->cellOffset + 2*pFrom->nCell); /* Reinitialize page pTo so that the contents of the MemPage structure - ** match the new data. The initialization of pTo "cannot" fail, as the - ** data copied from pFrom is known to be valid. */ + ** match the new data. The initialization of pTo can actually fail under + ** fairly obscure circumstances, even though it is a copy of initialized + ** page pFrom. + */ pTo->isInit = 0; - TESTONLY(rc = ) btreeInitPage(pTo); - assert( rc==SQLITE_OK ); + rc = btreeInitPage(pTo); + if( rc!=SQLITE_OK ){ + *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 ){ @@ -51381,26 +51417,28 @@ /* ** Given a wildcard parameter name, return the index of the variable ** with that name. If there is no variable with the given name, ** return 0. */ -SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt *pStmt, const char *zName){ - Vdbe *p = (Vdbe*)pStmt; +SQLITE_PRIVATE int sqlite3VdbeParameterIndex(Vdbe *p, const char *zName, int nName){ int i; if( p==0 ){ return 0; } createVarMap(p); if( zName ){ for(i=0; i<p->nVar; i++){ const char *z = p->azVar[i]; - if( z && strcmp(z,zName)==0 ){ + if( z && memcmp(z,zName,nName)==0 && z[nName]==0 ){ return i+1; } } } return 0; +} +SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt *pStmt, const char *zName){ + return sqlite3VdbeParameterIndex((Vdbe*)pStmt, zName, sqlite3Strlen30(zName)); } /* ** Transfer all bindings from the first statement over to the second. */ @@ -51509,29 +51547,33 @@ ** zSql is a zero-terminated string of UTF-8 SQL text. Return the number of ** bytes in this text up to but excluding the first character in ** a host parameter. If the text contains no host parameters, return ** the total number of bytes in the text. */ -static int findNextHostParameter(const char *zSql){ +static int findNextHostParameter(const char *zSql, int *pnToken){ int tokenType; int nTotal = 0; int n; + *pnToken = 0; while( zSql[0] ){ n = sqlite3GetToken((u8*)zSql, &tokenType); assert( n>0 && tokenType!=TK_ILLEGAL ); - if( tokenType==TK_VARIABLE ) break; + if( tokenType==TK_VARIABLE ){ + *pnToken = n; + break; + } nTotal += n; zSql += n; } return nTotal; } /* -** Return a pointer to a string in memory obtained form sqlite3Malloc() which +** Return a pointer to a string in memory obtained form sqlite3DbMalloc() which ** holds a copy of zRawSql but with host parameters expanded to their -** current values. +** current bindings. ** ** The calling function is responsible for making sure the memory returned ** is eventually freed. ** ** ALGORITHM: Scan the input string looking for host parameters in any of @@ -51545,60 +51587,46 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql( Vdbe *p, /* The prepared statement being evaluated */ const char *zRawSql /* Raw text of the SQL statement */ ){ sqlite3 *db; /* The database connection */ - int idx; /* Index of a host parameter */ + int idx = 0; /* Index of a host parameter */ int nextIndex = 1; /* Index of next ? host parameter */ int n; /* Length of a token prefix */ + int nToken; /* Length of the parameter token */ int i; /* Loop counter */ - int dummy; /* For holding a unused return value */ - Mem *pVar; /* Value of a host parameter */ - VdbeOp *pOp; /* For looping over opcodes */ + Mem *pVar; /* Value of a host parameter */ StrAccum out; /* Accumulate the output here */ char zBase[100]; /* Initial working space */ db = p->db; sqlite3StrAccumInit(&out, zBase, sizeof(zBase), db->aLimit[SQLITE_LIMIT_LENGTH]); out.db = db; while( zRawSql[0] ){ - n = findNextHostParameter(zRawSql); + n = findNextHostParameter(zRawSql, &nToken); assert( n>0 ); sqlite3StrAccumAppend(&out, zRawSql, n); zRawSql += n; - if( zRawSql[0]==0 ) break; + assert( zRawSql[0] || nToken==0 ); + if( nToken==0 ) break; if( zRawSql[0]=='?' ){ - zRawSql++; - if( sqlite3Isdigit(zRawSql[0]) ){ - idx = 0; - while( sqlite3Isdigit(zRawSql[0]) ){ - idx = idx*10 + zRawSql[0] - '0'; - zRawSql++; - } + if( nToken>1 ){ + assert( sqlite3Isdigit(zRawSql[1]) ); + sqlite3GetInt32(&zRawSql[1], &idx); }else{ idx = nextIndex; } }else{ assert( zRawSql[0]==':' || zRawSql[0]=='$' || zRawSql[0]=='@' ); testcase( zRawSql[0]==':' ); testcase( zRawSql[0]=='$' ); testcase( zRawSql[0]=='@' ); - n = sqlite3GetToken((u8*)zRawSql, &dummy); - idx = 0; - for(i=0, pOp=p->aOp; ALWAYS(i<p->nOp); i++, pOp++){ - if( pOp->opcode!=OP_Variable ) continue; - if( pOp->p3>1 ) continue; - if( pOp->p4.z==0 ) continue; - if( memcmp(pOp->p4.z, zRawSql, n)==0 && pOp->p4.z[n]==0 ){ - idx = pOp->p1; - break; - } - } + idx = sqlite3VdbeParameterIndex(p, zRawSql, nToken); assert( idx>0 ); - zRawSql += n; - } + } + zRawSql += nToken; nextIndex = idx + 1; assert( idx>0 && idx<=p->nVar ); pVar = &p->aVar[idx-1]; if( pVar->flags & MEM_Null ){ sqlite3StrAccumAppend(&out, "NULL", 4); @@ -51606,15 +51634,16 @@ sqlite3XPrintf(&out, "%lld", pVar->u.i); }else if( pVar->flags & MEM_Real ){ sqlite3XPrintf(&out, "%!.15g", pVar->r); }else if( pVar->flags & MEM_Str ){ #ifndef SQLITE_OMIT_UTF16 - if( ENC(db)!=SQLITE_UTF8 ){ + u8 enc = ENC(db); + if( enc!=SQLITE_UTF8 ){ Mem utf8; memset(&utf8, 0, sizeof(utf8)); utf8.db = db; - sqlite3VdbeMemSetStr(&utf8, pVar->z, pVar->n, ENC(db), SQLITE_STATIC); + sqlite3VdbeMemSetStr(&utf8, pVar->z, pVar->n, enc, SQLITE_STATIC); sqlite3VdbeChangeEncoding(&utf8, SQLITE_UTF8); sqlite3XPrintf(&out, "'%.*q'", utf8.n, utf8.z); sqlite3VdbeMemRelease(&utf8); }else #endif @@ -52272,11 +52301,11 @@ int rc = SQLITE_OK; /* Value to return */ sqlite3 *db = p->db; /* The database */ u8 resetSchemaOnFault = 0; /* Reset schema after an error if true */ u8 encoding = ENC(db); /* The database encoding */ #ifndef SQLITE_OMIT_PROGRESS_CALLBACK - u8 checkProgress; /* True if progress callbacks are enabled */ + int checkProgress; /* True if progress callbacks are enabled */ int nProgressOps = 0; /* Opcodes executed since progress callback. */ #endif Mem *aMem = p->aMem; /* Copy of p->aMem */ Mem *pIn1 = 0; /* 1st input operand */ Mem *pIn2 = 0; /* 2nd input operand */ @@ -55461,11 +55490,11 @@ ** u.az.r.flags = UNPACKED_INCRKEY; ** }else{ ** u.az.r.flags = 0; ** } */ - u.az.r.flags = UNPACKED_INCRKEY * (1 & (u.az.oc - OP_SeekLt)); + u.az.r.flags = (u16)(UNPACKED_INCRKEY * (1 & (u.az.oc - OP_SeekLt))); assert( u.az.oc!=OP_SeekGt || u.az.r.flags==UNPACKED_INCRKEY ); assert( u.az.oc!=OP_SeekLe || u.az.r.flags==UNPACKED_INCRKEY ); assert( u.az.oc!=OP_SeekGe || u.az.r.flags==0 ); assert( u.az.oc!=OP_SeekLt || u.az.r.flags==0 ); @@ -55594,11 +55623,11 @@ if( ALWAYS(u.bb.pC->pCursor!=0) ){ assert( u.bb.pC->isTable==0 ); if( pOp->p4.i>0 ){ u.bb.r.pKeyInfo = u.bb.pC->pKeyInfo; - u.bb.r.nField = pOp->p4.i; + u.bb.r.nField = (u16)pOp->p4.i; u.bb.r.aMem = pIn3; u.bb.r.flags = UNPACKED_PREFIX_MATCH; u.bb.pIdxKey = &u.bb.r; }else{ assert( pIn3->flags & MEM_Blob ); @@ -59309,10 +59338,14 @@ pExpr->affinity = SQLITE_AFF_INTEGER; }else if( pExpr->iTable==0 ){ testcase( iCol==31 ); testcase( iCol==32 ); pParse->oldmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<<iCol)); + }else{ + testcase( iCol==31 ); + testcase( iCol==32 ); + pParse->newmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<<iCol)); } pExpr->iColumn = (i16)iCol; pExpr->pTab = pTab; isTrigger = 1; } @@ -70890,11 +70923,13 @@ u32 mask; /* Mask of OLD.* columns in use */ int iCol; /* Iterator used while populating OLD.* */ /* TODO: Could use temporary registers here. Also could attempt to ** avoid copying the contents of the rowid register. */ - mask = sqlite3TriggerOldmask(pParse, pTrigger, 0, pTab, onconf); + mask = sqlite3TriggerColmask( + pParse, pTrigger, 0, 0, TRIGGER_BEFORE|TRIGGER_AFTER, pTab, onconf + ); mask |= sqlite3FkOldmask(pParse, pTab); iOld = pParse->nMem+1; pParse->nMem += (1 + pTab->nCol); /* Populate the OLD.* pseudo-table register array. These values will be @@ -84240,11 +84275,12 @@ pPrg->pProgram = pProgram = sqlite3DbMallocZero(db, sizeof(SubProgram)); if( !pProgram ) return 0; pProgram->nRef = 1; pPrg->pTrigger = pTrigger; pPrg->orconf = orconf; - pPrg->oldmask = 0xffffffff; + pPrg->aColmask[0] = 0xffffffff; + pPrg->aColmask[1] = 0xffffffff; /* Allocate and populate a new Parse context to use for coding the ** trigger sub-program. */ pSubParse = sqlite3StackAllocZero(db, sizeof(Parse)); if( !pSubParse ) return 0; @@ -84301,11 +84337,12 @@ pProgram->aOp = sqlite3VdbeTakeOpArray(v, &pProgram->nOp, &pTop->nMaxArg); } pProgram->nMem = pSubParse->nMem; pProgram->nCsr = pSubParse->nTab; pProgram->token = (void *)pTrigger; - pPrg->oldmask = pSubParse->oldmask; + pPrg->aColmask[0] = pSubParse->oldmask; + pPrg->aColmask[1] = pSubParse->newmask; sqlite3VdbeDelete(v); } assert( !pSubParse->pAinc && !pSubParse->pZombieTab ); assert( !pSubParse->pTriggerPrg && !pSubParse->nMaxArg ); @@ -84461,45 +84498,56 @@ } } } /* -** Triggers fired by UPDATE or DELETE statements may access values stored -** in the old.* pseudo-table. This function returns a 32-bit bitmask -** indicating which columns of the old.* table actually are used by -** triggers. This information may be used by the caller to avoid having -** to load the entire old.* record into memory when executing an UPDATE -** or DELETE command. +** Triggers may access values stored in the old.* or new.* pseudo-table. +** This function returns a 32-bit bitmask indicating which columns of the +** old.* or new.* tables actually are used by triggers. This information +** may be used by the caller, for example, to avoid having to load the entire +** old.* record into memory when executing an UPDATE or DELETE command. ** ** Bit 0 of the returned mask is set if the left-most column of the -** table may be accessed using an old.<col> reference. Bit 1 is set if +** table may be accessed using an [old|new].<col> reference. Bit 1 is set if ** the second leftmost column value is required, and so on. If there ** are more than 32 columns in the table, and at least one of the columns ** with an index greater than 32 may be accessed, 0xffffffff is returned. ** -** It is not possible to determine if the old.rowid column is accessed -** by triggers. The caller must always assume that it is. -** -** There is no equivalent function for new.* references. -*/ -SQLITE_PRIVATE u32 sqlite3TriggerOldmask( +** It is not possible to determine if the old.rowid or new.rowid column is +** accessed by triggers. The caller must always assume that it is. +** +** Parameter isNew must be either 1 or 0. If it is 0, then the mask returned +** applies to the old.* table. If 1, the new.* table. +** +** Parameter tr_tm must be a mask with one or both of the TRIGGER_BEFORE +** and TRIGGER_AFTER bits set. Values accessed by BEFORE triggers are only +** included in the returned mask if the TRIGGER_BEFORE bit is set in the +** tr_tm parameter. Similarly, values accessed by AFTER triggers are only +** included in the returned mask if the TRIGGER_AFTER bit is set in tr_tm. +*/ +SQLITE_PRIVATE u32 sqlite3TriggerColmask( Parse *pParse, /* Parse context */ Trigger *pTrigger, /* List of triggers on table pTab */ ExprList *pChanges, /* Changes list for any UPDATE OF triggers */ + int isNew, /* 1 for new.* ref mask, 0 for old.* ref mask */ + int tr_tm, /* Mask of TRIGGER_BEFORE|TRIGGER_AFTER */ Table *pTab, /* The table to code triggers from */ int orconf /* Default ON CONFLICT policy for trigger steps */ ){ const int op = pChanges ? TK_UPDATE : TK_DELETE; u32 mask = 0; Trigger *p; + assert( isNew==1 || isNew==0 ); for(p=pTrigger; p; p=p->pNext){ - if( p->op==op && checkColumnOverlap(p->pColumns,pChanges) ){ + if( p->op==op && (tr_tm&p->tr_tm) + && checkColumnOverlap(p->pColumns,pChanges) + ){ TriggerPrg *pPrg; pPrg = getRowTrigger(pParse, p, pTab, orconf); if( pPrg ){ - mask |= pPrg->oldmask; + mask |= pPrg->aColmask[isNew]; } } } return mask; @@ -84619,18 +84667,19 @@ Expr *pRowidExpr = 0; /* Expression defining the new record number */ int openAll = 0; /* True if all indices need to be opened */ AuthContext sContext; /* The authorization context */ NameContext sNC; /* The name-context to resolve expressions in */ int iDb; /* Database containing the table being updated */ - int j1; /* Addresses of jump instructions */ int okOnePass; /* True for one-pass algorithm without the FIFO */ int hasFK; /* True if foreign key processing is required */ #ifndef SQLITE_OMIT_TRIGGER - int isView; /* Trying to update a view */ - Trigger *pTrigger; /* List of triggers on pTab, if required */ -#endif + int isView; /* True when updating a view (INSTEAD OF trigger) */ + Trigger *pTrigger; /* List of triggers on pTab, if required */ + int tmask; /* Mask of TRIGGER_BEFORE|TRIGGER_AFTER */ +#endif + int newmask; /* Mask of NEW.* columns accessed by BEFORE triggers */ /* Register Allocations */ int regRowCount = 0; /* A count of rows changed */ int regOldRowid; /* The old rowid */ int regNewRowid; /* The new rowid */ @@ -84654,25 +84703,27 @@ /* Figure out if we have any triggers and if the table being ** updated is a view. */ #ifndef SQLITE_OMIT_TRIGGER - pTrigger = sqlite3TriggersExist(pParse, pTab, TK_UPDATE, pChanges, 0); + pTrigger = sqlite3TriggersExist(pParse, pTab, TK_UPDATE, pChanges, &tmask); isView = pTab->pSelect!=0; + assert( pTrigger || tmask==0 ); #else # define pTrigger 0 # define isView 0 +# define tmask 0 #endif #ifdef SQLITE_OMIT_VIEW # undef isView # define isView 0 #endif if( sqlite3ViewGetColumnNames(pParse, pTab) ){ goto update_cleanup; } - if( sqlite3IsReadOnly(pParse, pTab, (pTrigger?1:0)) ){ + if( sqlite3IsReadOnly(pParse, pTab, tmask) ){ goto update_cleanup; } aXRef = sqlite3DbMallocRaw(db, sizeof(int) * pTab->nCol ); if( aXRef==0 ) goto update_cleanup; for(i=0; i<pTab->nCol; i++) aXRef[i] = -1; @@ -84896,11 +84947,13 @@ /* If there are triggers on this table, populate an array of registers ** with the required old.* column data. */ if( hasFK || pTrigger ){ u32 oldmask = (hasFK ? sqlite3FkOldmask(pParse, pTab) : 0); - oldmask |= sqlite3TriggerOldmask(pParse, pTrigger, pChanges, pTab, onError); + oldmask |= sqlite3TriggerColmask(pParse, + pTrigger, pChanges, 0, TRIGGER_BEFORE|TRIGGER_AFTER, pTab, onError + ); for(i=0; i<pTab->nCol; i++){ if( aXRef[i]<0 || oldmask==0xffffffff || (oldmask & (1<<i)) ){ sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regOld+i); sqlite3ColumnDefault(v, pTab, i, regOld+i); }else{ @@ -84913,42 +84966,76 @@ } /* Populate the array of registers beginning at regNew with the new ** row data. This array is used to check constaints, create the new ** table and index records, and as the values for any new.* references - ** made by triggers. */ + ** made by triggers. + ** + ** If there are one or more BEFORE triggers, then do not populate the + ** registers associated with columns that are (a) not modified by + ** this UPDATE statement and (b) not accessed by new.* references. The + ** values for registers not modified by the UPDATE must be reloaded from + ** the database after the BEFORE triggers are fired anyway (as the trigger + ** may have modified them). So not loading those that are not going to + ** be used eliminates some redundant opcodes. + */ + newmask = sqlite3TriggerColmask( + pParse, pTrigger, pChanges, 1, TRIGGER_BEFORE, pTab, onError + ); for(i=0; i<pTab->nCol; i++){ if( i==pTab->iPKey ){ sqlite3VdbeAddOp2(v, OP_Null, 0, regNew+i); }else{ j = aXRef[i]; - if( j<0 ){ + if( j>=0 ){ + sqlite3ExprCode(pParse, pChanges->a[j].pExpr, regNew+i); + }else if( 0==(tmask&TRIGGER_BEFORE) || i>31 || (newmask&(1<<i)) ){ + /* This branch loads the value of a column that will not be changed + ** into a register. This is done if there are no BEFORE triggers, or + ** if there are one or more BEFORE triggers that use this value via + ** a new.* reference in a trigger program. + */ + testcase( i==31 ); + testcase( i==32 ); sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regNew+i); sqlite3ColumnDefault(v, pTab, i, regNew+i); - }else{ - sqlite3ExprCode(pParse, pChanges->a[j].pExpr, regNew+i); } } } /* Fire any BEFORE UPDATE triggers. This happens before constraints are - ** verified. One could argue that this is wrong. */ - if( pTrigger ){ + ** verified. One could argue that this is wrong. + */ + if( tmask&TRIGGER_BEFORE ){ sqlite3VdbeAddOp2(v, OP_Affinity, regNew, pTab->nCol); sqlite3TableAffinityStr(v, pTab); sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, TRIGGER_BEFORE, pTab, regOldRowid, onError, addr); /* The row-trigger may have deleted the row being updated. In this ** case, jump to the next row. No updates or AFTER triggers are ** required. This behaviour - what happens when the row being updated ** is deleted or renamed by a BEFORE trigger - is left undefined in the - ** documentation. */ + ** documentation. + */ sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, regOldRowid); + + /* If it did not delete it, the row-trigger may still have modified + ** some of the columns of the row being updated. Load the values for + ** all columns not modified by the update statement into their + ** registers in case this has happened. + */ + for(i=0; i<pTab->nCol; i++){ + if( aXRef[i]<0 && i!=pTab->iPKey ){ + sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regNew+i); + sqlite3ColumnDefault(v, pTab, i, regNew+i); + } + } } if( !isView ){ + int j1; /* Address of jump instruction */ /* Do constraint checks. */ sqlite3GenerateConstraintChecks(pParse, pTab, iCur, regNewRowid, aRegIdx, (chngRowid?regOldRowid:0), 1, onError, addr, 0); @@ -90583,11 +90670,11 @@ /* A routine to convert a binary TK_IS or TK_ISNOT expression into a ** unary TK_ISNULL or TK_NOTNULL expression. */ static void binaryToUnaryIfNull(Parse *pParse, Expr *pY, Expr *pA, int op){ sqlite3 *db = pParse->db; if( db->mallocFailed==0 && pY->op==TK_NULL ){ - pA->op = op; + pA->op = (u8)op; sqlite3ExprDelete(db, pA->pRight); pA->pRight = 0; } } @@ -97946,11 +98033,558 @@ #if defined(SQLITE_ENABLE_FTS3) && !defined(SQLITE_CORE) # define SQLITE_CORE 1 #endif -#include "fts3Int.h" +/************** Include fts3Int.h in the middle of fts3.c ********************/ +/************** Begin file fts3Int.h *****************************************/ +/* +** 2009 Nov 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. +** +****************************************************************************** +** +*/ + +#ifndef _FTSINT_H +#define _FTSINT_H + +#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) +# define NDEBUG 1 +#endif + +/************** Include fts3_tokenizer.h in the middle of fts3Int.h **********/ +/************** Begin file fts3_tokenizer.h **********************************/ +/* +** 2006 July 10 +** +** The author disclaims copyright to this source code. +** +************************************************************************* +** Defines the interface to tokenizers used by fulltext-search. There +** are three basic components: +** +** sqlite3_tokenizer_module is a singleton defining the tokenizer +** interface functions. This is essentially the class structure for +** tokenizers. +** +** sqlite3_tokenizer is used to define a particular tokenizer, perhaps +** including customization information defined at creation time. +** +** sqlite3_tokenizer_cursor is generated by a tokenizer to generate +** tokens from a particular input. +*/ +#ifndef _FTS3_TOKENIZER_H_ +#define _FTS3_TOKENIZER_H_ + +/* TODO(shess) Only used for SQLITE_OK and SQLITE_DONE at this time. +** If tokenizers are to be allowed to call sqlite3_*() functions, then +** we will need a way to register the API consistently. +*/ + +/* +** Structures used by the tokenizer interface. When a new tokenizer +** implementation is registered, the caller provides a pointer to +** an sqlite3_tokenizer_module containing pointers to the callback +** functions that make up an implementation. +** +** When an fts3 table is created, it passes any arguments passed to +** the tokenizer clause of the CREATE VIRTUAL TABLE statement to the +** sqlite3_tokenizer_module.xCreate() function of the requested tokenizer +** implementation. The xCreate() function in turn returns an +** sqlite3_tokenizer structure representing the specific tokenizer to +** be used for the fts3 table (customized by the tokenizer clause arguments). +** +** To tokenize an input buffer, the sqlite3_tokenizer_module.xOpen() +** method is called. It returns an sqlite3_tokenizer_cursor object +** that may be used to tokenize a specific input buffer based on +** the tokenization rules supplied by a specific sqlite3_tokenizer +** object. +*/ +typedef struct sqlite3_tokenizer_module sqlite3_tokenizer_module; +typedef struct sqlite3_tokenizer sqlite3_tokenizer; +typedef struct sqlite3_tokenizer_cursor sqlite3_tokenizer_cursor; + +struct sqlite3_tokenizer_module { + + /* + ** Structure version. Should always be set to 0. + */ + int iVersion; + + /* + ** Create a new tokenizer. The values in the argv[] array are the + ** arguments passed to the "tokenizer" clause of the CREATE VIRTUAL + ** TABLE statement that created the fts3 table. For example, if + ** the following SQL is executed: + ** + ** CREATE .. USING fts3( ... , tokenizer <tokenizer-name> arg1 arg2) + ** + ** then argc is set to 2, and the argv[] array contains pointers + ** to the strings "arg1" and "arg2". + ** + ** This method should return either SQLITE_OK (0), or an SQLite error + ** code. If SQLITE_OK is returned, then *ppTokenizer should be set + ** to point at the newly created tokenizer structure. The generic + ** sqlite3_tokenizer.pModule variable should not be initialised by + ** this callback. The caller will do so. + */ + int (*xCreate)( + int argc, /* Size of argv array */ + const char *const*argv, /* Tokenizer argument strings */ + sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */ + ); + + /* + ** Destroy an existing tokenizer. The fts3 module calls this method + ** exactly once for each successful call to xCreate(). + */ + int (*xDestroy)(sqlite3_tokenizer *pTokenizer); + + /* + ** Create a tokenizer cursor to tokenize an input buffer. The caller + ** is responsible for ensuring that the input buffer remains valid + ** until the cursor is closed (using the xClose() method). + */ + int (*xOpen)( + sqlite3_tokenizer *pTokenizer, /* Tokenizer object */ + const char *pInput, int nBytes, /* Input buffer */ + sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */ + ); + + /* + ** Destroy an existing tokenizer cursor. The fts3 module calls this + ** method exactly once for each successful call to xOpen(). + */ + int (*xClose)(sqlite3_tokenizer_cursor *pCursor); + + /* + ** Retrieve the next token from the tokenizer cursor pCursor. This + ** method should either return SQLITE_OK and set the values of the + ** "OUT" variables identified below, or SQLITE_DONE to indicate that + ** the end of the buffer has been reached, or an SQLite error code. + ** + ** *ppToken should be set to point at a buffer containing the + ** normalized version of the token (i.e. after any case-folding and/or + ** stemming has been performed). *pnBytes should be set to the length + ** of this buffer in bytes. The input text that generated the token is + ** identified by the byte offsets returned in *piStartOffset and + ** *piEndOffset. *piStartOffset should be set to the index of the first + ** byte of the token in the input buffer. *piEndOffset should be set + ** to the index of the first byte just past the end of the token in + ** the input buffer. + ** + ** The buffer *ppToken is set to point at is managed by the tokenizer + ** implementation. It is only required to be valid until the next call + ** to xNext() or xClose(). + */ + /* TODO(shess) current implementation requires pInput to be + ** nul-terminated. This should either be fixed, or pInput/nBytes + ** should be converted to zInput. + */ + int (*xNext)( + sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */ + const char **ppToken, int *pnBytes, /* OUT: Normalized text for token */ + int *piStartOffset, /* OUT: Byte offset of token in input buffer */ + int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */ + int *piPosition /* OUT: Number of tokens returned before this one */ + ); +}; + +struct sqlite3_tokenizer { + const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */ + /* Tokenizer implementations will typically add additional fields */ +}; + +struct sqlite3_tokenizer_cursor { + sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */ + /* Tokenizer implementations will typically add additional fields */ +}; + +#endif /* _FTS3_TOKENIZER_H_ */ + +/************** End of fts3_tokenizer.h **************************************/ +/************** Continuing where we left off in fts3Int.h ********************/ +/************** Include fts3_hash.h in the middle of fts3Int.h ***************/ +/************** Begin file fts3_hash.h ***************************************/ +/* +** 2001 September 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 is the header file for the generic hash-table implemenation +** used in SQLite. We've modified it slightly to serve as a standalone +** hash table implementation for the full-text indexing module. +** +*/ +#ifndef _FTS3_HASH_H_ +#define _FTS3_HASH_H_ + +/* Forward declarations of structures. */ +typedef struct Fts3Hash Fts3Hash; +typedef struct Fts3HashElem Fts3HashElem; + +/* A complete hash table is an instance of the following structure. +** The internals of this structure are intended to be opaque -- client +** code should not attempt to access or modify the fields of this structure +** directly. Change this structure only by using the routines below. +** However, many of the "procedures" and "functions" for modifying and +** accessing this structure are really macros, so we can't really make +** this structure opaque. +*/ +struct Fts3Hash { + char keyClass; /* HASH_INT, _POINTER, _STRING, _BINARY */ + char copyKey; /* True if copy of key made on insert */ + int count; /* Number of entries in this table */ + Fts3HashElem *first; /* The first element of the array */ + int htsize; /* Number of buckets in the hash table */ + struct _fts3ht { /* the hash table */ + int count; /* Number of entries with this hash */ + Fts3HashElem *chain; /* Pointer to first entry with this hash */ + } *ht; +}; + +/* Each element in the hash table is an instance of the following +** structure. All elements are stored on a single doubly-linked list. +** +** Again, this structure is intended to be opaque, but it can't really +** be opaque because it is used by macros. +*/ +struct Fts3HashElem { + Fts3HashElem *next, *prev; /* Next and previous elements in the table */ + void *data; /* Data associated with this element */ + void *pKey; int nKey; /* Key associated with this element */ +}; + +/* +** There are 2 different modes of operation for a hash table: +** +** FTS3_HASH_STRING pKey points to a string that is nKey bytes long +** (including the null-terminator, if any). Case +** is respected in comparisons. +** +** FTS3_HASH_BINARY pKey points to binary data nKey bytes long. +** memcmp() is used to compare keys. +** +** A copy of the key is made if the copyKey parameter to fts3HashInit is 1. +*/ +#define FTS3_HASH_STRING 1 +#define FTS3_HASH_BINARY 2 + +/* +** Access routines. To delete, insert a NULL pointer. +*/ +SQLITE_PRIVATE void sqlite3Fts3HashInit(Fts3Hash *pNew, char keyClass, char copyKey); +SQLITE_PRIVATE void *sqlite3Fts3HashInsert(Fts3Hash*, const void *pKey, int nKey, void *pData); +SQLITE_PRIVATE void *sqlite3Fts3HashFind(const Fts3Hash*, const void *pKey, int nKey); +SQLITE_PRIVATE void sqlite3Fts3HashClear(Fts3Hash*); + +/* +** Shorthand for the functions above +*/ +#define fts3HashInit sqlite3Fts3HashInit +#define fts3HashInsert sqlite3Fts3HashInsert +#define fts3HashFind sqlite3Fts3HashFind +#define fts3HashClear sqlite3Fts3HashClear + +/* +** Macros for looping over all elements of a hash table. The idiom is +** like this: +** +** Fts3Hash h; +** Fts3HashElem *p; +** ... +** for(p=fts3HashFirst(&h); p; p=fts3HashNext(p)){ +** SomeStructure *pData = fts3HashData(p); +** // do something with pData +** } +*/ +#define fts3HashFirst(H) ((H)->first) +#define fts3HashNext(E) ((E)->next) +#define fts3HashData(E) ((E)->data) +#define fts3HashKey(E) ((E)->pKey) +#define fts3HashKeysize(E) ((E)->nKey) + +/* +** Number of entries in a hash table +*/ +#define fts3HashCount(H) ((H)->count) + +#endif /* _FTS3_HASH_H_ */ + +/************** End of fts3_hash.h *******************************************/ +/************** Continuing where we left off in fts3Int.h ********************/ + +/* +** This constant controls how often segments are merged. Once there are +** FTS3_MERGE_COUNT segments of level N, they are merged into a single +** segment of level N+1. +*/ +#define FTS3_MERGE_COUNT 16 + +/* +** This is the maximum amount of data (in bytes) to store in the +** Fts3Table.pendingTerms hash table. Normally, the hash table is +** populated as documents are inserted/updated/deleted in a transaction +** and used to create a new segment when the transaction is committed. +** However if this limit is reached midway through a transaction, a new +** segment is created and the hash table cleared immediately. +*/ +#define FTS3_MAX_PENDING_DATA (1*1024*1024) + +/* +** Macro to return the number of elements in an array. SQLite has a +** similar macro called ArraySize(). Use a different name to avoid +** a collision when building an amalgamation with built-in FTS3. +*/ +#define SizeofArray(X) ((int)(sizeof(X)/sizeof(X[0]))) + +/* +** Maximum length of a varint encoded integer. The varint format is different +** from that used by SQLite, so the maximum length is 10, not 9. +*/ +#define FTS3_VARINT_MAX 10 + +/* +** This section provides definitions to allow the +** FTS3 extension to be compiled outside of the +** amalgamation. +*/ +#ifndef SQLITE_AMALGAMATION +/* +** Macros indicating that conditional expressions are always true or +** false. +*/ +# define ALWAYS(x) (x) +# define NEVER(X) (x) +/* +** Internal types used by SQLite. +*/ +typedef unsigned char u8; /* 1-byte (or larger) unsigned integer */ +typedef short int i16; /* 2-byte (or larger) signed integer */ +/* +** Macro used to suppress compiler warnings for unused parameters. +*/ +#define UNUSED_PARAMETER(x) (void)(x) +#endif + +typedef struct Fts3Table Fts3Table; +typedef struct Fts3Cursor Fts3Cursor; +typedef struct Fts3Expr Fts3Expr; +typedef struct Fts3Phrase Fts3Phrase; +typedef struct Fts3SegReader Fts3SegReader; +typedef struct Fts3SegFilter Fts3SegFilter; + +/* +** A connection to a fulltext index is an instance of the following +** structure. The xCreate and xConnect methods create an instance +** of this structure and xDestroy and xDisconnect free that instance. +** All other methods receive a pointer to the structure as one of their +** arguments. +*/ +struct Fts3Table { + sqlite3_vtab base; /* Base class used by SQLite core */ + sqlite3 *db; /* The database connection */ + const char *zDb; /* logical database name */ + const char *zName; /* virtual table name */ + int nColumn; /* number of named columns in virtual table */ + char **azColumn; /* column names. malloced */ + sqlite3_tokenizer *pTokenizer; /* tokenizer for inserts and queries */ + + /* Precompiled statements used by the implementation. Each of these + ** statements is run and reset within a single virtual table API call. + */ + sqlite3_stmt *aStmt[18]; + + /* Pointer to string containing the SQL: + ** + ** "SELECT block FROM %_segments WHERE blockid BETWEEN ? AND ? + ** ORDER BY blockid" + */ + char *zSelectLeaves; + int nLeavesStmt; /* Valid statements in aLeavesStmt */ + int nLeavesTotal; /* Total number of prepared leaves stmts */ + int nLeavesAlloc; /* Allocated size of aLeavesStmt */ + sqlite3_stmt **aLeavesStmt; /* Array of prepared zSelectLeaves stmts */ + + int nNodeSize; /* Soft limit for node size */ + + /* The following hash table is used to buffer pending index updates during + ** transactions. Variable nPendingData estimates the memory size of the + ** pending data, including hash table overhead, but not malloc overhead. + ** When nPendingData exceeds FTS3_MAX_PENDING_DATA, the buffer is flushed + ** automatically. Variable iPrevDocid is the docid of the most recently + ** inserted record. + */ + int nPendingData; + sqlite_int64 iPrevDocid; + Fts3Hash pendingTerms; +}; + +/* +** When the core wants to read from the virtual table, it creates a +** virtual table cursor (an instance of the following structure) using +** the xOpen method. Cursors are destroyed using the xClose method. +*/ +struct Fts3Cursor { + sqlite3_vtab_cursor base; /* Base class used by SQLite core */ + i16 eSearch; /* Search strategy (see below) */ + u8 isEof; /* True if at End Of Results */ + u8 isRequireSeek; /* True if must seek pStmt to %_content row */ + sqlite3_stmt *pStmt; /* Prepared statement in use by the cursor */ + Fts3Expr *pExpr; /* Parsed MATCH query string */ + sqlite3_int64 iPrevId; /* Previous id read from aDoclist */ + char *pNextId; /* Pointer into the body of aDoclist */ + char *aDoclist; /* List of docids for full-text queries */ + int nDoclist; /* Size of buffer at aDoclist */ +}; + +/* +** The Fts3Cursor.eSearch member is always set to one of the following. +** Actualy, Fts3Cursor.eSearch can be greater than or equal to +** FTS3_FULLTEXT_SEARCH. If so, then Fts3Cursor.eSearch - 2 is the index +** of the column to be searched. For example, in +** +** CREATE VIRTUAL TABLE ex1 USING fts3(a,b,c,d); +** SELECT docid FROM ex1 WHERE b MATCH 'one two three'; +** +** Because the LHS of the MATCH operator is 2nd column "b", +** Fts3Cursor.eSearch will be set to FTS3_FULLTEXT_SEARCH+1. (+0 for a, +** +1 for b, +2 for c, +3 for d.) If the LHS of MATCH were "ex1" +** indicating that all columns should be searched, +** then eSearch would be set to FTS3_FULLTEXT_SEARCH+4. +*/ +#define FTS3_FULLSCAN_SEARCH 0 /* Linear scan of %_content table */ +#define FTS3_DOCID_SEARCH 1 /* Lookup by rowid on %_content table */ +#define FTS3_FULLTEXT_SEARCH 2 /* Full-text index search */ + +/* +** A "phrase" is a sequence of one or more tokens that must match in +** sequence. A single token is the base case and the most common case. +** For a sequence of tokens contained in "...", nToken will be the number +** of tokens in the string. +*/ +struct Fts3Phrase { + int nToken; /* Number of tokens in the phrase */ + int iColumn; /* Index of column this phrase must match */ + int isNot; /* Phrase prefixed by unary not (-) operator */ + struct PhraseToken { + char *z; /* Text of the token */ + int n; /* Number of bytes in buffer pointed to by z */ + int isPrefix; /* True if token ends in with a "*" character */ + } aToken[1]; /* One entry for each token in the phrase */ +}; + +/* +** A tree of these objects forms the RHS of a MATCH operator. +*/ +struct Fts3Expr { + int eType; /* One of the FTSQUERY_XXX values defined below */ + int nNear; /* Valid if eType==FTSQUERY_NEAR */ + Fts3Expr *pParent; /* pParent->pLeft==this or pParent->pRight==this */ + Fts3Expr *pLeft; /* Left operand */ + Fts3Expr *pRight; /* Right operand */ + Fts3Phrase *pPhrase; /* Valid if eType==FTSQUERY_PHRASE */ +}; + +/* +** Candidate values for Fts3Query.eType. Note that the order of the first +** four values is in order of precedence when parsing expressions. For +** example, the following: +** +** "a OR b AND c NOT d NEAR e" +** +** is equivalent to: +** +** "a OR (b AND (c NOT (d NEAR e)))" +*/ +#define FTSQUERY_NEAR 1 +#define FTSQUERY_NOT 2 +#define FTSQUERY_AND 3 +#define FTSQUERY_OR 4 +#define FTSQUERY_PHRASE 5 + + +/* fts3_init.c */ +SQLITE_PRIVATE int sqlite3Fts3DeleteVtab(int, sqlite3_vtab *); +SQLITE_PRIVATE int sqlite3Fts3InitVtab(int, sqlite3*, void*, int, const char*const*, + sqlite3_vtab **, char **); + +/* fts3_write.c */ +SQLITE_PRIVATE int sqlite3Fts3UpdateMethod(sqlite3_vtab*,int,sqlite3_value**,sqlite3_int64*); +SQLITE_PRIVATE int sqlite3Fts3PendingTermsFlush(Fts3Table *); +SQLITE_PRIVATE void sqlite3Fts3PendingTermsClear(Fts3Table *); +SQLITE_PRIVATE int sqlite3Fts3Optimize(Fts3Table *); +SQLITE_PRIVATE int sqlite3Fts3SegReaderNew(Fts3Table *,int, sqlite3_int64, + sqlite3_int64, sqlite3_int64, const char *, int, Fts3SegReader**); +SQLITE_PRIVATE void sqlite3Fts3SegReaderFree(Fts3Table *, Fts3SegReader *); +SQLITE_PRIVATE int sqlite3Fts3SegReaderIterate( + Fts3Table *, Fts3SegReader **, int, Fts3SegFilter *, + int (*)(Fts3Table *, void *, char *, int, char *, int), void * +); +SQLITE_PRIVATE int sqlite3Fts3ReadBlock(Fts3Table*, sqlite3_int64, char const**, int*); +SQLITE_PRIVATE int sqlite3Fts3AllSegdirs(Fts3Table*, sqlite3_stmt **); + +/* Flags allowed as part of the 4th argument to SegmentReaderIterate() */ +#define FTS3_SEGMENT_REQUIRE_POS 0x00000001 +#define FTS3_SEGMENT_IGNORE_EMPTY 0x00000002 +#define FTS3_SEGMENT_COLUMN_FILTER 0x00000004 +#define FTS3_SEGMENT_PREFIX 0x00000008 + +/* Type passed as 4th argument to SegmentReaderIterate() */ +struct Fts3SegFilter { + const char *zTerm; + int nTerm; + int iCol; + int flags; +}; + +/* fts3.c */ +SQLITE_PRIVATE int sqlite3Fts3PutVarint(char *, sqlite3_int64); +SQLITE_PRIVATE int sqlite3Fts3GetVarint(const char *, sqlite_int64 *); +SQLITE_PRIVATE int sqlite3Fts3GetVarint32(const char *, int *); +SQLITE_PRIVATE int sqlite3Fts3VarintLen(sqlite3_uint64); +SQLITE_PRIVATE void sqlite3Fts3Dequote(char *); + +/* fts3_tokenizer.c */ +SQLITE_PRIVATE const char *sqlite3Fts3NextToken(const char *, int *); +SQLITE_PRIVATE int sqlite3Fts3InitHashTable(sqlite3 *, Fts3Hash *, const char *); +SQLITE_PRIVATE int sqlite3Fts3InitTokenizer(Fts3Hash *pHash, + const char *, sqlite3_tokenizer **, const char **, char ** +); + +/* fts3_snippet.c */ +SQLITE_PRIVATE void sqlite3Fts3Offsets(sqlite3_context*, Fts3Cursor*); +SQLITE_PRIVATE void sqlite3Fts3Snippet(sqlite3_context*, Fts3Cursor*, + const char *, const char *, const char * +); + +/* fts3_expr.c */ +SQLITE_PRIVATE int sqlite3Fts3ExprParse(sqlite3_tokenizer *, + char **, int, int, const char *, int, Fts3Expr ** +); +SQLITE_PRIVATE void sqlite3Fts3ExprFree(Fts3Expr *); +#ifdef SQLITE_TEST +SQLITE_PRIVATE void sqlite3Fts3ExprInitTestInterface(sqlite3 *db); +#endif + +#endif /* _FTSINT_H */ + +/************** End of fts3Int.h *********************************************/ +/************** Continuing where we left off in fts3.c ***********************/ #ifndef SQLITE_CORE SQLITE_EXTENSION_INIT1 #endif @@ -98108,11 +98742,11 @@ default: return; } for(i=1, j=0; z[i]; i++){ if( z[i]==quote ){ if( z[i+1]==quote ){ - z[j++] = quote; + z[j++] = (char)quote; i++; }else{ z[j++] = 0; break; } @@ -98132,22 +98766,10 @@ if( *pp>=pEnd ){ *pp = 0; }else{ fts3GetDeltaVarint(pp, pVal); } -} - - -/* -** The Fts3Cursor.eType member is always set to one of the following. -*/ -#define FTS3_FULLSCAN_SEARCH 0 /* Linear scan of %_content table */ -#define FTS3_DOCID_SEARCH 1 /* Lookup by rowid on %_content table */ -#define FTS3_FULLTEXT_SEARCH 2 /* Full-text index search */ - -static Fts3Table *cursor_vtab(Fts3Cursor *c){ - return (Fts3Table *) c->base.pVtab; } /* ** The xDisconnect() virtual table method. */ @@ -98295,11 +98917,11 @@ ** The argv[] array contains the following: ** ** argv[0] -> module name ** argv[1] -> database name ** argv[2] -> table name -** argv[...] -> "column name" fields... +** argv[...] -> "column name" and other module argument fields. */ int fts3InitVtab( int isCreate, /* True for xCreate, false for xConnect */ sqlite3 *db, /* The SQLite database connection */ void *pAux, /* Hash table containing tokenizers */ @@ -98307,43 +98929,55 @@ const char * const *argv, /* xCreate/xConnect argument array */ sqlite3_vtab **ppVTab, /* Write the resulting vtab structure here */ char **pzErr /* Write any error message here */ ){ Fts3Hash *pHash = (Fts3Hash *)pAux; - Fts3Table *p; /* Pointer to allocated vtab */ + Fts3Table *p; /* Pointer to allocated vtab */ int rc; /* Return code */ - int i; - int nByte; + int i; /* Iterator variable */ + int nByte; /* Size of allocation used for *p */ int iCol; int nString = 0; int nCol = 0; char *zCsr; int nDb; int nName; - const char *zTokenizer = 0; - sqlite3_tokenizer *pTokenizer; /* Tokenizer for this table */ - - nDb = strlen(argv[1]) + 1; - nName = strlen(argv[2]) + 1; + const char *zTokenizer = 0; /* Name of tokenizer to use */ + sqlite3_tokenizer *pTokenizer = 0; /* Tokenizer for this table */ + +#ifdef SQLITE_TEST + const char *zTestParam = 0; + if( strncmp(argv[argc-1], "test:", 5)==0 ){ + zTestParam = argv[argc-1]; + argc--; + } +#endif + + nDb = (int)strlen(argv[1]) + 1; + nName = (int)strlen(argv[2]) + 1; for(i=3; i<argc; i++){ char const *z = argv[i]; rc = sqlite3Fts3InitTokenizer(pHash, z, &pTokenizer, &zTokenizer, pzErr); if( rc!=SQLITE_OK ){ return rc; } if( z!=zTokenizer ){ - nString += strlen(z) + 1; + nString += (int)(strlen(z) + 1); } } nCol = argc - 3 - (zTokenizer!=0); if( zTokenizer==0 ){ rc = sqlite3Fts3InitTokenizer(pHash, 0, &pTokenizer, 0, pzErr); if( rc!=SQLITE_OK ){ return rc; } assert( pTokenizer ); + } + + if( nCol==0 ){ + nCol = 1; } /* Allocate and populate the Fts3Table structure. */ nByte = sizeof(Fts3Table) + /* Fts3Table */ nCol * sizeof(char *) + /* azColumn */ @@ -98360,10 +98994,11 @@ p->db = db; p->nColumn = nCol; p->nPendingData = 0; p->azColumn = (char **)&p[1]; p->pTokenizer = pTokenizer; + p->nNodeSize = 1000; zCsr = (char *)&p->azColumn[nCol]; fts3HashInit(&p->pendingTerms, FTS3_HASH_STRING, 1); /* Fill in the zName and zDb fields of the vtab structure. */ @@ -98387,10 +99022,14 @@ p->azColumn[iCol++] = zCsr; zCsr += n+1; assert( zCsr <= &((char *)p)[nByte] ); } } + if( iCol==0 ){ + assert( nCol==1 ); + p->azColumn[0] = "content"; + } /* If this is an xCreate call, create the underlying tables in the ** database. TODO: For xConnect(), it could verify that said tables exist. */ if( isCreate ){ @@ -98399,16 +99038,25 @@ } rc = fts3DeclareVtab(p); if( rc!=SQLITE_OK ) goto fts3_init_out; +#ifdef SQLITE_TEST + if( zTestParam ){ + p->nNodeSize = atoi(&zTestParam[5]); + } +#endif *ppVTab = &p->base; fts3_init_out: - if( rc!=SQLITE_OK ){ - if( p ) fts3DisconnectMethod((sqlite3_vtab *)p); - else if( pTokenizer ) pTokenizer->pModule->xDestroy(pTokenizer); + assert( p || (pTokenizer && rc!=SQLITE_OK) ); + if( rc!=SQLITE_OK ){ + if( p ){ + fts3DisconnectMethod((sqlite3_vtab *)p); + }else{ + pTokenizer->pModule->xDestroy(pTokenizer); + } } return rc; } /* @@ -98497,10 +99145,12 @@ /* ** Implementation of xOpen method. */ static int fts3OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){ sqlite3_vtab_cursor *pCsr; /* Allocated cursor */ + + UNUSED_PARAMETER(pVTab); /* Allocate a buffer large enough for an Fts3Cursor structure. If the ** allocation succeeds, zero it and return SQLITE_OK. Otherwise, ** if the allocation fails, return SQLITE_NOMEM. */ @@ -98664,16 +99314,20 @@ } sqlite3_free(zBuffer); return rc; } +/* +** This function is used to create delta-encoded serialized lists of FTS3 +** varints. Each call to this function appends a single varint to a list. +*/ static void fts3PutDeltaVarint( - char **pp, - sqlite3_int64 *piPrev, - sqlite3_int64 iVal -){ - assert( iVal-*piPrev > 0 ); + char **pp, /* IN/OUT: Output pointer */ + sqlite3_int64 *piPrev, /* IN/OUT: Previous value written to list */ + sqlite3_int64 iVal /* Write this value to the list */ +){ + assert( iVal-*piPrev > 0 || (*piPrev==0 && iVal==0) ); *pp += sqlite3Fts3PutVarint(*pp, iVal-*piPrev); *piPrev = iVal; } static void fts3PoslistCopy(char **pp, char **ppPoslist){ @@ -98680,11 +99334,11 @@ char *pEnd = *ppPoslist; char c = 0; while( *pEnd | c ) c = *pEnd++ & 0x80; pEnd++; if( pp ){ - int n = pEnd - *ppPoslist; + int n = (int)(pEnd - *ppPoslist); char *p = *pp; memcpy(p, *ppPoslist, n); p += n; *pp = p; } @@ -98692,19 +99346,69 @@ } static void fts3ColumnlistCopy(char **pp, char **ppPoslist){ char *pEnd = *ppPoslist; char c = 0; + + /* A column-list is terminated by either a 0x01 or 0x00. */ while( 0xFE & (*pEnd | c) ) c = *pEnd++ & 0x80; if( pp ){ - int n = pEnd - *ppPoslist; + int n = (int)(pEnd - *ppPoslist); char *p = *pp; memcpy(p, *ppPoslist, n); p += n; *pp = p; } *ppPoslist = pEnd; +} + +/* +** Value used to signify the end of an offset-list. This is safe because +** it is not possible to have a document with 2^31 terms. +*/ +#define OFFSET_LIST_END 0x7fffffff + +/* +** This function is used to help parse offset-lists. When this function is +** called, *pp may point to the start of the next varint in the offset-list +** being parsed, or it may point to 1 byte past the end of the offset-list +** (in which case **pp will be 0x00 or 0x01). +** +** If *pp points past the end of the current offset list, set *pi to +** OFFSET_LIST_END and return. Otherwise, read the next varint from *pp, +** increment the current value of *pi by the value read, and set *pp to +** point to the next value before returning. +*/ +static void fts3ReadNextPos( + char **pp, /* IN/OUT: Pointer into offset-list buffer */ + sqlite3_int64 *pi /* IN/OUT: Value read from offset-list */ +){ + if( **pp&0xFE ){ + fts3GetDeltaVarint(pp, pi); + *pi -= 2; + }else{ + *pi = OFFSET_LIST_END; + } +} + +/* +** If parameter iCol is not 0, write an 0x01 byte followed by the value of +** iCol encoded as a varint to *pp. +** +** Set *pp to point to the byte just after the last byte written before +** returning (do not modify it if iCol==0). Return the total number of bytes +** written (0 if iCol==0). +*/ +static int fts3PutColNumber(char **pp, int iCol){ + int n = 0; /* Number of bytes written */ + if( iCol ){ + char *p = *pp; /* Output pointer */ + n = 1 + sqlite3Fts3PutVarint(&p[1], iCol); + *p = 0x01; + *pp = &p[n]; + } + return n; } /* ** */ @@ -98715,44 +99419,57 @@ ){ char *p = *pp; char *p1 = *pp1; char *p2 = *pp2; - while( *p1 && *p2 ){ - int iCol1 = 0; - int iCol2 = 0; + while( *p1 || *p2 ){ + int iCol1; + int iCol2; + if( *p1==0x01 ) sqlite3Fts3GetVarint32(&p1[1], &iCol1); + else if( *p1==0x00 ) iCol1 = OFFSET_LIST_END; + else iCol1 = 0; + if( *p2==0x01 ) sqlite3Fts3GetVarint32(&p2[1], &iCol2); + else if( *p2==0x00 ) iCol2 = OFFSET_LIST_END; + else iCol2 = 0; if( iCol1==iCol2 ){ sqlite3_int64 i1 = 0; sqlite3_int64 i2 = 0; sqlite3_int64 iPrev = 0; - if( iCol1!=0 ){ - int n; - *p++ = 0x01; - n = sqlite3Fts3PutVarint(p, iCol1); - p += n; - p1 += 1 + n; - p2 += 1 + n; - } - while( (*p1&0xFE) || (*p2&0xFE) ){ + int n = fts3PutColNumber(&p, iCol1); + p1 += n; + p2 += n; + + /* At this point, both p1 and p2 point to the start of offset-lists. + ** An offset-list is a list of non-negative delta-encoded varints, each + ** incremented by 2 before being stored. Each list is terminated by a 0 + ** or 1 value (0x00 or 0x01). The following block merges the two lists + ** and writes the results to buffer p. p is left pointing to the byte + ** after the list written. No terminator (0x00 or 0x01) is written to + ** the output. + */ + fts3GetDeltaVarint(&p1, &i1); + fts3GetDeltaVarint(&p2, &i2); + do { + fts3PutDeltaVarint(&p, &iPrev, (i1<i2) ? i1 : i2); + iPrev -= 2; if( i1==i2 ){ - fts3GetDeltaVarint(&p1, &i1); i1 -= 2; - fts3GetDeltaVarint(&p2, &i2); i2 -= 2; + fts3ReadNextPos(&p1, &i1); + fts3ReadNextPos(&p2, &i2); }else if( i1<i2 ){ - fts3GetDeltaVarint(&p1, &i1); i1 -= 2; - }else{ - fts3GetDeltaVarint(&p2, &i2); i2 -= 2; - } - fts3PutDeltaVarint(&p, &iPrev, (i1<i2 ? i1 : i2) + 2); iPrev -= 2; - if( 0==(*p1&0xFE) ) i1 = 0x7FFFFFFF; - if( 0==(*p2&0xFE) ) i2 = 0x7FFFFFFF; - } + fts3ReadNextPos(&p1, &i1); + }else{ + fts3ReadNextPos(&p2, &i2); + } + }while( i1!=OFFSET_LIST_END || i2!=OFFSET_LIST_END ); }else if( iCol1<iCol2 ){ + p1 += fts3PutColNumber(&p, iCol1); fts3ColumnlistCopy(&p, &p1); }else{ + p2 += fts3PutColNumber(&p, iCol2); fts3ColumnlistCopy(&p, &p2); } } *p++ = '\0'; @@ -98815,17 +99532,18 @@ } iSave = isSaveLeft ? iPos1 : iPos2; fts3PutDeltaVarint(&p, &iPrev, iSave+2); iPrev -= 2; pSave = 0; } - if( iPos2<=iPos1 ){ + if( (!isSaveLeft && iPos2<=(iPos1+nToken)) || iPos2<=iPos1 ){ if( (*p2&0xFE)==0 ) break; fts3GetDeltaVarint(&p2, &iPos2); iPos2 -= 2; }else{ if( (*p1&0xFE)==0 ) break; fts3GetDeltaVarint(&p1, &iPos1); iPos1 -= 2; } + } if( pSave && pp ){ p = pSave; } @@ -98868,17 +99586,20 @@ *p++ = 0x00; *pp = p; return 1; } +/* +** Merge two position-lists as required by the NEAR operator. +*/ static int fts3PoslistNearMerge( char **pp, /* Output buffer */ char *aTmp, /* Temporary buffer space */ int nRight, /* Maximum difference in token positions */ int nLeft, /* Maximum difference in token positions */ - char **pp1, /* Left input list */ - char **pp2 /* Right input list */ + char **pp1, /* IN/OUT: Left input list */ + char **pp2 /* IN/OUT: Right input list */ ){ char *p1 = *pp1; char *p2 = *pp2; if( !pp ){ @@ -98961,10 +99682,14 @@ ); if( !aBuffer ){ return SQLITE_NOMEM; } + if( n1==0 && n2==0 ){ + *pnBuffer = 0; + return SQLITE_OK; + } /* Read the first docid from each doclist */ fts3GetDeltaVarint2(&p1, pEnd1, &i1); fts3GetDeltaVarint2(&p2, pEnd2, &i2); @@ -99040,12 +99765,11 @@ } } break; } - case MERGE_POS_NEAR: - case MERGE_NEAR: { + default: assert( mergetype==MERGE_POS_NEAR || mergetype==MERGE_NEAR ); { char *aTmp = 0; char **ppPos = 0; if( mergetype==MERGE_POS_NEAR ){ ppPos = &p; aTmp = sqlite3_malloc(2*(n1+n2)); @@ -99076,16 +99800,13 @@ } } sqlite3_free(aTmp); break; } - - default: - assert(!"Invalid mergetype value passed to fts3DoclistMerge()"); - } - - *pnBuffer = (p-aBuffer); + } + + *pnBuffer = (int)(p-aBuffer); return SQLITE_OK; } /* ** A pointer to an instance of this structure is used as the context @@ -99112,10 +99833,14 @@ int nDoclist ){ TermSelect *pTS = (TermSelect *)pContext; int nNew = pTS->nOutput + nDoclist; char *aNew = sqlite3_malloc(nNew); + + UNUSED_PARAMETER(p); + UNUSED_PARAMETER(zTerm); + UNUSED_PARAMETER(nTerm); if( !aNew ){ return SQLITE_NOMEM; } @@ -99194,25 +99919,38 @@ ** leaf). Do not bother inspecting any data in this case, just ** create a Fts3SegReader to scan the single leaf. */ rc = sqlite3Fts3SegReaderNew(p, iAge, 0, 0, 0, zRoot, nRoot, &pNew); }else{ - sqlite3_int64 i1; + int rc2; /* Return value of sqlite3Fts3ReadBlock() */ + sqlite3_int64 i1; /* Blockid of leaf that may contain zTerm */ rc = fts3SelectLeaf(p, zTerm, nTerm, zRoot, nRoot, &i1); if( rc==SQLITE_OK ){ sqlite3_int64 i2 = sqlite3_column_int64(pStmt, 2); rc = sqlite3Fts3SegReaderNew(p, iAge, i1, i2, 0, 0, 0, &pNew); } + + /* The following call to ReadBlock() serves to reset the SQL statement + ** used to retrieve blocks of data from the %_segments table. If it is + ** not reset here, then it may remain classified as an active statement + ** by SQLite, which may lead to "DROP TABLE" or "DETACH" commands + ** failing. + */ + rc2 = sqlite3Fts3ReadBlock(p, 0, 0, 0); + if( rc==SQLITE_OK ){ + rc = rc2; + } } iAge++; /* If a new Fts3SegReader was allocated, add it to the apSegment array. */ - assert( (rc==SQLITE_OK)==(pNew!=0) ); + assert( pNew!=0 || rc!=SQLITE_OK ); if( pNew ){ if( nSegment==nAlloc ){ + Fts3SegReader **pArray; nAlloc += 16; - Fts3SegReader **pArray = (Fts3SegReader **)sqlite3_realloc( + pArray = (Fts3SegReader **)sqlite3_realloc( apSegment, nAlloc*sizeof(Fts3SegReader *) ); if( !pArray ){ sqlite3Fts3SegReaderFree(p, pNew); rc = SQLITE_NOMEM; @@ -99348,10 +100086,13 @@ int nRight; if( SQLITE_OK==(rc = evalFts3Expr(p, pExpr->pRight, &aRight, &nRight)) && SQLITE_OK==(rc = evalFts3Expr(p, pExpr->pLeft, &aLeft, &nLeft)) ){ + assert( pExpr->eType==FTSQUERY_NEAR || pExpr->eType==FTSQUERY_OR + || pExpr->eType==FTSQUERY_AND || pExpr->eType==FTSQUERY_NOT + ); switch( pExpr->eType ){ case FTSQUERY_NEAR: { Fts3Expr *pLeft; Fts3Expr *pRight; int mergetype = MERGE_NEAR; @@ -99398,12 +100139,11 @@ *paOut = aBuffer; sqlite3_free(aLeft); break; } - case FTSQUERY_AND: - case FTSQUERY_NOT: { + default: { assert( FTSQUERY_NOT==MERGE_NOT && FTSQUERY_AND==MERGE_AND ); fts3DoclistMerge(pExpr->eType, 0, 0, aLeft, pnOut, aLeft, nLeft, aRight, nRight ); *paOut = aLeft; @@ -99453,10 +100193,13 @@ int rc; /* Return code */ char *zSql; /* SQL statement used to access %_content */ Fts3Table *p = (Fts3Table *)pCursor->pVtab; Fts3Cursor *pCsr = (Fts3Cursor *)pCursor; + UNUSED_PARAMETER(idxStr); + UNUSED_PARAMETER(nVal); + assert( idxNum>=0 && idxNum<=(FTS3_FULLTEXT_SEARCH+p->nColumn) ); assert( nVal==0 || nVal==1 ); assert( (nVal==0)==(idxNum==FTS3_FULLSCAN_SEARCH) ); /* In case the cursor has been used before, clear it now. */ @@ -99475,18 +100218,21 @@ }else{ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0); sqlite3_free(zSql); } if( rc!=SQLITE_OK ) return rc; - pCsr->eType = idxNum; + pCsr->eSearch = (i16)idxNum; if( idxNum==FTS3_DOCID_SEARCH ){ rc = sqlite3_bind_value(pCsr->pStmt, 1, apVal[0]); }else if( idxNum!=FTS3_FULLSCAN_SEARCH ){ int iCol = idxNum-FTS3_FULLTEXT_SEARCH; const char *zQuery = (const char *)sqlite3_value_text(apVal[0]); + if( zQuery==0 && sqlite3_value_type(apVal[0])!=SQLITE_NULL ){ + return SQLITE_NOMEM; + } rc = sqlite3Fts3PendingTermsFlush(p); if( rc!=SQLITE_OK ) return rc; rc = sqlite3Fts3ExprParse(p->pTokenizer, p->azColumn, p->nColumn, iCol, zQuery, -1, &pCsr->pExpr @@ -99509,42 +100255,10 @@ static int fts3EofMethod(sqlite3_vtab_cursor *pCursor){ return ((Fts3Cursor *)pCursor)->isEof; } /* -** This is the xColumn method of the virtual table. The SQLite -** core calls this method during a query when it needs the value -** of a column from the virtual table. This method needs to use -** one of the sqlite3_result_*() routines to store the requested -** value back in the pContext. -*/ -static int fts3ColumnMethod(sqlite3_vtab_cursor *pCursor, - sqlite3_context *pContext, int idxCol){ - Fts3Cursor *c = (Fts3Cursor *) pCursor; - Fts3Table *v = cursor_vtab(c); - int rc = fts3CursorSeek(c); - if( rc!=SQLITE_OK ){ - return rc; - } - - if( idxCol<v->nColumn ){ - sqlite3_value *pVal = sqlite3_column_value(c->pStmt, idxCol+1); - sqlite3_result_value(pContext, pVal); - }else if( idxCol==v->nColumn ){ - /* The extra column whose name is the same as the table. - ** Return a blob which is a pointer to the cursor - */ - sqlite3_result_blob(pContext, &c, sizeof(c), SQLITE_TRANSIENT); - }else if( idxCol==v->nColumn+1 ){ - /* The docid column, which is an alias for rowid. */ - sqlite3_value *pVal = sqlite3_column_value(c->pStmt, 0); - sqlite3_result_value(pContext, pVal); - } - return SQLITE_OK; -} - -/* ** This is the xRowid method. The SQLite core calls this routine to ** retrieve the rowid for the current row of the result set. fts3 ** exposes %_content.docid as the rowid for the virtual table. The ** rowid should be written to *pRowid. */ @@ -99554,10 +100268,47 @@ *pRowid = pCsr->iPrevId; }else{ *pRowid = sqlite3_column_int64(pCsr->pStmt, 0); } return SQLITE_OK; +} + +/* +** This is the xColumn method, called by SQLite to request a value from +** the row that the supplied cursor currently points to. +*/ +static int fts3ColumnMethod( + sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */ + sqlite3_context *pContext, /* Context for sqlite3_result_xxx() calls */ + int iCol /* Index of column to read value from */ +){ + int rc; /* Return Code */ + Fts3Cursor *pCsr = (Fts3Cursor *) pCursor; + Fts3Table *p = (Fts3Table *)pCursor->pVtab; + + /* The column value supplied by SQLite must be in range. */ + assert( iCol>=0 && iCol<=p->nColumn+1 ); + + rc = fts3CursorSeek(pCsr); + if( rc==SQLITE_OK ){ + if( iCol==p->nColumn+1 ){ + /* This call is a request for the "docid" column. Since "docid" is an + ** alias for "rowid", use the xRowid() method to obtain the value. + */ + sqlite3_int64 iRowid; + rc = fts3RowidMethod(pCursor, &iRowid); + sqlite3_result_int64(pContext, iRowid); + }else if( iCol==p->nColumn ){ + /* The extra column whose name is the same as the table. + ** Return a blob which is a pointer to the cursor. + */ + sqlite3_result_blob(pContext, &pCsr, sizeof(pCsr), SQLITE_TRANSIENT); + }else{ + sqlite3_result_value(pContext, sqlite3_column_value(pCsr->pStmt, iCol+1)); + } + } + return rc; } /* ** This function is the implementation of the xUpdate callback used by ** FTS3 virtual tables. It is invoked by SQLite each time a row is to be @@ -99582,10 +100333,11 @@ /* ** Implementation of xBegin() method. This is a no-op. */ static int fts3BeginMethod(sqlite3_vtab *pVtab){ + UNUSED_PARAMETER(pVtab); assert( ((Fts3Table *)pVtab)->nPendingData==0 ); return SQLITE_OK; } /* @@ -99592,10 +100344,11 @@ ** Implementation of xCommit() method. This is a no-op. The contents of ** the pending-terms hash-table have already been flushed into the database ** by fts3SyncMethod(). */ static int fts3CommitMethod(sqlite3_vtab *pVtab){ + UNUSED_PARAMETER(pVtab); assert( ((Fts3Table *)pVtab)->nPendingData==0 ); return SQLITE_OK; } /* @@ -99623,11 +100376,11 @@ sqlite3_value *pVal, /* argv[0] passed to function */ Fts3Cursor **ppCsr /* OUT: Store cursor handle here */ ){ Fts3Cursor *pRet; if( sqlite3_value_type(pVal)!=SQLITE_BLOB - && sqlite3_value_bytes(pVal)!=sizeof(Fts3Cursor *) + || sqlite3_value_bytes(pVal)!=sizeof(Fts3Cursor *) ){ char *zErr = sqlite3_mprintf("illegal first argument to %s", zFunc); sqlite3_result_error(pContext, zErr, -1); sqlite3_free(zErr); return SQLITE_ERROR; @@ -99639,26 +100392,35 @@ /* ** Implementation of the snippet() function for FTS3 */ static void fts3SnippetFunc( - sqlite3_context *pContext, - int argc, - sqlite3_value **argv + sqlite3_context *pContext, /* SQLite function call context */ + int nVal, /* Size of apVal[] array */ + sqlite3_value **apVal /* Array of arguments */ ){ Fts3Cursor *pCsr; /* Cursor handle passed through apVal[0] */ const char *zStart = "<b>"; const char *zEnd = "</b>"; const char *zEllipsis = "<b>...</b>"; - if( argc<1 || argc>4 ) return; - if( fts3FunctionArg(pContext, "snippet", argv[0], &pCsr) ) return; - - switch( argc ){ - case 4: zEllipsis = (const char*)sqlite3_value_text(argv[3]); - case 3: zEnd = (const char*)sqlite3_value_text(argv[2]); - case 2: zStart = (const char*)sqlite3_value_text(argv[1]); + /* There must be at least one argument passed to this function (otherwise + ** the non-overloaded version would have been called instead of this one). + */ + assert( nVal>=1 ); + + if( nVal>4 ){ + sqlite3_result_error(pContext, + "wrong number of arguments to function snippet()", -1); + return; + } + if( fts3FunctionArg(pContext, "snippet", apVal[0], &pCsr) ) return; + + switch( nVal ){ + case 4: zEllipsis = (const char*)sqlite3_value_text(apVal[3]); + case 3: zEnd = (const char*)sqlite3_value_text(apVal[2]); + case 2: zStart = (const char*)sqlite3_value_text(apVal[1]); } sqlite3Fts3Snippet(pContext, pCsr, zStart, zEnd, zEllipsis); } @@ -99669,10 +100431,12 @@ sqlite3_context *pContext, /* SQLite function call context */ int nVal, /* Size of argument array */ sqlite3_value **apVal /* Array of arguments */ ){ Fts3Cursor *pCsr; /* Cursor handle passed through apVal[0] */ + + UNUSED_PARAMETER(nVal); assert( nVal==1 ); if( fts3FunctionArg(pContext, "offsets", apVal[0], &pCsr) ) return; assert( pCsr ); sqlite3Fts3Offsets(pContext, pCsr); @@ -99693,10 +100457,12 @@ sqlite3_value **apVal /* Array of arguments */ ){ int rc; /* Return code */ Fts3Table *p; /* Virtual table handle */ Fts3Cursor *pCursor; /* Cursor handle passed through apVal[0] */ + + UNUSED_PARAMETER(nVal); assert( nVal==1 ); if( fts3FunctionArg(pContext, "optimize", apVal[0], &pCursor) ) return; p = (Fts3Table *)pCursor->base.pVtab; assert( p ); @@ -99734,10 +100500,15 @@ { "snippet", fts3SnippetFunc }, { "offsets", fts3OffsetsFunc }, { "optimize", fts3OptimizeFunc }, }; int i; /* Iterator variable */ + + UNUSED_PARAMETER(pVtab); + UNUSED_PARAMETER(nArg); + UNUSED_PARAMETER(ppArg); + for(i=0; i<SizeofArray(aOverload); i++){ if( strcmp(zName, aOverload[i].zName)==0 ){ *pxFunc = aOverload[i].xFunc; return 1; } @@ -100150,11 +100921,11 @@ pCursor = 0; } if( rc==SQLITE_DONE ){ int jj; - char *zNew; + char *zNew = NULL; int nNew = 0; int nByte = sizeof(Fts3Expr) + sizeof(Fts3Phrase); nByte += (p?(p->pPhrase->nToken-1):0) * sizeof(struct PhraseToken); p = fts3ReallocOrFree(p, nByte + nTemp); if( !p ){ @@ -100209,11 +100980,11 @@ const char *z, int n, /* Input string */ Fts3Expr **ppExpr, /* OUT: expression */ int *pnConsumed /* OUT: Number of bytes consumed */ ){ static const struct Fts3Keyword { - char z[4]; /* Keyword text */ + char *z; /* Keyword text */ unsigned char n; /* Length of the keyword */ unsigned char parenOnly; /* Only valid in paren mode */ unsigned char eType; /* Keyword code */ } aKeyword[] = { { "OR" , 2, 0, FTSQUERY_OR }, @@ -100279,11 +101050,11 @@ } memset(pRet, 0, sizeof(Fts3Expr)); pRet->eType = pKey->eType; pRet->nNear = nNear; *ppExpr = pRet; - *pnConsumed = (zInput - z) + nKey; + *pnConsumed = (int)((zInput - z) + nKey); return SQLITE_OK; } /* Turns out that wasn't a keyword after all. This happens if the ** user has supplied a token such as "ORacle". Continue. @@ -100299,18 +101070,18 @@ pParse->nNest++; rc = fts3ExprParse(pParse, &zInput[1], nInput-1, ppExpr, &nConsumed); if( rc==SQLITE_OK && !*ppExpr ){ rc = SQLITE_DONE; } - *pnConsumed = (zInput - z) + 1 + nConsumed; + *pnConsumed = (int)((zInput - z) + 1 + nConsumed); return rc; } /* Check for a close bracket. */ if( *zInput==')' ){ pParse->nNest--; - *pnConsumed = (zInput - z) + 1; + *pnConsumed = (int)((zInput - z) + 1); return SQLITE_DONE; } } /* See if we are dealing with a quoted phrase. If this is the case, then @@ -100318,11 +101089,11 @@ ** for processing. This is easy to do, as fts3 has no syntax for escaping ** a quote character embedded in a string. */ if( *zInput=='"' ){ for(ii=1; ii<nInput && zInput[ii]!='"'; ii++); - *pnConsumed = (zInput - z) + ii + 1; + *pnConsumed = (int)((zInput - z) + ii + 1); if( ii==nInput ){ return SQLITE_ERROR; } return getNextString(pParse, &zInput[1], ii-1, ppExpr); } @@ -100341,16 +101112,16 @@ */ iCol = pParse->iDefaultCol; iColLen = 0; for(ii=0; ii<pParse->nCol; ii++){ const char *zStr = pParse->azCol[ii]; - int nStr = strlen(zStr); + int nStr = (int)strlen(zStr); if( nInput>nStr && zInput[nStr]==':' && sqlite3_strnicmp(zStr, zInput, nStr)==0 ){ iCol = ii; - iColLen = ((zInput - z) + nStr + 1); + iColLen = (int)((zInput - z) + nStr + 1); break; } } rc = getNextToken(pParse, iCol, &z[iColLen], n-iColLen, ppExpr, pnConsumed); *pnConsumed += iColLen; @@ -100612,11 +101383,11 @@ if( z==0 ){ *ppExpr = 0; return SQLITE_OK; } if( n<0 ){ - n = strlen(z); + n = (int)strlen(z); } rc = fts3ExprParse(&sParse, z, n, ppExpr, &nParsed); /* Check for mismatched parenthesis */ if( rc==SQLITE_OK && sParse.nNest ){ @@ -100666,11 +101437,11 @@ } sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC); if( SQLITE_ROW==sqlite3_step(pStmt) ){ if( sqlite3_column_type(pStmt, 0)==SQLITE_BLOB ){ - memcpy(pp, sqlite3_column_blob(pStmt, 0), sizeof(*pp)); + memcpy((void *)pp, sqlite3_column_blob(pStmt, 0), sizeof(*pp)); } } return sqlite3_finalize(pStmt); } @@ -100849,125 +101620,10 @@ ** SQLite (in which case SQLITE_ENABLE_FTS3 is defined). */ #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) -/************** Include fts3_hash.h in the middle of fts3_hash.c *************/ -/************** Begin file fts3_hash.h ***************************************/ -/* -** 2001 September 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 is the header file for the generic hash-table implemenation -** used in SQLite. We've modified it slightly to serve as a standalone -** hash table implementation for the full-text indexing module. -** -*/ -#ifndef _FTS3_HASH_H_ -#define _FTS3_HASH_H_ - -/* Forward declarations of structures. */ -typedef struct Fts3Hash Fts3Hash; -typedef struct Fts3HashElem Fts3HashElem; - -/* A complete hash table is an instance of the following structure. -** The internals of this structure are intended to be opaque -- client -** code should not attempt to access or modify the fields of this structure -** directly. Change this structure only by using the routines below. -** However, many of the "procedures" and "functions" for modifying and -** accessing this structure are really macros, so we can't really make -** this structure opaque. -*/ -struct Fts3Hash { - char keyClass; /* HASH_INT, _POINTER, _STRING, _BINARY */ - char copyKey; /* True if copy of key made on insert */ - int count; /* Number of entries in this table */ - Fts3HashElem *first; /* The first element of the array */ - int htsize; /* Number of buckets in the hash table */ - struct _fts3ht { /* the hash table */ - int count; /* Number of entries with this hash */ - Fts3HashElem *chain; /* Pointer to first entry with this hash */ - } *ht; -}; - -/* Each element in the hash table is an instance of the following -** structure. All elements are stored on a single doubly-linked list. -** -** Again, this structure is intended to be opaque, but it can't really -** be opaque because it is used by macros. -*/ -struct Fts3HashElem { - Fts3HashElem *next, *prev; /* Next and previous elements in the table */ - void *data; /* Data associated with this element */ - void *pKey; int nKey; /* Key associated with this element */ -}; - -/* -** There are 2 different modes of operation for a hash table: -** -** FTS3_HASH_STRING pKey points to a string that is nKey bytes long -** (including the null-terminator, if any). Case -** is respected in comparisons. -** -** FTS3_HASH_BINARY pKey points to binary data nKey bytes long. -** memcmp() is used to compare keys. -** -** A copy of the key is made if the copyKey parameter to fts3HashInit is 1. -*/ -#define FTS3_HASH_STRING 1 -#define FTS3_HASH_BINARY 2 - -/* -** Access routines. To delete, insert a NULL pointer. -*/ -SQLITE_PRIVATE void sqlite3Fts3HashInit(Fts3Hash*, int keytype, int copyKey); -SQLITE_PRIVATE void *sqlite3Fts3HashInsert(Fts3Hash*, const void *pKey, int nKey, void *pData); -SQLITE_PRIVATE void *sqlite3Fts3HashFind(const Fts3Hash*, const void *pKey, int nKey); -SQLITE_PRIVATE void sqlite3Fts3HashClear(Fts3Hash*); - -/* -** Shorthand for the functions above -*/ -#define fts3HashInit sqlite3Fts3HashInit -#define fts3HashInsert sqlite3Fts3HashInsert -#define fts3HashFind sqlite3Fts3HashFind -#define fts3HashClear sqlite3Fts3HashClear - -/* -** Macros for looping over all elements of a hash table. The idiom is -** like this: -** -** Fts3Hash h; -** Fts3HashElem *p; -** ... -** for(p=fts3HashFirst(&h); p; p=fts3HashNext(p)){ -** SomeStructure *pData = fts3HashData(p); -** // do something with pData -** } -*/ -#define fts3HashFirst(H) ((H)->first) -#define fts3HashNext(E) ((E)->next) -#define fts3HashData(E) ((E)->data) -#define fts3HashKey(E) ((E)->pKey) -#define fts3HashKeysize(E) ((E)->nKey) - -/* -** Number of entries in a hash table -*/ -#define fts3HashCount(H) ((H)->count) - -#endif /* _FTS3_HASH_H_ */ - -/************** End of fts3_hash.h *******************************************/ -/************** Continuing where we left off in fts3_hash.c ******************/ /* ** Malloc and Free functions */ static void *fts3HashMalloc(int n){ @@ -100989,11 +101645,11 @@ ** FTS3_HASH_BINARY or FTS3_HASH_STRING. The value of keyClass ** determines what kind of key the hash table will use. "copyKey" is ** true if the hash table should make its own private copy of keys and ** false if it should just use the supplied pointer. */ -SQLITE_PRIVATE void sqlite3Fts3HashInit(Fts3Hash *pNew, int keyClass, int copyKey){ +SQLITE_PRIVATE void sqlite3Fts3HashInit(Fts3Hash *pNew, char keyClass, char copyKey){ assert( pNew!=0 ); assert( keyClass>=FTS3_HASH_STRING && keyClass<=FTS3_HASH_BINARY ); pNew->keyClass = keyClass; pNew->copyKey = copyKey; pNew->first = 0; @@ -101123,28 +101779,31 @@ /* Resize the hash table so that it cantains "new_size" buckets. ** "new_size" must be a power of 2. The hash table might fail ** to resize if sqliteMalloc() fails. -*/ -static void fts3Rehash(Fts3Hash *pH, int new_size){ +** +** Return non-zero if a memory allocation error occurs. +*/ +static int fts3Rehash(Fts3Hash *pH, int new_size){ struct _fts3ht *new_ht; /* The new hash table */ Fts3HashElem *elem, *next_elem; /* For looping over existing elements */ int (*xHash)(const void*,int); /* The hash function */ assert( (new_size & (new_size-1))==0 ); new_ht = (struct _fts3ht *)fts3HashMalloc( new_size*sizeof(struct _fts3ht) ); - if( new_ht==0 ) return; + if( new_ht==0 ) return 1; fts3HashFree(pH->ht); pH->ht = new_ht; pH->htsize = new_size; xHash = ftsHashFunction(pH->keyClass); for(elem=pH->first, pH->first=0; elem; elem = next_elem){ int h = (*xHash)(elem->pKey, elem->nKey) & (new_size-1); next_elem = elem->next; fts3HashInsertElement(pH, &new_ht[h], elem); } + return 0; } /* This function (for internal use only) locates an element in an ** hash table that matches the given key. The hash for this key has ** already been computed and is passed as the 4th parameter. @@ -101271,17 +101930,17 @@ elem->data = data; } return old_data; } if( data==0 ) return 0; - if( pH->htsize==0 ){ - fts3Rehash(pH,8); - if( pH->htsize==0 ){ - pH->count = 0; - return data; - } - } + if( (pH->htsize==0 && fts3Rehash(pH,8)) + || (pH->count>=pH->htsize && fts3Rehash(pH, pH->htsize*2)) + ){ + pH->count = 0; + return data; + } + assert( pH->htsize>0 ); new_elem = (Fts3HashElem*)fts3HashMalloc( sizeof(Fts3HashElem) ); if( new_elem==0 ) return data; if( pH->copyKey && pKey!=0 ){ new_elem->pKey = fts3HashMalloc( nKey ); if( new_elem->pKey==0 ){ @@ -101292,13 +101951,10 @@ }else{ new_elem->pKey = (void*)pKey; } new_elem->nKey = nKey; pH->count++; - if( pH->count > pH->htsize ){ - fts3Rehash(pH,pH->htsize*2); - } assert( pH->htsize>0 ); assert( (pH->htsize & (pH->htsize-1))==0 ); h = hraw & (pH->htsize-1); fts3HashInsertElement(pH, &pH->ht[h], new_elem); new_elem->data = data; @@ -101335,162 +101991,10 @@ */ #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) -/************** Include fts3_tokenizer.h in the middle of fts3_porter.c ******/ -/************** Begin file fts3_tokenizer.h **********************************/ -/* -** 2006 July 10 -** -** The author disclaims copyright to this source code. -** -************************************************************************* -** Defines the interface to tokenizers used by fulltext-search. There -** are three basic components: -** -** sqlite3_tokenizer_module is a singleton defining the tokenizer -** interface functions. This is essentially the class structure for -** tokenizers. -** -** sqlite3_tokenizer is used to define a particular tokenizer, perhaps -** including customization information defined at creation time. -** -** sqlite3_tokenizer_cursor is generated by a tokenizer to generate -** tokens from a particular input. -*/ -#ifndef _FTS3_TOKENIZER_H_ -#define _FTS3_TOKENIZER_H_ - -/* TODO(shess) Only used for SQLITE_OK and SQLITE_DONE at this time. -** If tokenizers are to be allowed to call sqlite3_*() functions, then -** we will need a way to register the API consistently. -*/ - -/* -** Structures used by the tokenizer interface. When a new tokenizer -** implementation is registered, the caller provides a pointer to -** an sqlite3_tokenizer_module containing pointers to the callback -** functions that make up an implementation. -** -** When an fts3 table is created, it passes any arguments passed to -** the tokenizer clause of the CREATE VIRTUAL TABLE statement to the -** sqlite3_tokenizer_module.xCreate() function of the requested tokenizer -** implementation. The xCreate() function in turn returns an -** sqlite3_tokenizer structure representing the specific tokenizer to -** be used for the fts3 table (customized by the tokenizer clause arguments). -** -** To tokenize an input buffer, the sqlite3_tokenizer_module.xOpen() -** method is called. It returns an sqlite3_tokenizer_cursor object -** that may be used to tokenize a specific input buffer based on -** the tokenization rules supplied by a specific sqlite3_tokenizer -** object. -*/ -typedef struct sqlite3_tokenizer_module sqlite3_tokenizer_module; -typedef struct sqlite3_tokenizer sqlite3_tokenizer; -typedef struct sqlite3_tokenizer_cursor sqlite3_tokenizer_cursor; - -struct sqlite3_tokenizer_module { - - /* - ** Structure version. Should always be set to 0. - */ - int iVersion; - - /* - ** Create a new tokenizer. The values in the argv[] array are the - ** arguments passed to the "tokenizer" clause of the CREATE VIRTUAL - ** TABLE statement that created the fts3 table. For example, if - ** the following SQL is executed: - ** - ** CREATE .. USING fts3( ... , tokenizer <tokenizer-name> arg1 arg2) - ** - ** then argc is set to 2, and the argv[] array contains pointers - ** to the strings "arg1" and "arg2". - ** - ** This method should return either SQLITE_OK (0), or an SQLite error - ** code. If SQLITE_OK is returned, then *ppTokenizer should be set - ** to point at the newly created tokenizer structure. The generic - ** sqlite3_tokenizer.pModule variable should not be initialised by - ** this callback. The caller will do so. - */ - int (*xCreate)( - int argc, /* Size of argv array */ - const char *const*argv, /* Tokenizer argument strings */ - sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */ - ); - - /* - ** Destroy an existing tokenizer. The fts3 module calls this method - ** exactly once for each successful call to xCreate(). - */ - int (*xDestroy)(sqlite3_tokenizer *pTokenizer); - - /* - ** Create a tokenizer cursor to tokenize an input buffer. The caller - ** is responsible for ensuring that the input buffer remains valid - ** until the cursor is closed (using the xClose() method). - */ - int (*xOpen)( - sqlite3_tokenizer *pTokenizer, /* Tokenizer object */ - const char *pInput, int nBytes, /* Input buffer */ - sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */ - ); - - /* - ** Destroy an existing tokenizer cursor. The fts3 module calls this - ** method exactly once for each successful call to xOpen(). - */ - int (*xClose)(sqlite3_tokenizer_cursor *pCursor); - - /* - ** Retrieve the next token from the tokenizer cursor pCursor. This - ** method should either return SQLITE_OK and set the values of the - ** "OUT" variables identified below, or SQLITE_DONE to indicate that - ** the end of the buffer has been reached, or an SQLite error code. - ** - ** *ppToken should be set to point at a buffer containing the - ** normalized version of the token (i.e. after any case-folding and/or - ** stemming has been performed). *pnBytes should be set to the length - ** of this buffer in bytes. The input text that generated the token is - ** identified by the byte offsets returned in *piStartOffset and - ** *piEndOffset. *piStartOffset should be set to the index of the first - ** byte of the token in the input buffer. *piEndOffset should be set - ** to the index of the first byte just past the end of the token in - ** the input buffer. - ** - ** The buffer *ppToken is set to point at is managed by the tokenizer - ** implementation. It is only required to be valid until the next call - ** to xNext() or xClose(). - */ - /* TODO(shess) current implementation requires pInput to be - ** nul-terminated. This should either be fixed, or pInput/nBytes - ** should be converted to zInput. - */ - int (*xNext)( - sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */ - const char **ppToken, int *pnBytes, /* OUT: Normalized text for token */ - int *piStartOffset, /* OUT: Byte offset of token in input buffer */ - int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */ - int *piPosition /* OUT: Number of tokens returned before this one */ - ); -}; - -struct sqlite3_tokenizer { - const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */ - /* Tokenizer implementations will typically add additional fields */ -}; - -struct sqlite3_tokenizer_cursor { - sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */ - /* Tokenizer implementations will typically add additional fields */ -}; - -#endif /* _FTS3_TOKENIZER_H_ */ - -/************** End of fts3_tokenizer.h **************************************/ -/************** Continuing where we left off in fts3_porter.c ****************/ /* ** Class derived from sqlite3_tokenizer */ typedef struct porter_tokenizer { @@ -101509,22 +102013,22 @@ char *zToken; /* storage for current token */ int nAllocated; /* space allocated to zToken buffer */ } porter_tokenizer_cursor; -/* Forward declaration */ -static const sqlite3_tokenizer_module porterTokenizerModule; - - /* ** Create a new tokenizer instance. */ static int porterCreate( int argc, const char * const *argv, sqlite3_tokenizer **ppTokenizer ){ porter_tokenizer *t; + + UNUSED_PARAMETER(argc); + UNUSED_PARAMETER(argv); + t = (porter_tokenizer *) sqlite3_malloc(sizeof(*t)); if( t==NULL ) return SQLITE_NOMEM; memset(t, 0, sizeof(*t)); *ppTokenizer = &t->base; return SQLITE_OK; @@ -101548,10 +102052,12 @@ sqlite3_tokenizer *pTokenizer, /* The tokenizer */ const char *zInput, int nInput, /* String to be tokenized */ sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */ ){ porter_tokenizer_cursor *c; + + UNUSED_PARAMETER(pTokenizer); c = (porter_tokenizer_cursor *) sqlite3_malloc(sizeof(*c)); if( c==NULL ) return SQLITE_NOMEM; c->zInput = zInput; @@ -101689,11 +102195,11 @@ ** ** The text is reversed here. So we are really looking at ** the first two characters of z[]. */ static int doubleConsonant(const char *z){ - return isConsonant(z) && z[0]==z[1] && isConsonant(z+1); + return isConsonant(z) && z[0]==z[1]; } /* ** Return TRUE if the word ends with three letters which ** are consonant-vowel-consonent and where the final consonant @@ -101702,14 +102208,14 @@ ** The word is reversed here. So we are really checking the ** first three letters and the first one cannot be in [wxy]. */ static int star_oh(const char *z){ return - z[0]!=0 && isConsonant(z) && + isConsonant(z) && z[0]!='w' && z[0]!='x' && z[0]!='y' && - z[1]!=0 && isVowel(z+1) && - z[2]!=0 && isConsonant(z+2); + isVowel(z+1) && + isConsonant(z+2); } /* ** If the word ends with zFrom and xCond() is true for the stem ** of the word that preceeds the zFrom ending, then change the @@ -101749,11 +102255,11 @@ */ static void copy_stemmer(const char *zIn, int nIn, char *zOut, int *pnOut){ int i, mx, j; int hasDigit = 0; for(i=0; i<nIn; i++){ - int c = zIn[i]; + char c = zIn[i]; if( c>='A' && c<='Z' ){ zOut[i] = c - 'A' + 'a'; }else{ if( c>='0' && c<='9' ) hasDigit = 1; zOut[i] = c; @@ -101793,21 +102299,21 @@ ** ** Stemming never increases the length of the word. So there is ** no chance of overflowing the zOut buffer. */ static void porter_stemmer(const char *zIn, int nIn, char *zOut, int *pnOut){ - int i, j, c; + int i, j; char zReverse[28]; char *z, *z2; if( nIn<3 || nIn>=sizeof(zReverse)-7 ){ /* The word is too big or too small for the porter stemmer. ** Fallback to the copy stemmer */ copy_stemmer(zIn, nIn, zOut, pnOut); return; } for(i=0, j=sizeof(zReverse)-6; i<nIn; i++, j--){ - c = zIn[i]; + char c = zIn[i]; if( c>='A' && c<='Z' ){ zReverse[j] = c + 'a' - 'A'; }else if( c>='a' && c<='z' ){ zReverse[j] = c; }else{ @@ -102002,11 +102508,11 @@ } /* z[] is now the stemmed word in reverse order. Flip it back ** around into forward order and return. */ - *pnOut = i = strlen(z); + *pnOut = i = (int)strlen(z); zOut[i] = 0; while( *z ){ zOut[--i] = *(z++); } } @@ -102240,11 +102746,11 @@ z1++; } } } - *pn = (z2-z1); + *pn = (int)(z2-z1); return z1; } SQLITE_PRIVATE int sqlite3Fts3InitTokenizer( Fts3Hash *pHash, /* Tokenizer hash table */ @@ -102261,11 +102767,10 @@ sqlite3_tokenizer_module *m; if( !z ){ zCopy = sqlite3_mprintf("simple"); }else{ - while( (*z&0x80) && isspace(*z) ) z++; if( sqlite3_strnicmp(z, "tokenize", 8) || fts3IsIdChar(z[8])){ return SQLITE_OK; } zCopy = sqlite3_mprintf("%s", &z[8]); *pzTokenizer = zArg; @@ -102278,24 +102783,24 @@ z = (char *)sqlite3Fts3NextToken(zCopy, &n); z[n] = '\0'; sqlite3Fts3Dequote(z); - m = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash, z, strlen(z)+1); + m = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash, z, (int)strlen(z)+1); if( !m ){ *pzErr = sqlite3_mprintf("unknown tokenizer: %s", z); rc = SQLITE_ERROR; }else{ char const **aArg = 0; int iArg = 0; z = &z[n+1]; - while( z<zEnd && (z = (char *)sqlite3Fts3NextToken(z, &n)) ){ + while( z<zEnd && (NULL!=(z = (char *)sqlite3Fts3NextToken(z, &n))) ){ int nNew = sizeof(char *)*(iArg+1); - char const **aNew = (const char **)sqlite3_realloc(aArg, nNew); + char const **aNew = (const char **)sqlite3_realloc((void *)aArg, nNew); if( !aNew ){ sqlite3_free(zCopy); - sqlite3_free(aArg); + sqlite3_free((void *)aArg); return SQLITE_NOMEM; } aArg = aNew; aArg[iArg++] = z; z[n] = '\0'; @@ -102303,15 +102808,15 @@ z = &z[n+1]; } rc = m->xCreate(iArg, aArg, ppTok); assert( rc!=SQLITE_OK || *ppTok ); if( rc!=SQLITE_OK ){ - *pzErr = sqlite3_mprintf("unknown tokenizer: %s", z); + *pzErr = sqlite3_mprintf("unknown tokenizer"); }else{ (*ppTok)->pModule = m; } - sqlite3_free(aArg); + sqlite3_free((void *)aArg); } sqlite3_free(zCopy); return rc; } @@ -102473,11 +102978,11 @@ } sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC); if( SQLITE_ROW==sqlite3_step(pStmt) ){ if( sqlite3_column_type(pStmt, 0)==SQLITE_BLOB ){ - memcpy(pp, sqlite3_column_blob(pStmt, 0), sizeof(*pp)); + memcpy((void *)pp, sqlite3_column_blob(pStmt, 0), sizeof(*pp)); } } return sqlite3_finalize(pStmt); } @@ -102509,10 +103014,13 @@ ){ int rc; const sqlite3_tokenizer_module *p1; const sqlite3_tokenizer_module *p2; sqlite3 *db = (sqlite3 *)sqlite3_user_data(context); + + UNUSED_PARAMETER(argc); + UNUSED_PARAMETER(argv); /* Test the query function */ sqlite3Fts3SimpleTokenizerModule(&p1); rc = queryTokenizer(db, "simple", &p2); assert( rc==SQLITE_OK ); @@ -102569,17 +103077,17 @@ if( !zTest || !zTest2 ){ rc = SQLITE_NOMEM; } #endif - if( rc!=SQLITE_OK - || (rc = sqlite3_create_function(db, zName, 1, any, p, scalarFunc, 0, 0)) - || (rc = sqlite3_create_function(db, zName, 2, any, p, scalarFunc, 0, 0)) + if( SQLITE_OK!=rc + || SQLITE_OK!=(rc = sqlite3_create_function(db, zName, 1, any, p, scalarFunc, 0, 0)) + || SQLITE_OK!=(rc = sqlite3_create_function(db, zName, 2, any, p, scalarFunc, 0, 0)) #ifdef SQLITE_TEST - || (rc = sqlite3_create_function(db, zTest, 2, any, p, testFunc, 0, 0)) - || (rc = sqlite3_create_function(db, zTest, 3, any, p, testFunc, 0, 0)) - || (rc = sqlite3_create_function(db, zTest2, 0, any, pdb, intTestFunc, 0, 0)) + || SQLITE_OK!=(rc = sqlite3_create_function(db, zTest, 2, any, p, testFunc, 0, 0)) + || SQLITE_OK!=(rc = sqlite3_create_function(db, zTest, 3, any, p, testFunc, 0, 0)) + || SQLITE_OK!=(rc = sqlite3_create_function(db, zTest2, 0, any, pdb, intTestFunc, 0, 0)) #endif ); sqlite3_free(zTest); sqlite3_free(zTest2); @@ -102633,13 +103141,10 @@ char *pToken; /* storage for current token */ int nTokenAllocated; /* space allocated to zToken buffer */ } simple_tokenizer_cursor; -/* Forward declaration */ -static const sqlite3_tokenizer_module simpleTokenizerModule; - static int simpleDelim(simple_tokenizer *t, unsigned char c){ return c<0x80 && t->delim[c]; } /* @@ -102659,11 +103164,11 @@ ** else we need to reindex. One solution would be a meta-table to ** track such information in the database, then we'd only want this ** information on the initial create. */ if( argc>1 ){ - int i, n = strlen(argv[1]); + int i, n = (int)strlen(argv[1]); for(i=0; i<n; i++){ unsigned char ch = argv[1][i]; /* We explicitly don't support UTF-8 delimiters for now. */ if( ch>=0x80 ){ sqlite3_free(t); @@ -102673,11 +103178,11 @@ } } else { /* Mark non-alphanumeric ASCII characters as delimiters */ int i; for(i=1; i<0x80; i++){ - t->delim[i] = !isalnum(i); + t->delim[i] = !isalnum(i) ? -1 : 0; } } *ppTokenizer = &t->base; return SQLITE_OK; @@ -102701,10 +103206,12 @@ sqlite3_tokenizer *pTokenizer, /* The tokenizer */ const char *pInput, int nBytes, /* String to be tokenized */ sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */ ){ simple_tokenizer_cursor *c; + + UNUSED_PARAMETER(pTokenizer); c = (simple_tokenizer_cursor *) sqlite3_malloc(sizeof(*c)); if( c==NULL ) return SQLITE_NOMEM; c->pInput = pInput; @@ -102775,11 +103282,11 @@ for(i=0; i<n; i++){ /* TODO(shess) This needs expansion to handle UTF-8 ** case-insensitivity. */ unsigned char ch = p[iStartOffset+i]; - c->pToken[i] = ch<0x80 ? tolower(ch) : ch; + c->pToken[i] = (char)(ch<0x80 ? tolower(ch) : ch); } *ppToken = c->pToken; *pnBytes = n; *piStartOffset = iStartOffset; *piEndOffset = c->iOffset; @@ -102836,13 +103343,10 @@ ** code in fts3.c. */ #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) - -#define INTERIOR_MAX 2048 /* Soft limit for segment node size */ -#define LEAF_MAX 2048 /* Soft limit for segment leaf size */ typedef struct PendingList PendingList; typedef struct SegmentNode SegmentNode; typedef struct SegmentWriter SegmentWriter; @@ -103092,20 +103596,22 @@ sqlite3_stmt *pStmt; int rc = fts3SqlStmt(p, SQL_GET_BLOCK, &pStmt, 0); if( rc!=SQLITE_OK ) return rc; sqlite3_reset(pStmt); - sqlite3_bind_int64(pStmt, 1, iBlock); - rc = sqlite3_step(pStmt); - if( rc!=SQLITE_ROW ){ - return SQLITE_CORRUPT; - } - - *pnBlock = sqlite3_column_bytes(pStmt, 0); - *pzBlock = (char *)sqlite3_column_blob(pStmt, 0); - if( !*pzBlock ){ - return SQLITE_NOMEM; + if( pzBlock ){ + sqlite3_bind_int64(pStmt, 1, iBlock); + rc = sqlite3_step(pStmt); + if( rc!=SQLITE_ROW ){ + return SQLITE_CORRUPT; + } + + *pnBlock = sqlite3_column_bytes(pStmt, 0); + *pzBlock = (char *)sqlite3_column_blob(pStmt, 0); + if( !*pzBlock ){ + return SQLITE_NOMEM; + } } return SQLITE_OK; } /* @@ -103222,11 +103728,13 @@ p->iLastPos = 0; } if( iCol>=0 ){ assert( iPos>p->iLastPos || (iPos==0 && p->iLastPos==0) ); rc = fts3PendingListAppendVarint(&p, 2+iPos-p->iLastPos); - p->iLastPos = iPos; + if( rc==SQLITE_OK ){ + p->iLastPos = iPos; + } } pendinglistappend_out: *pRc = rc; if( p!=*pp ){ @@ -103371,11 +103879,10 @@ Fts3Table *p, /* Full-text table */ sqlite3_value **apVal, /* Array of values to insert */ sqlite3_int64 *piDocid /* OUT: Docid for row just inserted */ ){ int rc; /* Return code */ - int i; /* Iterator variable */ sqlite3_stmt *pContentInsert; /* INSERT INTO %_content VALUES(...) */ /* Locate the statement handle used to insert data into the %_content ** table. The SQL for this statement is: ** @@ -103394,14 +103901,20 @@ ** Which is a problem, since "rowid" and "docid" are aliases for the ** same value. For example: ** ** INSERT INTO fts3tbl(rowid, docid) VALUES(1, 2); ** - ** In FTS3, if a non-NULL docid value is specified, it is the value - ** inserted. Otherwise, the rowid value is used. + ** In FTS3, this is an error. It is an error to specify non-NULL values + ** for both docid and some other rowid alias. */ if( SQLITE_NULL!=sqlite3_value_type(apVal[3+p->nColumn]) ){ + if( SQLITE_NULL==sqlite3_value_type(apVal[0]) + && SQLITE_NULL!=sqlite3_value_type(apVal[1]) + ){ + /* A rowid/docid conflict. */ + return SQLITE_ERROR; + } rc = sqlite3_bind_value(pContentInsert, 1, apVal[3+p->nColumn]); if( rc!=SQLITE_OK ) return rc; } /* Execute the statement to insert the record. Set *piDocid to the @@ -103457,13 +103970,15 @@ sqlite3_reset(pSelect); return rc; } } } - } - - return sqlite3_reset(pSelect); + rc = sqlite3_reset(pSelect); + }else{ + sqlite3_reset(pSelect); + } + return rc; } /* ** Forward declaration to account for the circular dependency between ** functions fts3SegmentMerge() and fts3AllocateSegdirIdx(). @@ -103486,18 +104001,18 @@ ** returned. Otherwise, an SQLite error code is returned. */ static int fts3AllocateSegdirIdx(Fts3Table *p, int iLevel, int *piIdx){ int rc; /* Return Code */ sqlite3_stmt *pNextIdx; /* Query for next idx at level iLevel */ - int iNext; /* Result of query pNextIdx */ + int iNext = 0; /* Result of query pNextIdx */ /* Set variable iNext to the next available segdir index at level iLevel. */ rc = fts3SqlStmt(p, SQL_NEXT_SEGMENT_INDEX, &pNextIdx, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int(pNextIdx, 1, iLevel); if( SQLITE_ROW==sqlite3_step(pNextIdx) ){ - iNext = sqlite3_column_int64(pNextIdx, 0); + iNext = sqlite3_column_int(pNextIdx, 0); } rc = sqlite3_reset(pNextIdx); } if( rc==SQLITE_OK ){ @@ -103611,11 +104126,11 @@ /* If required, populate the output variables with a pointer to and the ** size of the previous offset-list. */ if( ppOffsetList ){ *ppOffsetList = pReader->pOffsetList; - *pnOffsetList = p - pReader->pOffsetList - 1; + *pnOffsetList = (int)(p - pReader->pOffsetList - 1); } /* If there are no more entries in the doclist, set pOffsetList to ** NULL. Otherwise, set Fts3SegReader.iDocid to the next docid and ** Fts3SegReader.pOffsetList to point to the next offset list before @@ -103683,12 +104198,10 @@ /* The entire segment is stored in the root node. */ pReader->aNode = (char *)&pReader[1]; pReader->nNode = nRoot; memcpy(pReader->aNode, zRoot, nRoot); }else{ - sqlite3_stmt *pStmt; - /* If the text of the SQL statement to iterate through a contiguous ** set of entries in the %_segments table has not yet been composed, ** compose it now. */ if( !p->zSelectLeaves ){ @@ -103966,10 +104479,11 @@ int nPrev, /* Size of buffer zPrev in bytes */ const char *zNext, /* Buffer containing next term */ int nNext /* Size of buffer zNext in bytes */ ){ int n; + UNUSED_PARAMETER(nNext); for(n=0; n<nPrev && zPrev[n]==zNext[n]; n++); return n; } /* @@ -103998,17 +104512,17 @@ nPrefix = fts3PrefixCompress(pTree->zTerm, pTree->nTerm, zTerm, nTerm); nSuffix = nTerm-nPrefix; nReq += sqlite3Fts3VarintLen(nPrefix)+sqlite3Fts3VarintLen(nSuffix)+nSuffix; - if( nReq<=INTERIOR_MAX || !pTree->zTerm ){ - - if( nReq>INTERIOR_MAX ){ + if( nReq<=p->nNodeSize || !pTree->zTerm ){ + + if( nReq>p->nNodeSize ){ /* An unusual case: this is the first term to be added to the node - ** and the static node buffer (INTERIOR_MAX bytes) is not large + ** and the static node buffer (p->nNodeSize bytes) is not large ** enough. Use a separately malloced buffer instead This wastes - ** INTERIOR_MAX bytes, but since this scenario only comes about when + ** p->nNodeSize bytes, but since this scenario only comes about when ** the database contain two terms that share a prefix of almost 2KB, ** this is not expected to be a serious problem. */ assert( pTree->aData==(char *)&pTree[1] ); pTree->aData = (char *)sqlite3_malloc(nReq); @@ -104053,11 +104567,11 @@ ** ** Otherwise, the term is not added to the new node, it is left empty for ** now. Instead, the term is inserted into the parent of pTree. If pTree ** has no parent, one is created here. */ - pNew = (SegmentNode *)sqlite3_malloc(sizeof(SegmentNode) + INTERIOR_MAX); + pNew = (SegmentNode *)sqlite3_malloc(sizeof(SegmentNode) + p->nNodeSize); if( !pNew ){ return SQLITE_NOMEM; } memset(pNew, 0, sizeof(SegmentNode)); pNew->nData = 1 + FTS3_VARINT_MAX; @@ -104206,13 +104720,13 @@ if( !pWriter ) return SQLITE_NOMEM; memset(pWriter, 0, sizeof(SegmentWriter)); *ppWriter = pWriter; /* Allocate a buffer in which to accumulate data */ - pWriter->aData = (char *)sqlite3_malloc(LEAF_MAX); + pWriter->aData = (char *)sqlite3_malloc(p->nNodeSize); if( !pWriter->aData ) return SQLITE_NOMEM; - pWriter->nSize = LEAF_MAX; + pWriter->nSize = p->nNodeSize; /* Find the next free blockid in the %_segments table */ rc = fts3SqlStmt(p, SQL_NEXT_SEGMENTS_ID, &pStmt, 0); if( rc!=SQLITE_OK ) return rc; if( SQLITE_ROW==sqlite3_step(pStmt) ){ @@ -104232,11 +104746,11 @@ sqlite3Fts3VarintLen(nSuffix) + /* varint containing suffix size */ nSuffix + /* Term suffix */ sqlite3Fts3VarintLen(nDoclist) + /* Size of doclist */ nDoclist; /* Doclist data */ - if( nData>0 && nData+nReq>LEAF_MAX ){ + if( nData>0 && nData+nReq>p->nNodeSize ){ int rc; /* The current leaf node is full. Write it out to the database. */ rc = fts3WriteSegment(p, pWriter->iFree++, pWriter->aData, nData); if( rc!=SQLITE_OK ) return rc; @@ -104326,14 +104840,14 @@ int iLevel, /* Value for 'level' column of %_segdir */ int iIdx /* Value for 'idx' column of %_segdir */ ){ int rc; /* Return code */ if( pWriter->pTree ){ - sqlite3_int64 iLast; /* Largest block id written to database */ + sqlite3_int64 iLast = 0; /* Largest block id written to database */ sqlite3_int64 iLastLeaf; /* Largest leaf block id written to db */ - char *zRoot; /* Pointer to buffer containing root node */ - int nRoot; /* Size of buffer zRoot */ + char *zRoot = NULL; /* Pointer to buffer containing root node */ + int nRoot = 0; /* Size of buffer zRoot */ iLastLeaf = pWriter->iFree; rc = fts3WriteSegment(p, pWriter->iFree++, pWriter->aData, pWriter->nData); if( rc==SQLITE_OK ){ rc = fts3NodeWrite(p, pWriter->pTree, 1, @@ -104502,15 +105016,15 @@ while( 1 ){ char c = 0; while( p<pEnd && (c | *p)&0xFE ) c = *p++ & 0x80; if( iCol==iCurrent ){ - nList = (p - pList); + nList = (int)(p - pList); break; } - nList -= (p - pList); + nList -= (int)(p - pList); pList = p; if( nList==0 ){ break; } p = &pList[1]; @@ -104577,10 +105091,15 @@ int isIgnoreEmpty = (pFilter->flags & FTS3_SEGMENT_IGNORE_EMPTY); int isRequirePos = (pFilter->flags & FTS3_SEGMENT_REQUIRE_POS); int isColFilter = (pFilter->flags & FTS3_SEGMENT_COLUMN_FILTER); int isPrefix = (pFilter->flags & FTS3_SEGMENT_PREFIX); + + /* If there are zero segments, this function is a no-op. This scenario + ** comes about only when reading from an empty database. + */ + if( nSegment==0 ) goto finished; /* If the Fts3SegFilter defines a specific term (or term prefix) to search ** for, then advance each segment iterator until it points to a term of ** equal or greater value than the specified term. This prevents many ** unnecessary merge/sort operations for the case where single segment @@ -104906,11 +105425,11 @@ sqlite_int64 *pRowid /* OUT: The affected (or effected) rowid */ ){ Fts3Table *p = (Fts3Table *)pVtab; int rc = SQLITE_OK; /* Return Code */ int isRemove = 0; /* True for an UPDATE or DELETE */ - sqlite3_int64 iRemove; /* Rowid removed by UPDATE or DELETE */ + sqlite3_int64 iRemove = 0; /* Rowid removed by UPDATE or DELETE */ /* If this is a DELETE or UPDATE operation, remove the old record. */ if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){ int isEmpty; rc = fts3IsEmpty(p, apVal, &isEmpty); @@ -104953,13 +105472,22 @@ ** Flush any data in the pending-terms hash table to disk. If successful, ** merge all segments in the database (including the new segment, if ** there was any data to flush) into a single segment. */ SQLITE_PRIVATE int sqlite3Fts3Optimize(Fts3Table *p){ - int rc = sqlite3Fts3PendingTermsFlush(p); + int rc; + rc = sqlite3_exec(p->db, "SAVEPOINT fts3", 0, 0, 0); if( rc==SQLITE_OK ){ - rc = fts3SegmentMerge(p, -1); + rc = sqlite3Fts3PendingTermsFlush(p); + if( rc==SQLITE_OK ){ + rc = fts3SegmentMerge(p, -1); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(p->db, "RELEASE fts3", 0, 0, 0); + }else{ + sqlite3_exec(p->db, "ROLLBACK TO fts3 ; RELEASE fts3", 0, 0, 0); + } } return rc; } #endif @@ -104991,14 +105519,14 @@ struct Snippet { int nMatch; /* Total number of matches */ int nAlloc; /* Space allocated for aMatch[] */ struct snippetMatch { /* One entry for each matching term */ char snStatus; /* Status flag for use while constructing snippets */ + short int nByte; /* Number of bytes in the term */ short int iCol; /* The column that contains the match */ short int iTerm; /* The index in Query.pTerms[] of the matching term */ int iToken; /* The index of the matching document token */ - short int nByte; /* Number of bytes in the term */ int iStart; /* The offset to the first character of the term */ } *aMatch; /* Points to space obtained from malloc */ char *zOffset; /* Text rendering of aMatch[] */ int nOffset; /* strlen(zOffset) */ char *zSnippet; /* Snippet text */ @@ -105008,171 +105536,125 @@ /* It is not safe to call isspace(), tolower(), or isalnum() on ** hi-bit-set characters. This is the same solution used in the ** tokenizer. */ -/* TODO(shess) The snippet-generation code should be using the -** tokenizer-generated tokens rather than doing its own local -** tokenization. -*/ -/* TODO(shess) Is __isascii() a portable version of (c&0x80)==0? */ -static int safe_isspace(char c){ +static int fts3snippetIsspace(char c){ return (c&0x80)==0 ? isspace(c) : 0; } -static int safe_isalnum(char c){ - return (c&0x80)==0 ? isalnum(c) : 0; -} - -/*******************************************************************/ -/* DataBuffer is used to collect data into a buffer in piecemeal -** fashion. It implements the usual distinction between amount of -** data currently stored (nData) and buffer capacity (nCapacity). -** -** dataBufferInit - create a buffer with given initial capacity. -** dataBufferReset - forget buffer's data, retaining capacity. -** dataBufferSwap - swap contents of two buffers. -** dataBufferExpand - expand capacity without adding data. -** dataBufferAppend - append data. -** dataBufferAppend2 - append two pieces of data at once. -** dataBufferReplace - replace buffer's data. -*/ -typedef struct DataBuffer { - char *pData; /* Pointer to malloc'ed buffer. */ - int nCapacity; /* Size of pData buffer. */ - int nData; /* End of data loaded into pData. */ -} DataBuffer; - -static void dataBufferInit(DataBuffer *pBuffer, int nCapacity){ - assert( nCapacity>=0 ); - pBuffer->nData = 0; - pBuffer->nCapacity = nCapacity; - pBuffer->pData = nCapacity==0 ? NULL : sqlite3_malloc(nCapacity); -} -static void dataBufferReset(DataBuffer *pBuffer){ - pBuffer->nData = 0; -} -static void dataBufferExpand(DataBuffer *pBuffer, int nAddCapacity){ - assert( nAddCapacity>0 ); - /* TODO(shess) Consider expanding more aggressively. Note that the - ** underlying malloc implementation may take care of such things for - ** us already. - */ - if( pBuffer->nData+nAddCapacity>pBuffer->nCapacity ){ - pBuffer->nCapacity = pBuffer->nData+nAddCapacity; - pBuffer->pData = sqlite3_realloc(pBuffer->pData, pBuffer->nCapacity); - } -} -static void dataBufferAppend(DataBuffer *pBuffer, - const char *pSource, int nSource){ - assert( nSource>0 && pSource!=NULL ); - dataBufferExpand(pBuffer, nSource); - memcpy(pBuffer->pData+pBuffer->nData, pSource, nSource); - pBuffer->nData += nSource; -} -static void dataBufferAppend2(DataBuffer *pBuffer, - const char *pSource1, int nSource1, - const char *pSource2, int nSource2){ - assert( nSource1>0 && pSource1!=NULL ); - assert( nSource2>0 && pSource2!=NULL ); - dataBufferExpand(pBuffer, nSource1+nSource2); - memcpy(pBuffer->pData+pBuffer->nData, pSource1, nSource1); - memcpy(pBuffer->pData+pBuffer->nData+nSource1, pSource2, nSource2); - pBuffer->nData += nSource1+nSource2; -} -static void dataBufferReplace(DataBuffer *pBuffer, - const char *pSource, int nSource){ - dataBufferReset(pBuffer); - dataBufferAppend(pBuffer, pSource, nSource); -} - - -/* StringBuffer is a null-terminated version of DataBuffer. */ + + +/* +** A StringBuffer object holds a zero-terminated string that grows +** arbitrarily by appending. Space to hold the string is obtained +** from sqlite3_malloc(). After any memory allocation failure, +** StringBuffer.z is set to NULL and no further allocation is attempted. +*/ typedef struct StringBuffer { - DataBuffer b; /* Includes null terminator. */ + char *z; /* Text of the string. Space from malloc. */ + int nUsed; /* Number bytes of z[] used, not counting \000 terminator */ + int nAlloc; /* Bytes allocated for z[] */ } StringBuffer; -static void initStringBuffer(StringBuffer *sb){ - dataBufferInit(&sb->b, 100); - dataBufferReplace(&sb->b, "", 1); -} -static int stringBufferLength(StringBuffer *sb){ - return sb->b.nData-1; -} -static char *stringBufferData(StringBuffer *sb){ - return sb->b.pData; -} - -static void nappend(StringBuffer *sb, const char *zFrom, int nFrom){ - assert( sb->b.nData>0 ); - if( nFrom>0 ){ - sb->b.nData--; - dataBufferAppend2(&sb->b, zFrom, nFrom, "", 1); - } -} -static void append(StringBuffer *sb, const char *zFrom){ - nappend(sb, zFrom, strlen(zFrom)); -} - -static int endsInWhiteSpace(StringBuffer *p){ - return stringBufferLength(p)>0 && - safe_isspace(stringBufferData(p)[stringBufferLength(p)-1]); + +/* +** Initialize a new StringBuffer. +*/ +static void fts3SnippetSbInit(StringBuffer *p){ + p->nAlloc = 100; + p->nUsed = 0; + p->z = sqlite3_malloc( p->nAlloc ); +} + +/* +** Append text to the string buffer. +*/ +static void fts3SnippetAppend(StringBuffer *p, const char *zNew, int nNew){ + if( p->z==0 ) return; + if( nNew<0 ) nNew = (int)strlen(zNew); + if( p->nUsed + nNew >= p->nAlloc ){ + int nAlloc; + char *zNew; + + nAlloc = p->nUsed + nNew + p->nAlloc; + zNew = sqlite3_realloc(p->z, nAlloc); + if( zNew==0 ){ + sqlite3_free(p->z); + p->z = 0; + return; + } + p->z = zNew; + p->nAlloc = nAlloc; + } + memcpy(&p->z[p->nUsed], zNew, nNew); + p->nUsed += nNew; + p->z[p->nUsed] = 0; } /* If the StringBuffer ends in something other than white space, add a ** single space character to the end. */ -static void appendWhiteSpace(StringBuffer *p){ - if( stringBufferLength(p)==0 ) return; - if( !endsInWhiteSpace(p) ) append(p, " "); +static void fts3SnippetAppendWhiteSpace(StringBuffer *p){ + if( p->z && p->nUsed && !fts3snippetIsspace(p->z[p->nUsed-1]) ){ + fts3SnippetAppend(p, " ", 1); + } } /* Remove white space from the end of the StringBuffer */ -static void trimWhiteSpace(StringBuffer *p){ - while( endsInWhiteSpace(p) ){ - p->b.pData[--p->b.nData-1] = '\0'; - } -} - +static void fts3SnippetTrimWhiteSpace(StringBuffer *p){ + if( p->z ){ + while( p->nUsed && fts3snippetIsspace(p->z[p->nUsed-1]) ){ + p->nUsed--; + } + p->z[p->nUsed] = 0; + } +} /* ** Release all memory associated with the Snippet structure passed as ** an argument. */ static void fts3SnippetFree(Snippet *p){ - sqlite3_free(p->aMatch); - sqlite3_free(p->zOffset); - sqlite3_free(p->zSnippet); - sqlite3_free(p); + if( p ){ + sqlite3_free(p->aMatch); + sqlite3_free(p->zOffset); + sqlite3_free(p->zSnippet); + sqlite3_free(p); + } } /* ** Append a single entry to the p->aMatch[] log. */ -static void snippetAppendMatch( +static int snippetAppendMatch( Snippet *p, /* Append the entry to this snippet */ int iCol, int iTerm, /* The column and query term */ int iToken, /* Matching token in document */ int iStart, int nByte /* Offset and size of the match */ ){ int i; struct snippetMatch *pMatch; if( p->nMatch+1>=p->nAlloc ){ + struct snippetMatch *pNew; p->nAlloc = p->nAlloc*2 + 10; - p->aMatch = sqlite3_realloc(p->aMatch, p->nAlloc*sizeof(p->aMatch[0]) ); - if( p->aMatch==0 ){ + pNew = sqlite3_realloc(p->aMatch, p->nAlloc*sizeof(p->aMatch[0]) ); + if( pNew==0 ){ + p->aMatch = 0; p->nMatch = 0; p->nAlloc = 0; - return; - } + return SQLITE_NOMEM; + } + p->aMatch = pNew; } i = p->nMatch++; pMatch = &p->aMatch[i]; - pMatch->iCol = iCol; - pMatch->iTerm = iTerm; + pMatch->iCol = (short)iCol; + pMatch->iTerm = (short)iTerm; pMatch->iToken = iToken; pMatch->iStart = iStart; - pMatch->nByte = nByte; + pMatch->nByte = (short)nByte; + return SQLITE_OK; } /* ** Sizing information for the circular buffer used in snippetOffsetsOfColumn() */ @@ -105242,11 +105724,11 @@ /* ** Add entries to pSnippet->aMatch[] for every match that occurs against ** document zDoc[0..nDoc-1] which is stored in column iColumn. */ -static void snippetOffsetsOfColumn( +static int snippetOffsetsOfColumn( Fts3Cursor *pCur, /* The fulltest search cursor */ Snippet *pSnippet, /* The Snippet object to be filled in */ int iColumn, /* Index of fulltext table column */ const char *zDoc, /* Text of the fulltext table column */ int nDoc /* Length of zDoc in bytes */ @@ -105272,15 +105754,16 @@ pVtab = (Fts3Table *)pCur->base.pVtab; nColumn = pVtab->nColumn; pTokenizer = pVtab->pTokenizer; pTModule = pTokenizer->pModule; rc = pTModule->xOpen(pTokenizer, zDoc, nDoc, &pTCursor); - if( rc ) return; + if( rc ) return rc; pTCursor->pTokenizer = pTokenizer; prevMatch = 0; - while( !pTModule->xNext(pTCursor, &zToken, &nToken, &iBegin, &iEnd, &iPos) ){ + while( (rc = pTModule->xNext(pTCursor, &zToken, &nToken, + &iBegin, &iEnd, &iPos))==SQLITE_OK ){ Fts3Expr *pIter = pCur->pExpr; int iIter = -1; iRotorBegin[iRotor&FTS3_ROTOR_MASK] = iBegin; iRotorLen[iRotor&FTS3_ROTOR_MASK] = iEnd-iBegin; match = 0; @@ -105301,19 +105784,22 @@ if( iIter>0 && (prevMatch & (1<<i))==0 ) continue; match |= 1<<i; if( i==(FTS3_ROTOR_SZ-2) || nPhrase==iIter+1 ){ for(j=nPhrase-1; j>=0; j--){ int k = (iRotor-j) & FTS3_ROTOR_MASK; - snippetAppendMatch(pSnippet, iColumn, i-j, iPos-j, - iRotorBegin[k], iRotorLen[k]); + rc = snippetAppendMatch(pSnippet, iColumn, i-j, iPos-j, + iRotorBegin[k], iRotorLen[k]); + if( rc ) goto end_offsets_of_column; } } } prevMatch = match<<1; iRotor++; } +end_offsets_of_column: pTModule->xClose(pTCursor); + return rc==SQLITE_DONE ? SQLITE_OK : rc; } /* ** Remove entries from the pSnippet structure to account for the NEAR ** operator. When this is called, pSnippet contains the list of token @@ -105445,16 +105931,19 @@ /* ** Compute all offsets for the current row of the query. ** If the offsets have already been computed, this routine is a no-op. */ static int snippetAllOffsets(Fts3Cursor *pCsr, Snippet **ppSnippet){ - Fts3Table *p = (Fts3Table *)pCsr->base.pVtab; - int nColumn; - int iColumn, i; - int iFirst, iLast; + Fts3Table *p = (Fts3Table *)pCsr->base.pVtab; /* The FTS3 virtual table */ + int nColumn; /* Number of columns. Docid does count */ + int iColumn; /* Index of of a column */ + int i; /* Loop index */ + int iFirst; /* First column to search */ + int iLast; /* Last coumn to search */ int iTerm = 0; Snippet *pSnippet; + int rc = SQLITE_OK; if( pCsr->pExpr==0 ){ return SQLITE_OK; } @@ -105464,33 +105953,37 @@ return SQLITE_NOMEM; } memset(pSnippet, 0, sizeof(Snippet)); nColumn = p->nColumn; - iColumn = (pCsr->eType - 2); + iColumn = (pCsr->eSearch - 2); if( iColumn<0 || iColumn>=nColumn ){ /* Look for matches over all columns of the full-text index */ iFirst = 0; iLast = nColumn-1; }else{ /* Look for matches in the iColumn-th column of the index only */ iFirst = iColumn; iLast = iColumn; } - for(i=iFirst; i<=iLast; i++){ + for(i=iFirst; rc==SQLITE_OK && i<=iLast; i++){ const char *zDoc; int nDoc; zDoc = (const char*)sqlite3_column_text(pCsr->pStmt, i+1); nDoc = sqlite3_column_bytes(pCsr->pStmt, i+1); - snippetOffsetsOfColumn(pCsr, pSnippet, i, zDoc, nDoc); + if( zDoc==0 && sqlite3_column_type(pCsr->pStmt, i+1)!=SQLITE_NULL ){ + rc = SQLITE_NOMEM; + }else{ + rc = snippetOffsetsOfColumn(pCsr, pSnippet, i, zDoc, nDoc); + } } while( trimSnippetOffsets(pCsr->pExpr, pSnippet, &iTerm) ){ iTerm = 0; } - return SQLITE_OK; + return rc; } /* ** Convert the information in the aMatch[] array of the snippet ** into the string zOffset[0..nOffset-1]. This string is used as @@ -105500,11 +105993,11 @@ int i; int cnt = 0; StringBuffer sb; char zBuf[200]; if( p->zOffset ) return; - initStringBuffer(&sb); + fts3SnippetSbInit(&sb); for(i=0; i<p->nMatch; i++){ struct snippetMatch *pMatch = &p->aMatch[i]; if( pMatch->iTerm>=0 ){ /* If snippetMatch.iTerm is less than 0, then the match was ** discarded as part of processing the NEAR operator (see the @@ -105512,16 +106005,16 @@ ** it in this case */ zBuf[0] = ' '; sqlite3_snprintf(sizeof(zBuf)-1, &zBuf[cnt>0], "%d %d %d %d", pMatch->iCol, pMatch->iTerm, pMatch->iStart, pMatch->nByte); - append(&sb, zBuf); + fts3SnippetAppend(&sb, zBuf, -1); cnt++; } } - p->zOffset = stringBufferData(&sb); - p->nOffset = stringBufferLength(&sb); + p->zOffset = sb.z; + p->nOffset = sb.z ? sb.nUsed : 0; } /* ** zDoc[0..nDoc-1] is phrase of text. aMatch[0..nMatch-1] are a set ** of matching words some of which might be in zDoc. zDoc is column @@ -105544,11 +106037,11 @@ return 0; } if( iBreak>=nDoc-10 ){ return nDoc; } - for(i=0; i<nMatch && aMatch[i].iCol<iCol; i++){} + for(i=0; ALWAYS(i<nMatch) && aMatch[i].iCol<iCol; i++){} while( i<nMatch && aMatch[i].iStart+aMatch[i].nByte<iBreak ){ i++; } if( i<nMatch ){ if( aMatch[i].iStart<iBreak+10 ){ return aMatch[i].iStart; } @@ -105555,14 +106048,14 @@ if( i>0 && aMatch[i-1].iStart+aMatch[i-1].nByte>=iBreak ){ return aMatch[i-1].iStart; } } for(i=1; i<=10; i++){ - if( safe_isspace(zDoc[iBreak-i]) ){ + if( fts3snippetIsspace(zDoc[iBreak-i]) ){ return iBreak - i + 1; } - if( safe_isspace(zDoc[iBreak+i]) ){ + if( fts3snippetIsspace(zDoc[iBreak+i]) ){ return iBreak + i + 1; } } return iBreak; } @@ -105602,11 +106095,11 @@ sqlite3_free(pSnippet->zSnippet); pSnippet->zSnippet = 0; aMatch = pSnippet->aMatch; nMatch = pSnippet->nMatch; - initStringBuffer(&sb); + fts3SnippetSbInit(&sb); for(i=0; i<nMatch; i++){ aMatch[i].snStatus = SNIPPET_IGNORE; } nDesired = 0; @@ -105636,14 +106129,14 @@ } if( iCol==tailCol && iStart<=tailOffset+20 ){ iStart = tailOffset; } if( (iCol!=tailCol && tailCol>=0) || iStart!=tailOffset ){ - trimWhiteSpace(&sb); - appendWhiteSpace(&sb); - append(&sb, zEllipsis); - appendWhiteSpace(&sb); + fts3SnippetTrimWhiteSpace(&sb); + fts3SnippetAppendWhiteSpace(&sb); + fts3SnippetAppend(&sb, zEllipsis, -1); + fts3SnippetAppendWhiteSpace(&sb); } iEnd = aMatch[i].iStart + aMatch[i].nByte + 40; iEnd = wordBoundary(iEnd, zDoc, nDoc, aMatch, nMatch, iCol); if( iEnd>=nDoc-10 ){ iEnd = nDoc; @@ -105657,48 +106150,56 @@ && aMatch[iMatch].iCol<=iCol ){ iMatch++; } if( iMatch<nMatch && aMatch[iMatch].iStart<iEnd && aMatch[iMatch].iCol==iCol ){ - nappend(&sb, &zDoc[iStart], aMatch[iMatch].iStart - iStart); + fts3SnippetAppend(&sb, &zDoc[iStart], aMatch[iMatch].iStart - iStart); iStart = aMatch[iMatch].iStart; - append(&sb, zStartMark); - nappend(&sb, &zDoc[iStart], aMatch[iMatch].nByte); - append(&sb, zEndMark); + fts3SnippetAppend(&sb, zStartMark, -1); + fts3SnippetAppend(&sb, &zDoc[iStart], aMatch[iMatch].nByte); + fts3SnippetAppend(&sb, zEndMark, -1); iStart += aMatch[iMatch].nByte; for(j=iMatch+1; j<nMatch; j++){ if( aMatch[j].iTerm==aMatch[iMatch].iTerm && aMatch[j].snStatus==SNIPPET_DESIRED ){ nDesired--; aMatch[j].snStatus = SNIPPET_IGNORE; } } }else{ - nappend(&sb, &zDoc[iStart], iEnd - iStart); + fts3SnippetAppend(&sb, &zDoc[iStart], iEnd - iStart); iStart = iEnd; } } tailCol = iCol; tailOffset = iEnd; } - trimWhiteSpace(&sb); + fts3SnippetTrimWhiteSpace(&sb); if( tailEllipsis ){ - appendWhiteSpace(&sb); - append(&sb, zEllipsis); - } - pSnippet->zSnippet = stringBufferData(&sb); - pSnippet->nSnippet = stringBufferLength(&sb); + fts3SnippetAppendWhiteSpace(&sb); + fts3SnippetAppend(&sb, zEllipsis, -1); + } + pSnippet->zSnippet = sb.z; + pSnippet->nSnippet = sb.z ? sb.nUsed : 0; } SQLITE_PRIVATE void sqlite3Fts3Offsets( sqlite3_context *pCtx, /* SQLite function call context */ Fts3Cursor *pCsr /* Cursor object */ ){ Snippet *p; /* Snippet structure */ int rc = snippetAllOffsets(pCsr, &p); - snippetOffsetText(p); - sqlite3_result_text(pCtx, p->zOffset, p->nOffset, SQLITE_TRANSIENT); + if( rc==SQLITE_OK ){ + snippetOffsetText(p); + if( p->zOffset ){ + sqlite3_result_text(pCtx, p->zOffset, p->nOffset, SQLITE_TRANSIENT); + }else{ + sqlite3_result_error_nomem(pCtx); + } + }else{ + sqlite3_result_error_nomem(pCtx); + } fts3SnippetFree(p); } SQLITE_PRIVATE void sqlite3Fts3Snippet( sqlite3_context *pCtx, /* SQLite function call context */ @@ -105707,12 +106208,20 @@ const char *zEnd, /* Snippet end text - "</b>" */ const char *zEllipsis /* Snippet ellipsis text - "<b>...</b>" */ ){ Snippet *p; /* Snippet structure */ int rc = snippetAllOffsets(pCsr, &p); - snippetText(pCsr, p, zStart, zEnd, zEllipsis); - sqlite3_result_text(pCtx, p->zSnippet, p->nSnippet, SQLITE_TRANSIENT); + if( rc==SQLITE_OK ){ + snippetText(pCsr, p, zStart, zEnd, zEllipsis); + if( p->zSnippet ){ + sqlite3_result_text(pCtx, p->zSnippet, p->nSnippet, SQLITE_TRANSIENT); + }else{ + sqlite3_result_error_nomem(pCtx); + } + }else{ + sqlite3_result_error_nomem(pCtx); + } fts3SnippetFree(p); } #endif
Modified src/sqlite3.h from [260f444797] to [99a4282d9b].
@@ -119,11 +119,11 @@ ** ** Requirements: [H10011] [H10014] */ #define SQLITE_VERSION "3.6.21" #define SQLITE_VERSION_NUMBER 3006021 -#define SQLITE_SOURCE_ID "2009-11-25 21:05:09 5086bf8e838c824accda531afeb56a51dd40d795" +#define SQLITE_SOURCE_ID "2009-12-07 16:39:13 1ed88e9d01e9eda5cbc622e7614277f29bcc551c" /* ** CAPI3REF: Run-Time Library Version Numbers {H10020} <S60100> ** KEYWORDS: sqlite3_version ** @@ -1293,10 +1293,13 @@ ** the return value of this interface. ** ** For the purposes of this routine, an [INSERT] is considered to ** be successful even if it is subsequently rolled back. ** +** This function is accessible to SQL statements via the +** [last_insert_rowid() SQL function]. +** ** Requirements: ** [H12221] [H12223] ** ** If a separate thread performs a new [INSERT] on the same ** database connection while the [sqlite3_last_insert_rowid()] @@ -1350,12 +1353,12 @@ ** changes in the most recently completed INSERT, UPDATE, or DELETE ** statement within the body of the same trigger. ** However, the number returned does not include changes ** caused by subtriggers since those have their own context. ** -** See also the [sqlite3_total_changes()] interface and the -** [count_changes pragma]. +** See also the [sqlite3_total_changes()] interface, the +** [count_changes pragma], and the [changes() SQL function]. ** ** Requirements: ** [H12241] [H12243] ** ** If a separate thread makes changes on the same database connection @@ -1378,12 +1381,12 @@ ** are counted. ** The changes are counted as soon as the statement that makes them is ** completed (when the statement handle is passed to [sqlite3_reset()] or ** [sqlite3_finalize()]). ** -** See also the [sqlite3_changes()] interface and the -** [count_changes pragma]. +** See also the [sqlite3_changes()] interface, the +** [count_changes pragma], and the [total_changes() SQL function]. ** ** Requirements: ** [H12261] [H12263] ** ** If a separate thread makes changes on the same database connection @@ -4140,10 +4143,12 @@ ** should free this memory by calling [sqlite3_free()]. ** ** {H12606} Extension loading must be enabled using ** [sqlite3_enable_load_extension()] prior to calling this API, ** otherwise an error will be returned. +** +** See also the [load_extension() SQL function]. */ SQLITE_API int sqlite3_load_extension( sqlite3 *db, /* Load the extension into this database connection */ const char *zFile, /* Name of the shared library containing extension */ const char *zProc, /* Entry point. Derived from zFile if 0 */
Modified src/style.c from [3c29836aa3] to [c7fc56d774].
@@ -143,11 +143,11 @@ @ <div class="content"> cgi_destination(CGI_BODY); /* Put the footer at the bottom of the page. */ - @ </div><br clear="both"></br> + @ </div><br clear="both"/> zFooter = db_get("footer", (char*)zDefaultFooter); if( g.thTrace ) Th_Trace("BEGIN_FOOTER<br />\n", -1); Th_Render(zFooter); if( g.thTrace ) Th_Trace("END_FOOTER<br />\n", -1);
Modified src/update.c from [807405a4a8] to [74a2862e7d].
@@ -304,11 +304,11 @@ file_tree_name(zFile, &fname, 1); if( access(zFile, 0) ) yesRevert = 1; if( yesRevert==0 ){ char *prompt = mprintf("revert file %B? this will" - " destroy local changes [y/N]? ", + " destroy local changes (y/N)? ", &fname); blob_zero(&ans); prompt_user(prompt, &ans); free( prompt ); if( blob_str(&ans)[0]=='y' ){
Modified src/wiki.c from [c28c68f6a8] to [362c97b880].
@@ -630,18 +630,20 @@ */ void wikirules_page(void){ style_header("Wiki Formatting Rules"); @ <h2>Formatting Rule Summary</h2> @ <ol> - @ <li> Blank lines are paragraph breaks - @ <li> Bullets are "*" surrounded by two spaces at the beginning of the line. - @ <li> Enumeration items are a number surrounded by two space - @ at the beginning of a line. - @ <li> Indented pargraphs begin with a tab or two spaces. - @ <li> Hyperlinks are contained with square brackets: "[target]" - @ <li> Most ordinary HTML works. - @ <li> <verbatim> and <nowiki>. + @ <li>Blank lines are paragraph breaks</li> + @ <li>Bullets are "*" surrounded by two spaces at the beginning of the + @ line.</li> + @ <li>Enumeration items are "#" surrounded by two spaces at the beginning of + @ a line.</li> + @ <li>Indented pargraphs begin with a tab or two spaces.</li> + @ <li>Hyperlinks are contained with square brackets: "[target]" or + @ "[target|name]".</li> + @ <li>Most ordinary HTML works.</li> + @ <li><verbatim> and <nowiki>.</li> @ </ol> @ <p>We call the first five rules above "wiki" formatting rules. The @ last two rules are the HTML formatting rule.</p> @ <h2>Formatting Rule Details</h2> @ <ol> @@ -653,16 +655,13 @@ @ A bullet list item is a line that begins with a single "*" character @ surrounded on @ both sides by two or more spaces or by a tab. Only a single level @ of bullet list is supported by wiki. For nested lists, use HTML.</p> @ <li> <p><b>Enumeration Lists</b>. - @ An enumeration list item is a line that begins - @ with one or more digits optionally - @ followed by a "." and is surrounded on both sides by two or more spaces or - @ by a tab. The number is significant and becomes the number shown - @ in the rendered enumeration item. Only a single level of enumeration - @ list is supported by wiki. For nested enumerations or for + @ An enumeration list item is a line that begins with a single "#" character + @ surrounded on both sides by two or more spaces or by a tab. Only a single + @ level of enumeration list is supported by wiki. For nested lists or for @ enumerations that count using letters or roman numerials, use HTML.</p> @ <li> <p><b>Indented Paragraphs</b>. @ Any paragraph that begins with two or more spaces or a tab and @ which is not a bullet or enumeration list item is rendered @ indented. Only a single level of indentation is supported by wiki; use
Modified src/wikiformat.c from [0e2f5af22b] to [b1603948aa].
@@ -329,14 +329,15 @@ #define TOKEN_MARKUP 1 /* <...> */ #define TOKEN_CHARACTER 2 /* "&" or "<" not part of markup */ #define TOKEN_LINK 3 /* [...] */ #define TOKEN_PARAGRAPH 4 /* blank lines */ #define TOKEN_NEWLINE 5 /* A single "\n" */ -#define TOKEN_BULLET 6 /* " * " */ -#define TOKEN_ENUM 7 /* " \(?\d+[.)]? " */ -#define TOKEN_INDENT 8 /* " " */ -#define TOKEN_TEXT 9 /* None of the above */ +#define TOKEN_BUL_LI 6 /* " * " */ +#define TOKEN_NUM_LI 7 /* " # " */ +#define TOKEN_ENUM 8 /* " \(?\d+[.)]? " */ +#define TOKEN_INDENT 9 /* " " */ +#define TOKEN_TEXT 10 /* None of the above */ /* ** State flags */ #define AT_NEWLINE 0x001 /* At start of a line */ @@ -452,23 +453,23 @@ return i>1 && z[i]==';'; } } /* -** Check to see if the z[] string is the beginning of a wiki bullet. +** Check to see if the z[] string is the beginning of a wiki list item. ** If it is, return the length of the bullet text. Otherwise return 0. */ -static int bulletLength(const char *z){ +static int listItemLength(const char *z, const char listChar){ int i, n; n = 0; i = 0; while( z[n]==' ' || z[n]=='\t' ){ if( z[n]=='\t' ) i++; i++; n++; } - if( i<2 || z[n]!='*' ) return 0; + if( i<2 || z[n]!=listChar ) return 0; n++; i = 0; while( z[n]==' ' || z[n]=='\t' ){ if( z[n]=='\t' ) i++; i++; @@ -578,13 +579,18 @@ *pTokenType = TOKEN_NEWLINE; return 1; } } if( (p->state & AT_NEWLINE)!=0 && isspace(z[0]) ){ - n = bulletLength(z); + n = listItemLength(z, '*'); + if( n>0 ){ + *pTokenType = TOKEN_BUL_LI; + return n; + } + n = listItemLength(z, '#'); if( n>0 ){ - *pTokenType = TOKEN_BULLET; + *pTokenType = TOKEN_NUM_LI; return n; } n = enumLength(z); if( n>0 ){ *pTokenType = TOKEN_ENUM; @@ -838,11 +844,11 @@ /* ** Begin a new paragraph if that something that is needed. */ static void startAutoParagraph(Renderer *p){ - if( p->wantAutoParagraph==0 ) return; + if( p->wantAutoParagraph==0 || p->wikiList==MARKUP_OL || p->wikiList==MARKUP_UL ) return; blob_appendf(p->pOut, "<p>", -1); pushStack(p, MARKUP_P); p->wantAutoParagraph = 0; p->inAutoParagraph = 1; } @@ -1059,11 +1065,11 @@ case TOKEN_NEWLINE: { blob_append(p->pOut, "\n", 1); p->state |= AT_NEWLINE; break; } - case TOKEN_BULLET: { + case TOKEN_BUL_LI: { if( inlineOnly ){ blob_append(p->pOut, " • ", -1); }else{ if( p->wikiList!=MARKUP_UL ){ if( p->wikiList ){ @@ -1070,10 +1076,29 @@ popStackToTag(p, p->wikiList); } pushStack(p, MARKUP_UL); blob_append(p->pOut, "<ul>", 4); p->wikiList = MARKUP_UL; + } + popStackToTag(p, MARKUP_LI); + startAutoParagraph(p); + pushStack(p, MARKUP_LI); + blob_append(p->pOut, "<li>", 4); + } + break; + } + case TOKEN_NUM_LI: { + if( inlineOnly ){ + blob_append(p->pOut, " # ", -1); + }else{ + if( p->wikiList!=MARKUP_OL ){ + if( p->wikiList ){ + popStackToTag(p, p->wikiList); + } + pushStack(p, MARKUP_OL); + blob_append(p->pOut, "<ol>", 4); + p->wikiList = MARKUP_OL; } popStackToTag(p, MARKUP_LI); startAutoParagraph(p); pushStack(p, MARKUP_LI); blob_append(p->pOut, "<li>", 4);