From 9b8d3572a94969ca6e6aebc8dbe81c6213e4671a Mon Sep 17 00:00:00 2001 From: drh Date: Sat, 21 Apr 2012 11:33:39 +0000 Subject: [PATCH 01/10] If terminating interactive input to the command-line shell with ^D, issue an extra \n to move the cursor to the next line before exiting. FossilOrigin-Name: feff1ef0b8f7b51ae80a9d34380b46a5103bf6cd --- manifest | 15 +- manifest.uuid | 2 +- src/shell.c | 4 +- src/test_spellfix.c | 1951 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1963 insertions(+), 9 deletions(-) create mode 100644 src/test_spellfix.c diff --git a/manifest b/manifest index 164cba9eef..fd521769a8 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Do\snot\sconsider\sa\sDISTINCT\sclause\sredundant\sunless\sa\ssubset\sof\sthe\sresult-set\sis\scollectively\ssubject\sto\sa\sUNIQUE\sconstraint\sand\sit\scan\sbe\sguaranteed\sthat\sall\scolumns\sof\sthe\ssubset\sare\sNOT\sNULL\s(either\sdue\sto\sNOT\sNULL\sconstraints\sWHERE\sclause\sterms).\sFix\sfor\s[385a5b56b9]. -D 2012-04-20T16:59:24.885 +C If\sterminating\sinteractive\sinput\sto\sthe\scommand-line\sshell\swith\s^D,\sissue\nan\sextra\s\\n\sto\smove\sthe\scursor\sto\sthe\snext\sline\sbefore\sexiting. +D 2012-04-21T11:33:39.769 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 2f37e468503dbe79d35c9f6dffcf3fae1ae9ec20 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -174,7 +174,7 @@ F src/random.c cd4a67b3953b88019f8cd4ccd81394a8ddfaba50 F src/resolve.c 969ec2bc52db1b068054ecf5ddc74f244102a71d F src/rowset.c f6a49f3e9579428024662f6e2931832511f831a1 F src/select.c d7b9018b7dd2e821183d69477ab55c39b8272335 -F src/shell.c 11185a9a4574f363bd4268a2780d37480ae00040 +F src/shell.c dec1a1896ffa9eaedd6d9907cd43aca4b9d3295d F src/sqlite.h.in 4338f299fc83dada8407358d585c0e240ecb76a3 F src/sqlite3ext.h 6904f4aadf976f95241311fbffb00823075d9477 F src/sqliteInt.h c5e917c4f1453f3972b1fd0c81105dfe4f09cc32 @@ -218,6 +218,7 @@ F src/test_quota.h ee5da2ae7f84d1c8e0e0e2ab33f01d69f10259b5 F src/test_rtree.c aba603c949766c4193f1068b91c787f57274e0d9 F src/test_schema.c 8c06ef9ddb240c7a0fcd31bc221a6a2aade58bf0 F src/test_server.c 2f99eb2837dfa06a4aacf24af24c6affdf66a84f +F src/test_spellfix.c 495535f3eb57acdc384572da570e869bb1834bf4 F src/test_stat.c d7035cfcc0ff1f93c000b621f36524318e004e11 F src/test_superlock.c 2b97936ca127d13962c3605dbc9a4ef269c424cd F src/test_syscall.c a992d8c80ea91fbf21fb2dd570db40e77dd7e6ae @@ -994,7 +995,7 @@ F tool/tostr.awk e75472c2f98dd76e06b8c9c1367f4ab07e122d06 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f F tool/warnings-clang.sh a8a0a3babda96dfb1ff51adda3cbbf3dfb7266c2 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381 -P 9870e4c4fef10112c987c40cb1b95255a7214202 -R 7b0d4f32973f40703f4feb63496b017b -U dan -Z 293167d92a8f749c1bd20f8cdd573739 +P 7b8548b1872cc1225355ba8311e93dd08d6526e2 +R 6a28d821935f4caf8f00acbe95308ad8 +U drh +Z a4a6f8d23b8334a25a8498dbc291bb1a diff --git a/manifest.uuid b/manifest.uuid index 566056cbe1..7bec9efcd2 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7b8548b1872cc1225355ba8311e93dd08d6526e2 \ No newline at end of file +feff1ef0b8f7b51ae80a9d34380b46a5103bf6cd \ No newline at end of file diff --git a/src/shell.c b/src/shell.c index 2607e680de..7fc16ffe12 100644 --- a/src/shell.c +++ b/src/shell.c @@ -2572,7 +2572,9 @@ static int process_input(struct callback_data *p, FILE *in){ free(zLine); zLine = one_input_line(zSql, in); if( zLine==0 ){ - break; /* We have reached EOF */ + /* End of input */ + if( stdin_is_interactive ) printf("\n"); + break; } if( seenInterrupt ){ if( in!=0 ) break; diff --git a/src/test_spellfix.c b/src/test_spellfix.c new file mode 100644 index 0000000000..5a221e0b1b --- /dev/null +++ b/src/test_spellfix.c @@ -0,0 +1,1951 @@ +/* +** 2012 April 10 +** +** 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 module implements a VIRTUAL TABLE that can be used to search +** a large vocabulary for close matches. For example, this virtual +** table can be used to suggest corrections to misspelled words. Or, +** it could be used with FTS4 to do full-text search using potentially +** misspelled words. +** +** Create an instance of the virtual table this way: +** +** CREATE VIRTUAL TABLE demo USING spellfix1; +** +** The "spellfix1" term is the name of this module. The "demo" is the +** name of the virtual table you will be creating. The table is initially +** empty. You have to populate it with your vocabulary. Suppose you +** have a list of words in a table named "big_vocabulary". Then do this: +** +** INSERT INTO demo(word) SELECT word FROM big_vocabulary; +** +** If you intend to use this virtual table in cooperation with an FTS4 +** table (for spelling correctly of search terms) then you can extract +** the vocabulary using an fts3aux table: +** +** INSERT INTO demo(word) SELECT term FROM search_aux WHERE col='*'; +** +** You can also provide the virtual table with a "rank" for each word. +** The "rank" is an estimate of how common the word is. Larger numbers +** mean the word is more common. If you omit the rank when populating +** the table, then a rank of 1 is assumed. But if you have rank +** information, you can supply it and the virtual table will show a +** slight preference for selecting more commonly used terms. To +** populate the rank from an fts4aux table "search_aux" do something +** like this: +** +** INSERT INTO demo(word,rank) +** SELECT term, documents FROM search_aux WHERE col='*'; +** +** To query the virtual table, include a MATCH operator in the WHERE +** clause. For example: +** +** SELECT word FROM demo WHERE word MATCH 'kennasaw'; +** +** Using a dataset of American place names (derived from +** http://geonames.usgs.gov/domestic/download_data.htm) the query above +** returns 20 results beginning with: +** +** kennesaw +** kenosha +** kenesaw +** kenaga +** keanak +** +** If you append the character '*' to the end of the pattern, then +** a prefix search is performed. For example: +** +** SELECT word FROM demo WHERE word MATCH 'kennes*'; +** +** Yields 20 results beginning with: +** +** kennesaw +** kennestone +** kenneson +** kenneys +** keanes +** keenes +** +** The virtual table actually has a unique rowid with five columns plus three +** extra hidden columns. The columns are as follows: +** +** rowid A unique integer number associated with each +** vocabulary item in the table. This can be used +** as a foreign key on other tables in the database. +** +** word The text of the word that matches the pattern. +** Both word and pattern can contains unicode characters +** and can be mixed case. +** +** rank This is the rank of the word, as specified in the +** original INSERT statement. +** +** distance This is an edit distance or Levensthein distance going +** from the pattern to the word. +** +** langid This is the language-id of the word. All queries are +** against a single language-id, which defaults to 0. +** For any given query this value is the same on all rows. +** +** score The score is a combination of rank and distance. The +** idea is that a lower score is better. The virtual table +** attempts to find words with the lowest score and +** by default (unless overridden by ORDER BY) returns +** results in order of increasing score. +** +** top (HIDDEN) For any query, this value is the same on all +** rows. It is an integer which is the maximum number of +** rows that will be output. The actually number of rows +** output might be less than this number, but it will never +** be greater. The default value for top is 20, but that +** can be changed for each query by including a term of +** the form "top=N" in the WHERE clause of the query. +** +** scope (HIDDEN) For any query, this value is the same on all +** rows. The scope is a measure of how widely the virtual +** table looks for matching words. Smaller values of +** scope cause a broader search. The scope is normally +** choosen automatically and is capped at 4. Applications +** can change the scope by including a term of the form +** "scope=N" in the WHERE clause of the query. Increasing +** the scope will make the query run faster, but will reduce +** the possible corrections. +** +** srchcnt (HIDDEN) For any query, this value is the same on all +** rows. This value is an integer which is the number of +** of words examined using the edit-distance algorithm to +** find the top matches that are ultimately displayed. This +** value is for diagnostic use only. +** +** soundslike (HIDDEN) When inserting vocabulary entries, this field +** can be set to an spelling that matches what the word +** sounds like. See the DEALING WITH UNUSUAL AND DIFFICULT +** SPELLINGS section below for details. +** +** When inserting into or updating the virtual table, only the rowid, word, +** rank, and langid may be changes. Any attempt to set or modify the values +** of distance, score, top, scope, or srchcnt is silently ignored. +** +** ALGORITHM +** +** A shadow table named "%_vocab" (where the % is replaced by the name of +** the virtual table; Ex: "demo_vocab" for the "demo" virtual table) is +** constructed with these columns: +** +** id The unique id (INTEGER PRIMARY KEY) +** +** rank The rank of word. +** +** langid The language id for this entry. +** +** word The original UTF8 text of the vocabulary word +** +** k1 The word transliterated into lower-case ASCII. +** There is a standard table of mappings from non-ASCII +** characters into ASCII. Examples: "æ" -> "ae", +** "þ" -> "th", "ß" -> "ss", "á" -> "a", ... The +** accessory function spellfix1_translit(X) will do +** the non-ASCII to ASCII mapping. The built-in lower(X) +** function will convert to lower-case. Thus: +** k1 = lower(spellfix1_translit(word)). +** +** k2 This field holds a phonetic code derived from k1. Letters +** that have similar sounds are mapped into the same symbol. +** For example, all vowels and vowel clusters become the +** single symbol "A". And the letters "p", "b", "f", and +** "v" all become "B". All nasal sounds are represented +** as "N". And so forth. The mapping is base on +** ideas found in Soundex, Metaphone, and other +** long-standing phonetic matching systems. This key can +** be generated by the function spellfix1_charclass(X). +** Hence: k2 = spellfix1_charclass(k1) +** +** There is also a function for computing the Wagner edit distance or the +** Levenshtein distance between a pattern and a word. This function +** is exposed as spellfix1_editdist(X,Y). The edit distance function +** returns the "cost" of converting X into Y. Some transformations +** cost more than others. Changing one vowel into a different vowel, +** for example is relatively cheap, as is doubling a constant, or +** omitting the second character of a double-constant. Other transformations +** or more expensive. The idea is that the edit distance function returns +** a low cost of words that are similar and a higher cost for words +** that are futher apart. In this implementation, the maximum cost +** of any single-character edit (delete, insert, or substitute) is 100, +** with lower costs for some edits (such as transforming vowels). +** +** The "score" for a comparison is the edit distance between the pattern +** and the word, adjusted down by the base-2 logorithm of the word rank. +** For example, a match with distance 100 but rank 1000 would have a +** score of 122 (= 100 - log2(1000) + 32) where as a match with distance +** 100 with a rank of 1 would have a score of 131 (100 - log2(1) + 32). +** (NB: The constant 32 is added to each score to keep it from going +** negative in case the edit distance is zero.) In this way, frequently +** used words get a slightly lower cost which tends to move them toward +** the top of the list of alternative spellings. +** +** A straightforward implementation of a spelling corrector would be +** to compare the search term against every word in the vocabulary +** and select the 20 with the lowest scores. However, there will +** typically be hundreds of thousands or millions of words in the +** vocabulary, and so this approach is not fast enough. +** +** Suppose the term that is being spell-corrected is X. To limit +** the search space, X is converted to a k2-like key using the +** equivalent of: +** +** key = spellfix1_charclass(lower(spellfix1_translit(X))) +** +** This key is then limited to "scope" characters. The default scope +** value is 4, but an alternative scope can be specified using the +** "scope=N" term in the WHERE clause. After the key has been truncated, +** the edit distance is run against every term in the vocabulary that +** has a k2 value that begins with the abbreviated key. +** +** For example, suppose the input word is "Paskagula". The phonetic +** key is "BACACALA" which is then truncated to 4 characters "BACA". +** The edit distance is then run on the 4980 entries (out of +** 272,597 entries total) of the vocabulary whose k2 values begin with +** BACA, yielding "Pascagoula" as the best match. +** +** Only terms of the vocabulary with a matching langid are searched. +** Hence, the same table can contain entries from multiple languages +** and only the requested language will be used. The default langid +** is 0. +** +** DEALING WITH UNUSUAL AND DIFFICULT SPELLINGS +** +** The algorithm above works quite well for most cases, but there are +** exceptions. These exceptions can be dealt with by making additional +** entries in the virtual table using the "soundslike" column. +** +** For example, many words of Greek origin begin with letters "ps" where +** the "p" is silent. Ex: psalm, pseudonym, psoriasis, psyche. In +** another example, many Scottish surnames can be spelled with an +** initial "Mac" or "Mc". Thus, "MacKay" and "McKay" are both pronounced +** the same. +** +** Accommodation can be made for words that are not spelled as they +** sound by making additional entries into the virtual table for the +** same word, but adding an alternative spelling in the "soundslike" +** column. For example, the canonical entry for "psalm" would be this: +** +** INSERT INTO demo(word) VALUES('psalm'); +** +** To enhance the ability to correct the spelling of "salm" into +** "psalm", make an addition entry like this: +** +** INSERT INTO demo(word,soundslike) VALUES('psalm','salm'); +** +** It is ok to make multiple entries for the same word as long as +** each entry has a different soundslike value. Note that if no +** soundslike value is specified, the soundslike defaults to the word +** itself. +** +** Listed below are some cases where it might make sense to add additional +** soundslike entries. The specific entries will depend on the application +** and the target language. +** +** * Silent "p" in words beginning with "ps": psalm, psyche +** +** * Silent "p" in words beginning with "pn": pneumonia, pneumatic +** +** * Silent "p" in words beginning with "pt": pterodactyl, ptolemaic +** +** * Silent "d" in words beginning with "dj": djinn, Djikarta +** +** * Silent "k" in words beginning with "kn": knight, Knuthson +** +** * Silent "g" in words beginning with "gn": gnarly, gnome, gnat +** +** * "Mac" versus "Mc" beginning Scottish surnames +** +** * "Tch" sounds in Slavic words: Tchaikovsky vs. Chaykovsky +** +** * The letter "j" pronounced like "h" in Spanish: LaJolla +** +** * Words beginning with "wr" versus "r": write vs. rite +** +** * Miscellanous problem words such as "debt", "tsetse", +** "Nguyen", "Van Nuyes". +*/ +#if SQLITE_CORE +# include "sqliteInt.h" +#else +# include +# include +# include +# include "sqlite3ext.h" + SQLITE_EXTENSION_INIT1 +#endif /* !SQLITE_CORE */ + +/* +** Character classes for ASCII characters: +** +** 0 '' Silent letters: H W +** 1 'A' Any vowel: A E I O U (Y) +** 2 'B' A bilabeal stop or fricative: B F P V +** 3 'C' Other fricatives or back stops: C G J K Q S X Z +** 4 'D' Alveolar stops: D T +** 5 'H' Letter H at the beginning of a word +** 6 'L' Glides: L R +** 7 'M' Nasals: M N +** 8 'W' Letter W at the beginning of a word +** 9 'Y' Letter Y at the beginning of a word. +** 10 '9' A digit: 0 1 2 3 4 5 6 7 8 9 +** 11 ' ' White space +** 12 '?' Other. +*/ +#define CCLASS_SILENT 0 +#define CCLASS_VOWEL 1 +#define CCLASS_B 2 +#define CCLASS_C 3 +#define CCLASS_D 4 +#define CCLASS_H 5 +#define CCLASS_L 6 +#define CCLASS_M 7 +#define CCLASS_W 8 +#define CCLASS_Y 9 +#define CCLASS_DIGIT 10 +#define CCLASS_SPACE 11 +#define CCLASS_OTHER 12 + +/* +** The following table gives the character class for non-initial ASCII +** characters. +*/ +static const unsigned char midClass[] = { + /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */ + /* 0x */ 12, 12, 12, 12, 12, 12, 12, 12, 12, 11, 11, 12, 11, 12, 12, 12, + /* 1x */ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + /* 2x */ 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + /* 3x */ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 12, 12, 12, 12, 12, 12, + /* 4x */ 12, 1, 2, 3, 4, 1, 2, 3, 0, 1, 3, 3, 6, 7, 7, 1, + /* 5x */ 2, 3, 6, 3, 4, 1, 2, 0, 3, 1, 3, 12, 12, 12, 12, 12, + /* 6x */ 12, 1, 2, 3, 4, 1, 2, 3, 0, 1, 3, 3, 6, 7, 7, 1, + /* 7x */ 2, 3, 6, 3, 4, 1, 2, 0, 3, 1, 3, 12, 12, 12, 12, 12, +}; + +/* +** This tables gives the character class for ASCII characters that form the +** initial character of a word. The only difference from midClass is with +** the letters H, W, and Y. +*/ +static const unsigned char initClass[] = { + /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */ + /* 0x */ 12, 12, 12, 12, 12, 12, 12, 12, 12, 11, 11, 12, 11, 12, 12, 12, + /* 1x */ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + /* 2x */ 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + /* 3x */ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 12, 12, 12, 12, 12, 12, + /* 4x */ 12, 1, 2, 3, 4, 1, 2, 3, 5, 1, 3, 3, 6, 7, 7, 1, + /* 5x */ 2, 3, 6, 3, 4, 1, 2, 8, 3, 9, 3, 12, 12, 12, 12, 12, + /* 6x */ 12, 1, 2, 3, 4, 1, 2, 3, 5, 1, 3, 3, 6, 7, 7, 1, + /* 7x */ 2, 3, 6, 3, 4, 1, 2, 8, 3, 9, 3, 12, 12, 12, 12, 12, +}; + +/* +** Mapping from the character class number (0-12) to a symbol for each +** character class. Note that initClass[] can be used to map the class +** symbol back into the class number. +*/ +static const unsigned char className[] = ".ABCDHLMWY9 ?"; + +/* +** Generate a string of character classes corresponding to the +** ASCII characters in the input string zIn. If the input is not +** ASCII then the behavior is undefined. +** +** Space to hold the result is obtained from sqlite3_malloc() +** +** Return NULL if memory allocation fails. +*/ +static unsigned char *characterClassString(const unsigned char *zIn, int nIn){ + unsigned char *zOut = sqlite3_malloc( nIn + 1 ); + int i; + int nOut = 0; + char cPrev = 0x77; + const unsigned char *aClass = initClass; + + if( zOut==0 ) return 0; + for(i=0; i='A' && cTo<='Z') || (cTo>='a' && cTo<='z')) ){ + /* differ only in case */ + return 0; + } + classFrom = characterClass(cPrev, cFrom); + classTo = characterClass(cPrev, cTo); + if( classFrom==classTo ){ + /* Same character class */ + return classFrom=='A' ? 25 : 40; + } + if( classFrom>=CCLASS_B && classFrom<=CCLASS_Y + && classTo>=CCLASS_B && classTo<=CCLASS_Y ){ + /* Convert from one consonant to another, but in a different class */ + return 75; + } + /* Any other subsitution */ + return 100; +} + +/* +** Given two strings zA and zB which are pure ASCII, return the cost +** of transforming zA into zB. If zA ends with '*' assume that it is +** a prefix of zB and give only minimal penalty for extra characters +** on the end of zB. +** +** Smaller numbers mean a closer match. +** +** Negative values indicate an error: +** -1 One of the inputs is NULL +** -2 Non-ASCII characters on input +** -3 Unable to allocate memory +*/ +static int editdist(const char *zA, const char *zB){ + int nA, nB; /* Number of characters in zA[] and zB[] */ + int xA, xB; /* Loop counters for zA[] and zB[] */ + char cA, cB; /* Current character of zA and zB */ + char cAprev, cBprev; /* Previous character of zA and zB */ + int d; /* North-west cost value */ + int dc = 0; /* North-west character value */ + int res; /* Final result */ + int *m; /* The cost matrix */ + char *cx; /* Corresponding character values */ + int *toFree = 0; /* Malloced space */ + int mStack[60+15]; /* Stack space to use if not too much is needed */ + + /* Early out if either input is NULL */ + if( zA==0 || zB==0 ) return -1; + + /* Skip any common prefix */ + while( zA[0] && zA[0]==zB[0] ){ dc = zA[0]; zA++; zB++; } + if( zA[0]==0 && zB[0]==0 ) return 0; + +#if 0 + printf("A=\"%s\" B=\"%s\" dc=%c\n", zA, zB, dc?dc:' '); +#endif + + /* Verify input strings and measure their lengths */ + for(nA=0; zA[nA]; nA++){ + if( zA[nA]>127 ) return -2; + } + for(nB=0; zB[nB]; nB++){ + if( zB[nB]>127 ) return -2; + } + + /* Special processing if either string is empty */ + if( nA==0 ){ + cBprev = dc; + for(xB=res=0; (cB = zB[xB])!=0; xB++){ + res += insertOrDeleteCost(cBprev, cB)/FINAL_INS_COST_DIV; + cBprev = cB; + } + return res; + } + if( nB==0 ){ + cAprev = dc; + for(xA=res=0; (cA = zA[xA])!=0; xA++){ + res += insertOrDeleteCost(cAprev, cA); + cAprev = cA; + } + return res; + } + + /* A is a prefix of B */ + if( zA[0]=='*' && zA[1]==0 ) return 0; + + /* Allocate and initialize the Wagner matrix */ + if( nB<(sizeof(mStack)*4)/(sizeof(mStack[0])*5) ){ + m = mStack; + }else{ + m = toFree = sqlite3_malloc( (nB+1)*5*sizeof(m[0])/4 ); + if( m==0 ) return -3; + } + cx = (char*)&m[nB+1]; + + /* Compute the Wagner edit distance */ + m[0] = 0; + cx[0] = dc; + cBprev = dc; + for(xB=1; xB<=nB; xB++){ + cB = zB[xB-1]; + cx[xB] = cB; + m[xB] = m[xB-1] + insertOrDeleteCost(cBprev, cB); + cBprev = cB; + } + cAprev = dc; + for(xA=1; xA<=nA; xA++){ + int lastA = (xA==nA); + cA = zA[xA-1]; + if( cA=='*' && lastA ) break; + d = m[0]; + dc = cx[0]; + m[0] = d + insertOrDeleteCost(cAprev, cA); + cBprev = 0; + for(xB=1; xB<=nB; xB++){ + int totalCost, insCost, delCost, subCost, ncx; + cB = zB[xB-1]; + + /* Cost to insert cB */ + insCost = insertOrDeleteCost(cx[xB-1], cB); + if( lastA ) insCost /= FINAL_INS_COST_DIV; + + /* Cost to delete cA */ + delCost = insertOrDeleteCost(cx[xB], cA); + + /* Cost to substitute cA->cB */ + subCost = substituteCost(cx[xB-1], cA, cB); + + /* Best cost */ + totalCost = insCost + m[xB-1]; + ncx = cB; + if( (delCost + m[xB])nA ){ + res = m[nA]; + for(xB=nA+1; xB<=nB; xB++){ + if( m[xB]=0xc0 ){ + c = sqlite3Utf8Trans1[c-0xc0]; + while( i=xBtm ){ + x = (xTop + xBtm)/2; + if( translit[x].cFrom==c ){ + zOut[nOut++] = translit[x].cTo0; + if( translit[x].cTo1 ){ + zOut[nOut++] = translit[x].cTo1; + /* Add an extra "ch" after the "sh" for Щ and щ */ + if( c==0x0429 || c== 0x0449 ){ + zOut[nOut++] = 'c'; + zOut[nOut++] = 'h'; + } + } + c = 0; + break; + }else if( translit[x].cFrom>c ){ + xTop = x-1; + }else{ + xBtm = x+1; + } + } + if( c ) zOut[nOut++] = '?'; + } + } + zOut[nOut] = 0; + return zOut; +} + +/* +** spellfix1_translit(X) +** +** Convert a string that contains non-ASCII Roman characters into +** pure ASCII. +*/ +static void transliterateSqlFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const unsigned char *zIn = sqlite3_value_text(argv[0]); + int nIn = sqlite3_value_bytes(argv[0]); + unsigned char *zOut = transliterate(zIn, nIn); + if( zOut==0 ){ + sqlite3_result_error_nomem(context); + }else{ + sqlite3_result_text(context, (char*)zOut, -1, sqlite3_free); + } +} + +/* +** spellfix1_scriptcode(X) +** +** Try to determine the dominant script used by the word X and return +** its ISO 15924 numeric code. +** +** The current implementation only understands the following scripts: +** +** 215 (Latin) +** 220 (Cyrillic) +** 200 (Greek) +** +** This routine will return 998 if the input X contains characters from +** two or more of the above scripts or 999 if X contains no characters +** from any of the above scripts. +*/ +static void scriptCodeSqlFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const unsigned char *zIn = sqlite3_value_text(argv[0]); + int nIn = sqlite3_value_bytes(argv[0]); + int c, sz; + int scriptMask = 0; + int res; +# define SCRIPT_LATIN 0x0001 +# define SCRIPT_CYRILLIC 0x0002 +# define SCRIPT_GREEK 0x0004 + + while( nIn>0 ){ + c = utf8Read(zIn, nIn, &sz); + zIn += sz; + nIn -= sz; + if( c<0x02af ){ + scriptMask |= SCRIPT_LATIN; + }else if( c>=0x0400 && c<=0x04ff ){ + scriptMask |= SCRIPT_CYRILLIC; + }else if( c>=0x0386 && c<=0x03ce ){ + scriptMask |= SCRIPT_GREEK; + } + } + switch( scriptMask ){ + case 0: res = 999; break; + case SCRIPT_LATIN: res = 215; break; + case SCRIPT_CYRILLIC: res = 220; break; + case SCRIPT_GREEK: res = 200; break; + default: res = 998; break; + } + sqlite3_result_int(context, res); +} + +/***************************************************************************** +** Fuzzy-search virtual table +*****************************************************************************/ + +typedef struct spellfix1_vtab spellfix1_vtab; +typedef struct spellfix1_cursor spellfix1_cursor; + +/* Fuzzy-search virtual table object */ +struct spellfix1_vtab { + sqlite3_vtab base; /* Base class - must be first */ + sqlite3 *db; /* Database connection */ + char *zDbName; /* Name of database holding this table */ + char *zTableName; /* Name of the virtual table */ +}; + +/* Fuzzy-search cursor object */ +struct spellfix1_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + spellfix1_vtab *pVTab; /* The table to which this cursor belongs */ + int nRow; /* Number of rows of content */ + int nAlloc; /* Number of allocated rows */ + int iRow; /* Current row of content */ + int iLang; /* Value of the lang= constraint */ + int iTop; /* Value of the top= constraint */ + int iScope; /* Value of the scope= constraint */ + int nSearch; /* Number of vocabulary items checked */ + struct spellfix1_row { /* For each row of content */ + sqlite3_int64 iRowid; /* Rowid for this row */ + char *zWord; /* Text for this row */ + int iRank; /* Rank for this row */ + int iDistance; /* Distance from pattern for this row */ + int iScore; /* Score for sorting */ + } *a; +}; + +/* +** Construct one or more SQL statements from the format string given +** and then evaluate those statements. The success code is written +** into *pRc. +** +** If *pRc is initially non-zero then this routine is a no-op. +*/ +static void spellfix1DbExec( + int *pRc, /* Success code */ + sqlite3 *db, /* Database in which to run SQL */ + const char *zFormat, /* Format string for SQL */ + ... /* Arguments to the format string */ +){ + va_list ap; + char *zSql; + if( *pRc ) return; + va_start(ap, zFormat); + zSql = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + if( zSql==0 ){ + *pRc = SQLITE_NOMEM; + }else{ + *pRc = sqlite3_exec(db, zSql, 0, 0, 0); + sqlite3_free(zSql); + } +} + +/* +** xDisconnect/xDestroy method for the fuzzy-search module. +*/ +static int spellfix1Uninit(int isDestroy, sqlite3_vtab *pVTab){ + spellfix1_vtab *p = (spellfix1_vtab*)pVTab; + int rc = SQLITE_OK; + if( isDestroy ){ + sqlite3 *db = p->db; + spellfix1DbExec(&rc, db, "DROP TABLE IF EXISTS \"%w\".\"%w_vocab\"", + p->zDbName, p->zTableName); + } + if( rc==SQLITE_OK ){ + sqlite3_free(p->zTableName); + sqlite3_free(p); + } + return rc; +} +static int spellfix1Disconnect(sqlite3_vtab *pVTab){ + return spellfix1Uninit(0, pVTab); +} +static int spellfix1Destroy(sqlite3_vtab *pVTab){ + return spellfix1Uninit(1, pVTab); +} + +/* +** xConnect/xCreate method for the spellfix1 module. Arguments are: +** +** argv[0] -> module name ("spellfix1") +** argv[1] -> database name +** argv[2] -> table name +** argv[3].. -> optional arguments (currently ignored) +*/ +static int spellfix1Init( + int isCreate, + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVTab, + char **pzErr +){ + spellfix1_vtab *pNew = 0; + const char *zModule = argv[0]; + const char *zDbName = argv[1]; + const char *zTableName = argv[2]; + int nDbName; + int rc = SQLITE_OK; + + if( argc<3 ){ + *pzErr = sqlite3_mprintf( + "%s: wrong number of CREATE VIRTUAL TABLE arguments", argv[0] + ); + rc = SQLITE_ERROR; + }else{ + nDbName = strlen(zDbName); + pNew = sqlite3_malloc( sizeof(*pNew) + nDbName + 1); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(pNew, 0, sizeof(*pNew)); + pNew->zDbName = (char*)&pNew[1]; + memcpy(pNew->zDbName, zDbName, nDbName+1); + pNew->zTableName = sqlite3_mprintf("%s", zTableName); + pNew->db = db; + if( pNew->zTableName==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_declare_vtab(db, + "CREATE TABLE x(word,rank,distance,langid," + "score,top HIDDEN,scope HIDDEN,srchcnt HIDDEN," + "soundslike HIDDEN)" + ); + } + if( rc==SQLITE_OK && isCreate ){ + sqlite3_uint64 r; + spellfix1DbExec(&rc, db, + "CREATE TABLE IF NOT EXISTS \"%w\".\"%w_vocab\"(\n" + " id INTEGER PRIMARY KEY,\n" + " rank INT,\n" + " langid INT,\n" + " word TEXT,\n" + " k1 TEXT,\n" + " k2 TEXT\n" + ");\n", + zDbName, zTableName + ); + sqlite3_randomness(sizeof(r), &r); + spellfix1DbExec(&rc, db, + "CREATE INDEX IF NOT EXISTS \"%w\".\"%w_index_%llx\" " + "ON \"%w_vocab\"(langid,k2);", + zDbName, zModule, r, zTableName + ); + } + } + } + + *ppVTab = (sqlite3_vtab *)pNew; + return rc; +} + +/* +** The xConnect and xCreate methods +*/ +static int spellfix1Connect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVTab, + char **pzErr +){ + return spellfix1Init(0, db, pAux, argc, argv, ppVTab, pzErr); +} +static int spellfix1Create( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVTab, + char **pzErr +){ + return spellfix1Init(1, db, pAux, argc, argv, ppVTab, pzErr); +} + +/* +** Reset a cursor so that it contains zero rows of content but holds +** space for N rows. +*/ +static void spellfix1ResetCursor(spellfix1_cursor *pCur, int N){ + int i; + for(i=0; inRow; i++){ + sqlite3_free(pCur->a[i].zWord); + } + pCur->a = sqlite3_realloc(pCur->a, sizeof(pCur->a[0])*N); + pCur->nAlloc = N; + pCur->nRow = 0; + pCur->iRow = 0; + pCur->nSearch = 0; +} + +/* +** Close a fuzzy-search cursor. +*/ +static int spellfix1Close(sqlite3_vtab_cursor *cur){ + spellfix1_cursor *pCur = (spellfix1_cursor *)cur; + spellfix1ResetCursor(pCur, 0); + sqlite3_free(pCur); + return SQLITE_OK; +} + +/* +** Search for terms of these forms: +** +** (A) word MATCH $str +** (B) langid == $langid +** (C) top = $top +** (D) scope = $scope +** +** The plan number is a bit mask formed with these bits: +** +** 0x01 (A) is found +** 0x02 (B) is found +** 0x04 (C) is found +** 0x08 (D) is found +** +** filter.argv[*] values contains $str, $langid, $top, and $scope, +** if specified and in that order. +*/ +static int spellfix1BestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ + int iPlan = 0; + int iLangTerm = -1; + int iTopTerm = -1; + int iScopeTerm = -1; + int i; + const struct sqlite3_index_constraint *pConstraint; + pConstraint = pIdxInfo->aConstraint; + for(i=0; inConstraint; i++, pConstraint++){ + if( pConstraint->usable==0 ) continue; + + /* Terms of the form: word MATCH $str */ + if( (iPlan & 1)==0 + && pConstraint->iColumn==0 + && pConstraint->op==SQLITE_INDEX_CONSTRAINT_MATCH + ){ + iPlan |= 1; + pIdxInfo->aConstraintUsage[i].argvIndex = 1; + pIdxInfo->aConstraintUsage[i].omit = 1; + } + + /* Terms of the form: langid = $langid */ + if( (iPlan & 2)==0 + && pConstraint->iColumn==3 + && pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ + ){ + iPlan |= 2; + iLangTerm = i; + } + + /* Terms of the form: top = $top */ + if( (iPlan & 4)==0 + && pConstraint->iColumn==5 + && pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ + ){ + iPlan |= 4; + iTopTerm = i; + } + + /* Terms of the form: scope = $scope */ + if( (iPlan & 8)==0 + && pConstraint->iColumn==6 + && pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ + ){ + iPlan |= 8; + iScopeTerm = i; + } + } + if( iPlan&1 ){ + int idx = 2; + pIdxInfo->idxNum = iPlan; + if( pIdxInfo->nOrderBy==1 + && pIdxInfo->aOrderBy[0].iColumn==4 + && pIdxInfo->aOrderBy[0].desc==0 + ){ + pIdxInfo->orderByConsumed = 1; /* Default order by iScore */ + } + if( iPlan&2 ){ + pIdxInfo->aConstraintUsage[iLangTerm].argvIndex = idx++; + pIdxInfo->aConstraintUsage[iLangTerm].omit = 1; + } + if( iPlan&4 ){ + pIdxInfo->aConstraintUsage[iTopTerm].argvIndex = idx++; + pIdxInfo->aConstraintUsage[iTopTerm].omit = 1; + } + if( iPlan&8 ){ + pIdxInfo->aConstraintUsage[iScopeTerm].argvIndex = idx++; + pIdxInfo->aConstraintUsage[iScopeTerm].omit = 1; + } + pIdxInfo->estimatedCost = (double)10000; + }else{ + pIdxInfo->idxNum = 0; + pIdxInfo->estimatedCost = (double)10000000; + } + return SQLITE_OK; +} + +/* +** Open a new fuzzy-search cursor. +*/ +static int spellfix1Open(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ + spellfix1_vtab *p = (spellfix1_vtab*)pVTab; + spellfix1_cursor *pCur; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + pCur->pVTab = p; + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* +** Adjust a distance measurement by the words rank in order to show +** preference to common words. +*/ +static int spellfix1Score(int iDistance, int iRank){ + int iLog2; + for(iLog2=0; iRank>0; iLog2++, iRank>>=1){} + return iDistance + 32 - iLog2; +} + +/* +** Compare two spellfix1_row objects for sorting purposes in qsort() such +** that they sort in order of increasing distance. +*/ +static int spellfix1RowCompare(const void *A, const void *B){ + const struct spellfix1_row *a = (const struct spellfix1_row*)A; + const struct spellfix1_row *b = (const struct spellfix1_row*)B; + return a->iScore - b->iScore; +} + +/* +** This version of the xFilter method work if the MATCH term is present +** and we are doing a scan. +*/ +static int spellfix1FilterForMatch( + spellfix1_cursor *pCur, + int idxNum, + int argc, + sqlite3_value **argv +){ + const unsigned char *zPatternIn; + char *zPattern; + int nPattern; + char *zClass; + int nClass; + int iLimit = 20; + int iScope = 4; + int iLang = 0; + char *zSql; + int rc; + sqlite3_stmt *pStmt; + int idx = 1; + spellfix1_vtab *p = pCur->pVTab; + + if( idxNum&2 ){ + iLang = sqlite3_value_int(argv[idx++]); + } + if( idxNum&4 ){ + iLimit = sqlite3_value_int(argv[idx++]); + if( iLimit<1 ) iLimit = 1; + } + if( idxNum&8 ){ + iScope = sqlite3_value_int(argv[idx++]); + if( iScope<1 ) iScope = 1; + } + spellfix1ResetCursor(pCur, iLimit); + zPatternIn = sqlite3_value_text(argv[0]); + if( zPatternIn==0 ) return SQLITE_OK; + zPattern = (char*)transliterate(zPatternIn, sqlite3_value_bytes(argv[0])); + if( zPattern==0 ) return SQLITE_NOMEM; + nPattern = strlen(zPattern); + if( zPattern[nPattern-1]=='*' ) nPattern--; + if( nPatterniScope ){ + zClass[iScope] = 0; + nClass = iScope; + } + zSql = sqlite3_mprintf( + "SELECT id, word, rank, k1" + " FROM \"%w\".\"%w_vocab\"" + " WHERE langid=%d AND k2 GLOB '%q*'", + p->zDbName, p->zTableName, iLang, zClass + ); + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + if( rc==SQLITE_OK ){ + const char *zK1; + int iDist; + int iRank; + int iScore; + int iWorst = 999999999; + int idx; + int idxWorst; + int i; + + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + zK1 = (const char*)sqlite3_column_text(pStmt, 3); + if( zK1==0 ) continue; + pCur->nSearch++; + iRank = sqlite3_column_int(pStmt, 2); + iDist = editdist(zPattern, zK1); + iScore = spellfix1Score(iDist,iRank); + if( pCur->nRownAlloc ){ + idx = pCur->nRow; + }else if( iScorea[idx].zWord); + }else{ + continue; + } + pCur->a[idx].zWord = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 1)); + pCur->a[idx].iRowid = sqlite3_column_int64(pStmt, 0); + pCur->a[idx].iRank = iRank; + pCur->a[idx].iDistance = iDist; + pCur->a[idx].iScore = iScore; + if( pCur->nRownAlloc ) pCur->nRow++; + if( pCur->nRow==pCur->nAlloc ){ + iWorst = pCur->a[0].iScore; + idxWorst = 0; + for(i=1; inRow; i++){ + iScore = pCur->a[i].iScore; + if( iWorsta, pCur->nRow, sizeof(pCur->a[0]), spellfix1RowCompare); + pCur->iTop = iLimit; + pCur->iScope = iScope; + sqlite3_finalize(pStmt); + sqlite3_free(zPattern); + sqlite3_free(zClass); + return SQLITE_OK; +} + +/* +** This version of xFilter handles a full-table scan case +*/ +static int spellfix1FilterForFullScan( + spellfix1_cursor *pCur, + int idxNum, + int argc, + sqlite3_value **argv +){ + spellfix1ResetCursor(pCur, 0); + return SQLITE_OK; +} + + +/* +** Called to "rewind" a cursor back to the beginning so that +** it starts its output over again. Always called at least once +** prior to any spellfix1Column, spellfix1Rowid, or spellfix1Eof call. +*/ +static int spellfix1Filter( + sqlite3_vtab_cursor *cur, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + spellfix1_cursor *pCur = (spellfix1_cursor *)cur; + int rc; + if( idxNum & 1 ){ + rc = spellfix1FilterForMatch(pCur, idxNum, argc, argv); + }else{ + rc = spellfix1FilterForFullScan(pCur, idxNum, argc, argv); + } + return rc; +} + + +/* +** Advance a cursor to its next row of output +*/ +static int spellfix1Next(sqlite3_vtab_cursor *cur){ + spellfix1_cursor *pCur = (spellfix1_cursor *)cur; + if( pCur->iRow < pCur->nRow ) pCur->iRow++; + return SQLITE_OK; +} + +/* +** Return TRUE if we are at the end-of-file +*/ +static int spellfix1Eof(sqlite3_vtab_cursor *cur){ + spellfix1_cursor *pCur = (spellfix1_cursor *)cur; + return pCur->iRow>=pCur->nRow; +} + +/* +** Return columns from the current row. +*/ +static int spellfix1Column(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ + spellfix1_cursor *pCur = (spellfix1_cursor*)cur; + switch( i ){ + case 0: { + sqlite3_result_text(ctx, pCur->a[pCur->iRow].zWord, -1, SQLITE_STATIC); + break; + } + case 1: { + sqlite3_result_int(ctx, pCur->a[pCur->iRow].iRank); + break; + } + case 2: { + sqlite3_result_int(ctx, pCur->a[pCur->iRow].iDistance); + break; + } + case 3: { + sqlite3_result_int(ctx, pCur->iLang); + break; + } + case 4: { + sqlite3_result_int(ctx, pCur->a[pCur->iRow].iScore); + break; + } + case 5: { + sqlite3_result_int(ctx, pCur->iTop); + break; + } + case 6: { + sqlite3_result_int(ctx, pCur->iScope); + break; + } + case 7: { + sqlite3_result_int(ctx, pCur->nSearch); + break; + } + default: { + sqlite3_result_null(ctx); + break; + } + } + return SQLITE_OK; +} + +/* +** The rowid. +*/ +static int spellfix1Rowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + spellfix1_cursor *pCur = (spellfix1_cursor*)cur; + *pRowid = pCur->a[pCur->iRow].iRowid; + return SQLITE_OK; +} + +/* +** The xUpdate() method. +*/ +static int spellfix1Update( + sqlite3_vtab *pVTab, + int argc, + sqlite3_value **argv, + sqlite_int64 *pRowid +){ + int rc = SQLITE_OK; + sqlite3_int64 rowid, newRowid; + spellfix1_vtab *p = (spellfix1_vtab*)pVTab; + sqlite3 *db = p->db; + + if( argc==1 ){ + /* A delete operation on the rowid given by argv[0] */ + rowid = *pRowid = sqlite3_value_int64(argv[0]); + spellfix1DbExec(&rc, db, "DELETE FROM \"%w\".\"%w_vocab\" " + " WHERE id=%lld", + p->zDbName, p->zTableName, rowid); + }else{ + const unsigned char *zWord = sqlite3_value_text(argv[2]); + int nWord = sqlite3_value_bytes(argv[2]); + int iLang = sqlite3_value_int(argv[5]); + int iRank = sqlite3_value_int(argv[3]); + const unsigned char *zSoundslike = sqlite3_value_text(argv[10]); + int nSoundslike = sqlite3_value_bytes(argv[10]); + char *zK1, *zK2; + int i; + char c; + + if( zWord==0 ){ + pVTab->zErrMsg = sqlite3_mprintf("%w.word may not be NULL", + p->zTableName); + return SQLITE_CONSTRAINT; + } + if( iRank<1 ) iRank = 1; + if( zSoundslike ){ + zK1 = (char*)transliterate(zSoundslike, nSoundslike); + }else{ + zK1 = (char*)transliterate(zWord, nWord); + } + if( zK1==0 ) return SQLITE_NOMEM; + for(i=0; (c = zK1[i])!=0; i++){ + if( c>='A' && c<='Z' ) zK1[i] += 'a' - 'A'; + } + zK2 = (char*)characterClassString((const unsigned char*)zK1, i); + if( zK2==0 ){ + sqlite3_free(zK1); + return SQLITE_NOMEM; + } + if( sqlite3_value_type(argv[0])==SQLITE_NULL ){ + spellfix1DbExec(&rc, db, + "INSERT INTO \"%w\".\"%w_vocab\"(rank,langid,word,k1,k2) " + "VALUES(%d,%d,%Q,%Q,%Q)", + p->zDbName, p->zTableName, + iRank, iLang, zWord, zK1, zK2 + ); + *pRowid = sqlite3_last_insert_rowid(db); + }else{ + rowid = sqlite3_value_int64(argv[0]); + newRowid = *pRowid = sqlite3_value_int64(argv[1]); + spellfix1DbExec(&rc, db, + "UPDATE \"%w\".\"%w_vocab\" SET id=%lld, rank=%d, lang=%d," + " word=%Q, rank=%d, k1=%Q, k2=%Q WHERE id=%lld", + p->zDbName, p->zTableName, newRowid, iRank, iLang, + zWord, zK1, zK2, rowid + ); + } + sqlite3_free(zK1); + sqlite3_free(zK2); + } + return rc; +} + +/* +** Rename the spellfix1 table. +*/ +static int spellfix1Rename(sqlite3_vtab *pVTab, const char *zNew){ + spellfix1_vtab *p = (spellfix1_vtab*)pVTab; + sqlite3 *db = p->db; + int rc = SQLITE_OK; + char *zNewName = sqlite3_mprintf("%s", zNew); + if( zNewName==0 ){ + return SQLITE_NOMEM; + } + spellfix1DbExec(&rc, db, + "ALTER TABLE \"%w\".\"%w_vocab\" RENAME TO \"%w_vocab\"", + p->zDbName, p->zTableName, zNewName + ); + if( rc==SQLITE_OK ){ + sqlite3_free(p->zTableName); + p->zTableName = zNewName; + } + return rc; +} + + +/* +** A virtual table module that provides fuzzy search. +*/ +static sqlite3_module spellfix1Module = { + 0, /* iVersion */ + spellfix1Create, /* xCreate - handle CREATE VIRTUAL TABLE */ + spellfix1Connect, /* xConnect - reconnected to an existing table */ + spellfix1BestIndex, /* xBestIndex - figure out how to do a query */ + spellfix1Disconnect, /* xDisconnect - close a connection */ + spellfix1Destroy, /* xDestroy - handle DROP TABLE */ + spellfix1Open, /* xOpen - open a cursor */ + spellfix1Close, /* xClose - close a cursor */ + spellfix1Filter, /* xFilter - configure scan constraints */ + spellfix1Next, /* xNext - advance a cursor */ + spellfix1Eof, /* xEof - check for end of scan */ + spellfix1Column, /* xColumn - read data */ + spellfix1Rowid, /* xRowid - read data */ + spellfix1Update, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + spellfix1Rename, /* xRename */ +}; + +/* +** Register the various functions and the virtual table. +*/ +static int spellfix1Register(sqlite3 *db){ + int nErr = 0; + int i; + nErr += sqlite3_create_function(db, "spellfix1_translit", 1, SQLITE_UTF8, 0, + transliterateSqlFunc, 0, 0); + nErr += sqlite3_create_function(db, "spellfix1_editdist", 2, SQLITE_UTF8, 0, + editdistSqlFunc, 0, 0); + nErr += sqlite3_create_function(db, "spellfix1_charclass", 1, SQLITE_UTF8, 0, + characterClassSqlFunc, 0, 0); + nErr += sqlite3_create_function(db, "spellfix1_scriptcode", 1, SQLITE_UTF8, 0, + scriptCodeSqlFunc, 0, 0); + nErr += sqlite3_create_module(db, "spellfix1", &spellfix1Module, 0); + + /* Verify sanity of the translit[] table */ + for(i=0; i Date: Mon, 23 Apr 2012 12:38:05 +0000 Subject: [PATCH 02/10] Update the ".table" command in the shell to show all tables in all attached databases, and to avoid using the deprecated sqlite3_get_table() function. FossilOrigin-Name: ce2d06e2533763a8008e7a405630293d8f9a3108 --- manifest | 12 +++---- manifest.uuid | 2 +- src/shell.c | 99 ++++++++++++++++++++++++++++++++------------------- 3 files changed, 70 insertions(+), 43 deletions(-) diff --git a/manifest b/manifest index fd521769a8..9c161337e4 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C If\sterminating\sinteractive\sinput\sto\sthe\scommand-line\sshell\swith\s^D,\sissue\nan\sextra\s\\n\sto\smove\sthe\scursor\sto\sthe\snext\sline\sbefore\sexiting. -D 2012-04-21T11:33:39.769 +C Update\sthe\s".table"\scommand\sin\sthe\sshell\sto\sshow\sall\stables\sin\sall\nattached\sdatabases,\sand\sto\savoid\susing\sthe\sdeprecated\ssqlite3_get_table()\nfunction. +D 2012-04-23T12:38:05.598 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 2f37e468503dbe79d35c9f6dffcf3fae1ae9ec20 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -174,7 +174,7 @@ F src/random.c cd4a67b3953b88019f8cd4ccd81394a8ddfaba50 F src/resolve.c 969ec2bc52db1b068054ecf5ddc74f244102a71d F src/rowset.c f6a49f3e9579428024662f6e2931832511f831a1 F src/select.c d7b9018b7dd2e821183d69477ab55c39b8272335 -F src/shell.c dec1a1896ffa9eaedd6d9907cd43aca4b9d3295d +F src/shell.c 151a17fe8464e40097c13672b6f756a38988147a F src/sqlite.h.in 4338f299fc83dada8407358d585c0e240ecb76a3 F src/sqlite3ext.h 6904f4aadf976f95241311fbffb00823075d9477 F src/sqliteInt.h c5e917c4f1453f3972b1fd0c81105dfe4f09cc32 @@ -995,7 +995,7 @@ F tool/tostr.awk e75472c2f98dd76e06b8c9c1367f4ab07e122d06 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f F tool/warnings-clang.sh a8a0a3babda96dfb1ff51adda3cbbf3dfb7266c2 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381 -P 7b8548b1872cc1225355ba8311e93dd08d6526e2 -R 6a28d821935f4caf8f00acbe95308ad8 +P feff1ef0b8f7b51ae80a9d34380b46a5103bf6cd +R 684c337b6696cf99874c83d471c520fc U drh -Z a4a6f8d23b8334a25a8498dbc291bb1a +Z 459397b23e497549b544849ef1acdc2b diff --git a/manifest.uuid b/manifest.uuid index 7bec9efcd2..27adcb8c75 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -feff1ef0b8f7b51ae80a9d34380b46a5103bf6cd \ No newline at end of file +ce2d06e2533763a8008e7a405630293d8f9a3108 \ No newline at end of file diff --git a/src/shell.c b/src/shell.c index 7fc16ffe12..34b72c4d62 100644 --- a/src/shell.c +++ b/src/shell.c @@ -2248,46 +2248,71 @@ static int do_meta_command(char *zLine, struct callback_data *p){ }else if( c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0 && nArg<3 ){ + sqlite3_stmt *pStmt; char **azResult; - int nRow; - char *zErrMsg; + int nRow, nAlloc; + char *zSql = 0; + int ii; open_db(p); - if( nArg==1 ){ - rc = sqlite3_get_table(p->db, - "SELECT name FROM sqlite_master " - "WHERE type IN ('table','view') AND name NOT LIKE 'sqlite_%' " - "UNION ALL " - "SELECT name FROM sqlite_temp_master " - "WHERE type IN ('table','view') " - "ORDER BY 1", - &azResult, &nRow, 0, &zErrMsg - ); - }else{ - zShellStatic = azArg[1]; - rc = sqlite3_get_table(p->db, - "SELECT name FROM sqlite_master " - "WHERE type IN ('table','view') AND name LIKE shellstatic() " - "UNION ALL " - "SELECT name FROM sqlite_temp_master " - "WHERE type IN ('table','view') AND name LIKE shellstatic() " - "ORDER BY 1", - &azResult, &nRow, 0, &zErrMsg - ); - zShellStatic = 0; + rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); + if( rc ) return rc; + zSql = sqlite3_mprintf( + "SELECT name FROM sqlite_master" + " WHERE type IN ('table','view')" + " AND name NOT LIKE 'sqlite_%%'" + " AND name LIKE ?1"); + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1); + if( zDbName==0 || strcmp(zDbName,"main")==0 ) continue; + if( strcmp(zDbName,"temp")==0 ){ + zSql = sqlite3_mprintf( + "%z UNION ALL " + "SELECT 'temp.' || name FROM sqlite_temp_master" + " WHERE type IN ('table','view')" + " AND name NOT LIKE 'sqlite_%%'" + " AND name LIKE ?1", zSql); + }else{ + zSql = sqlite3_mprintf( + "%z UNION ALL " + "SELECT '%q.' || name FROM \"%w\".sqlite_master" + " WHERE type IN ('table','view')" + " AND name NOT LIKE 'sqlite_%%'" + " AND name LIKE ?1", zSql, zDbName, zDbName); + } } - if( zErrMsg ){ - fprintf(stderr,"Error: %s\n", zErrMsg); - sqlite3_free(zErrMsg); - rc = 1; - }else if( rc != SQLITE_OK ){ - fprintf(stderr,"Error: querying sqlite_master and sqlite_temp_master\n"); - rc = 1; + sqlite3_finalize(pStmt); + zSql = sqlite3_mprintf("%z ORDER BY 1", zSql); + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + if( rc ) return rc; + nRow = nAlloc = 0; + azResult = 0; + if( nArg>1 ){ + sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT); }else{ + sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC); + } + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + if( nRow>=nAlloc ){ + char **azNew; + int n = nAlloc*2 + 10; + azNew = sqlite3_realloc(azResult, sizeof(azResult[0])*n); + if( azNew==0 ){ + fprintf(stderr, "Error: out of memory\n"); + break; + } + nAlloc = n; + azResult = azNew; + } + azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); + if( azResult[nRow] ) nRow++; + } + sqlite3_finalize(pStmt); + if( nRow>0 ){ int len, maxlen = 0; int i, j; int nPrintCol, nPrintRow; - for(i=1; i<=nRow; i++){ - if( azResult[i]==0 ) continue; + for(i=0; imaxlen ) maxlen = len; } @@ -2295,14 +2320,15 @@ static int do_meta_command(char *zLine, struct callback_data *p){ if( nPrintCol<1 ) nPrintCol = 1; nPrintRow = (nRow + nPrintCol - 1)/nPrintCol; for(i=0; i=8 && strncmp(azArg[0], "testctrl", n)==0 && nArg>=2 ){ @@ -2437,6 +2463,7 @@ static int do_meta_command(char *zLine, struct callback_data *p){ }else if( c=='t' && strncmp(azArg[0], "trace", n)==0 && nArg>1 ){ + open_db(p); output_file_close(p->traceOut); p->traceOut = output_file_open(azArg[1]); #ifndef SQLITE_OMIT_TRACE From b202d70a8780f09d1e72474c693133fd0c75dbab Mon Sep 17 00:00:00 2001 From: drh Date: Tue, 24 Apr 2012 12:12:57 +0000 Subject: [PATCH 03/10] Fix a sign-extension problem for BLOB output in ".insert" mode of the command-line shell. FossilOrigin-Name: 282f2a74c23aa3fca6087bdeaf5d961b4f5bbe47 --- manifest | 12 ++++++------ manifest.uuid | 2 +- src/shell.c | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/manifest b/manifest index 9c161337e4..fa3712737a 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Update\sthe\s".table"\scommand\sin\sthe\sshell\sto\sshow\sall\stables\sin\sall\nattached\sdatabases,\sand\sto\savoid\susing\sthe\sdeprecated\ssqlite3_get_table()\nfunction. -D 2012-04-23T12:38:05.598 +C Fix\sa\ssign-extension\sproblem\sfor\sBLOB\soutput\sin\s".insert"\smode\sof\sthe\ncommand-line\sshell. +D 2012-04-24T12:12:57.348 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 2f37e468503dbe79d35c9f6dffcf3fae1ae9ec20 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -174,7 +174,7 @@ F src/random.c cd4a67b3953b88019f8cd4ccd81394a8ddfaba50 F src/resolve.c 969ec2bc52db1b068054ecf5ddc74f244102a71d F src/rowset.c f6a49f3e9579428024662f6e2931832511f831a1 F src/select.c d7b9018b7dd2e821183d69477ab55c39b8272335 -F src/shell.c 151a17fe8464e40097c13672b6f756a38988147a +F src/shell.c 04399b2f9942bd02ed5ffee3b84bcdb39c52a1e6 F src/sqlite.h.in 4338f299fc83dada8407358d585c0e240ecb76a3 F src/sqlite3ext.h 6904f4aadf976f95241311fbffb00823075d9477 F src/sqliteInt.h c5e917c4f1453f3972b1fd0c81105dfe4f09cc32 @@ -995,7 +995,7 @@ F tool/tostr.awk e75472c2f98dd76e06b8c9c1367f4ab07e122d06 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f F tool/warnings-clang.sh a8a0a3babda96dfb1ff51adda3cbbf3dfb7266c2 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381 -P feff1ef0b8f7b51ae80a9d34380b46a5103bf6cd -R 684c337b6696cf99874c83d471c520fc +P ce2d06e2533763a8008e7a405630293d8f9a3108 +R 3cb8ce97e629e462f046b05d1b3f651d U drh -Z 459397b23e497549b544849ef1acdc2b +Z 1d5f5dcc20d86aab9e41b01973a0dba1 diff --git a/manifest.uuid b/manifest.uuid index 27adcb8c75..3baf8a3fc6 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -ce2d06e2533763a8008e7a405630293d8f9a3108 \ No newline at end of file +282f2a74c23aa3fca6087bdeaf5d961b4f5bbe47 \ No newline at end of file diff --git a/src/shell.c b/src/shell.c index 34b72c4d62..73341fc3cc 100644 --- a/src/shell.c +++ b/src/shell.c @@ -499,7 +499,7 @@ static void output_hex_blob(FILE *out, const void *pBlob, int nBlob){ int i; char *zBlob = (char *)pBlob; fprintf(out,"X'"); - for(i=0; i Date: Tue, 24 Apr 2012 12:46:05 +0000 Subject: [PATCH 04/10] Move the shell test scripts into the test/ subfolder so that they are run automatically by "make test". FossilOrigin-Name: 9fb7da6904e479f4671eeebf1a4b7e4e4e4f2b7b --- manifest | 22 ++++++++-------- manifest.uuid | 2 +- {tool => test}/shell1.test | 50 +++++++++---------------------------- {tool => test}/shell2.test | 49 +++++++++--------------------------- {tool => test}/shell3.test | 51 +++++++++----------------------------- {tool => test}/shell4.test | 39 ++++++++++------------------- {tool => test}/shell5.test | 40 ++++++++++-------------------- test/tester.tcl | 10 ++++++++ 8 files changed, 84 insertions(+), 179 deletions(-) rename {tool => test}/shell1.test (96%) rename {tool => test}/shell2.test (86%) rename {tool => test}/shell3.test (76%) rename {tool => test}/shell4.test (83%) rename {tool => test}/shell5.test (92%) diff --git a/manifest b/manifest index fa3712737a..1e7684a508 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sa\ssign-extension\sproblem\sfor\sBLOB\soutput\sin\s".insert"\smode\sof\sthe\ncommand-line\sshell. -D 2012-04-24T12:12:57.348 +C Move\sthe\sshell\stest\sscripts\sinto\sthe\stest/\ssubfolder\sso\sthat\sthey\sare\nrun\sautomatically\sby\s"make\stest". +D 2012-04-24T12:46:05.097 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 2f37e468503dbe79d35c9f6dffcf3fae1ae9ec20 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -689,6 +689,11 @@ F test/shared6.test 866bb4982c45ce216c61ded5e8fde4e7e2f3ffa9 F test/shared7.test 960760bc8d03e1419e70dea69cf41db62853616e F test/shared_err.test 91e26ec4f3fbe07951967955585137e2f18993de F test/sharedlock.test ffa0a3c4ac192145b310f1254f8afca4d553eabf +F test/shell1.test 6d5a331713a7eadde26802b6be7f6330a1064319 w tool/shell1.test +F test/shell2.test 037d6ad16e873354195d30bb2dc4b5321788154a w tool/shell2.test +F test/shell3.test 9196c42772d575685e722c92b4b39053c6ebba59 w tool/shell3.test +F test/shell4.test aa4eef8118b412d1a01477a53426ece169ea86a9 w tool/shell4.test +F test/shell5.test fa2188bbb13fe2d183fd04a5f7b512650c35ef5d w tool/shell5.test F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3 F test/shrink.test 8c70f62b6e8eb4d54533de6d65bd06b1b9a17868 F test/sidedelete.test f0ad71abe6233e3b153100f3b8d679b19a488329 @@ -720,7 +725,7 @@ F test/tclsqlite.test 1597d353308531527583481d14d9da52ea8ed0af F test/tempdb.test 19d0f66e2e3eeffd68661a11c83ba5e6ace9128c F test/temptable.test 51edd31c65ed1560dd600b1796e8325df96318e2 F test/temptrigger.test 26670ed7a39cf2296a7f0a9e0a1d7bdb7abe936d -F test/tester.tcl 7db9e90e4a9cc57ea92118ee011634f86dc8e847 +F test/tester.tcl dc0f9daa0a7c257df86a1a7603d5df2236e49145 F test/thread001.test 7cc2ce08f9cde95964736d11e91f9ab610f82f91 F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -972,11 +977,6 @@ F tool/omittest.tcl 72a49b8a9a8b0bf213a438180307a0df836d4380 F tool/opcodeDoc.awk b3a2a3d5d3075b8bd90b7afe24283efdd586659c F tool/restore_jrnl.tcl 6957a34f8f1f0f8285e07536225ec3b292a9024a F tool/rollback-test.c 9fc98427d1e23e84429d7e6d07d9094fbdec65a5 -F tool/shell1.test 20dfe7099cf2afe37aecd69afb7678d14f7a0abf -F tool/shell2.test 5dc76b8005b465f420fed8241621da7513060ff3 -F tool/shell3.test 4fad469e8003938426355afdf34155f08c587836 -F tool/shell4.test 35f9c3d452b4e76d5013c63e1fd07478a62f14ce -F tool/shell5.test 0e987fb8d40638bb5c90163cb58cbe3e07dbed56 F tool/showdb.c 2e28d8e499b016485672e9a7ac65dacc0d28ff69 F tool/showjournal.c b62cecaab86a4053d944c276bb5232e4d17ece02 F tool/showwal.c f09e5a80a293919290ec85a6a37c85a5ddcf37d9 @@ -995,7 +995,7 @@ F tool/tostr.awk e75472c2f98dd76e06b8c9c1367f4ab07e122d06 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f F tool/warnings-clang.sh a8a0a3babda96dfb1ff51adda3cbbf3dfb7266c2 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381 -P ce2d06e2533763a8008e7a405630293d8f9a3108 -R 3cb8ce97e629e462f046b05d1b3f651d +P 282f2a74c23aa3fca6087bdeaf5d961b4f5bbe47 +R fb1a373c8aab089fc987aaa3b3a46480 U drh -Z 1d5f5dcc20d86aab9e41b01973a0dba1 +Z d17213a51e75d7b6538576f270927349 diff --git a/manifest.uuid b/manifest.uuid index 3baf8a3fc6..4c160508c7 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -282f2a74c23aa3fca6087bdeaf5d961b4f5bbe47 \ No newline at end of file +9fb7da6904e479f4671eeebf1a4b7e4e4e4f2b7b \ No newline at end of file diff --git a/tool/shell1.test b/test/shell1.test similarity index 96% rename from tool/shell1.test rename to test/shell1.test index 9dd9df5555..473d5a3845 100644 --- a/tool/shell1.test +++ b/test/shell1.test @@ -11,7 +11,6 @@ # # The focus of this file is testing the CLI shell tool. # -# $Id: shell1.test,v 1.7 2009/07/17 16:54:48 shaneh Exp $ # # Test plan: @@ -20,44 +19,19 @@ # shell1-2.*: Basic "dot" command token parsing. # shell1-3.*: Basic test that "dot" command can be called. # - -package require sqlite3 - -set CLI "./sqlite3" - -proc do_test {name cmd expected} { - puts -nonewline "$name ..." - set res [uplevel $cmd] - if {$res eq $expected} { - puts Ok - } else { - puts Error - puts " Got: $res" - puts " Expected: $expected" - exit - } +set testdir [file dirname $argv0] +source $testdir/tester.tcl +if {$tcl_platform(platform)=="windows"} { + set CLI "sqlite3.exe" +} else { + set CLI "./sqlite3" } - -proc execsql {sql} { - uplevel [list db eval $sql] +if {![file executable $CLI]} { + finish_test + return } - -proc catchsql {sql} { - set rc [catch {uplevel [list db eval $sql]} msg] - list $rc $msg -} - -proc catchcmd {db {cmd ""}} { - global CLI - set out [open cmds.txt w] - puts $out $cmd - close $out - set line "exec $CLI $db < cmds.txt" - set rc [catch { eval $line } msg] - list $rc $msg -} - -file delete -force test.db test.db.journal +db close +forcedelete test.db test.db-journal test.db-wal sqlite3 db test.db #---------------------------------------------------------------------------- @@ -717,4 +691,4 @@ do_test shell1-3-28.1 { ".log stdout\nSELECT coalesce(sqlite_log(123,'hello'),'456');" } "0 {(123) hello\n456}" -puts "CLI tests completed successfully" +finish_test diff --git a/tool/shell2.test b/test/shell2.test similarity index 86% rename from tool/shell2.test rename to test/shell2.test index b63fafc365..826093262e 100644 --- a/tool/shell2.test +++ b/test/shell2.test @@ -18,44 +18,19 @@ # # shell2-1.*: Misc. test of various tickets and reported errors. # - -package require sqlite3 - -set CLI "./sqlite3" - -proc do_test {name cmd expected} { - puts -nonewline "$name ..." - set res [uplevel $cmd] - if {$res eq $expected} { - puts Ok - } else { - puts Error - puts " Got: $res" - puts " Expected: $expected" - exit - } +set testdir [file dirname $argv0] +source $testdir/tester.tcl +if {$tcl_platform(platform)=="windows"} { + set CLI "sqlite3.exe" +} else { + set CLI "./sqlite3" } - -proc execsql {sql} { - uplevel [list db eval $sql] +if {![file executable $CLI]} { + finish_test + return } - -proc catchsql {sql} { - set rc [catch {uplevel [list db eval $sql]} msg] - list $rc $msg -} - -proc catchcmd {db {cmd ""}} { - global CLI - set out [open cmds.txt w] - puts $out $cmd - close $out - set line "exec $CLI $db < cmds.txt" - set rc [catch { eval $line } msg] - list $rc $msg -} - -file delete -force test.db test.db.journal +db close +forcedelete test.db test.db-journal test.db-wal sqlite3 db test.db @@ -219,4 +194,4 @@ b 1 2}} -puts "CLI tests completed successfully" +finish_test diff --git a/tool/shell3.test b/test/shell3.test similarity index 76% rename from tool/shell3.test rename to test/shell3.test index d37adff2d3..d02177b7f6 100644 --- a/tool/shell3.test +++ b/test/shell3.test @@ -19,47 +19,21 @@ # shell3-1.*: Basic tests for running SQL statments from command line. # shell3-2.*: Basic tests for running SQL file from command line. # - -package require sqlite3 - -set CLI "./sqlite3" - -proc do_test {name cmd expected} { - puts -nonewline "$name ..." - set res [uplevel $cmd] - if {$res eq $expected} { - puts Ok - } else { - puts Error - puts " Got: $res" - puts " Expected: $expected" - exit - } +set testdir [file dirname $argv0] +source $testdir/tester.tcl +if {$tcl_platform(platform)=="windows"} { + set CLI "sqlite3.exe" +} else { + set CLI "./sqlite3" } - -proc execsql {sql} { - uplevel [list db eval $sql] +if {![file executable $CLI]} { + finish_test + return } - -proc catchsql {sql} { - set rc [catch {uplevel [list db eval $sql]} msg] - list $rc $msg -} - -proc catchcmd {db {cmd ""}} { - global CLI - set out [open cmds.txt w] - puts $out $cmd - close $out - set line "exec $CLI $db < cmds.txt" - set rc [catch { eval $line } msg] - list $rc $msg -} - -file delete -force test.db test.db.journal +db close +forcedelete test.db test.db-journal test.db-wal sqlite3 db test.db - #---------------------------------------------------------------------------- # shell3-1.*: Basic tests for running SQL statments from command line. # @@ -120,5 +94,4 @@ do_test shell3-2.7 { catchcmd "foo.db" "CREATE TABLE" } {1 {Error: incomplete SQL: CREATE TABLE}} - -puts "CLI tests completed successfully" +finish_test diff --git a/tool/shell4.test b/test/shell4.test similarity index 83% rename from tool/shell4.test rename to test/shell4.test index 085c279bb3..5af44c8fd7 100644 --- a/tool/shell4.test +++ b/test/shell4.test @@ -19,33 +19,20 @@ # # shell4-1.*: Basic tests specific to the "stats" command. # - -set CLI "./sqlite3" - -proc do_test {name cmd expected} { - puts -nonewline "$name ..." - set res [uplevel $cmd] - if {$res eq $expected} { - puts Ok - } else { - puts Error - puts " Got: $res" - puts " Expected: $expected" - exit - } +set testdir [file dirname $argv0] +source $testdir/tester.tcl +if {$tcl_platform(platform)=="windows"} { + set CLI "sqlite3.exe" +} else { + set CLI "./sqlite3" } - -proc catchcmd {db {cmd ""}} { - global CLI - set out [open cmds.txt w] - puts $out $cmd - close $out - set line "exec $CLI $db < cmds.txt" - set rc [catch { eval $line } msg] - list $rc $msg +if {![file executable $CLI]} { + finish_test + return } - -file delete -force test.db test.db.journal +db close +forcedelete test.db test.db-journal test.db-wal +sqlite3 db test.db #---------------------------------------------------------------------------- # Test cases shell4-1.*: Tests specific to the "stats" command. @@ -126,4 +113,4 @@ SELECT 1; [regexp {Autoindex Inserts} $res] } {1 1 1} -puts "CLI tests completed successfully" +finish_test diff --git a/tool/shell5.test b/test/shell5.test similarity index 92% rename from tool/shell5.test rename to test/shell5.test index 828d71c97a..d90cedf949 100644 --- a/tool/shell5.test +++ b/test/shell5.test @@ -19,33 +19,20 @@ # # shell5-1.*: Basic tests specific to the ".import" command. # - -set CLI "./sqlite3" - -proc do_test {name cmd expected} { - puts -nonewline "$name ..." - set res [uplevel $cmd] - if {$res eq $expected} { - puts Ok - } else { - puts Error - puts " Got: $res" - puts " Expected: $expected" - exit - } +set testdir [file dirname $argv0] +source $testdir/tester.tcl +if {$tcl_platform(platform)=="windows"} { + set CLI "sqlite3.exe" +} else { + set CLI "./sqlite3" } - -proc catchcmd {db {cmd ""}} { - global CLI - set out [open cmds.txt w] - puts $out $cmd - close $out - set line "exec $CLI $db < cmds.txt" - set rc [catch { eval $line } msg] - list $rc $msg +if {![file executable $CLI]} { + finish_test + return } - -file delete -force test.db test.db.journal +db close +forcedelete test.db test.db-journal test.db-wal +sqlite3 db test.db #---------------------------------------------------------------------------- # Test cases shell5-1.*: Basic handling of the .import and .separator commands. @@ -239,5 +226,4 @@ do_test shell5-1.7.1 { SELECT COUNT(*) FROM t3;}] } [list 0 $rows] - -puts "CLI tests completed successfully" +finish_test diff --git a/test/tester.tcl b/test/tester.tcl index 5bc7eb704d..d1c4f0bd6c 100644 --- a/test/tester.tcl +++ b/test/tester.tcl @@ -518,6 +518,16 @@ proc do_test {name cmd expected} { flush stdout } +proc catchcmd {db {cmd ""}} { + global CLI + set out [open cmds.txt w] + puts $out $cmd + close $out + set line "exec $CLI $db < cmds.txt" + set rc [catch { eval $line } msg] + list $rc $msg +} + proc filepath_normalize {p} { # test cases should be written to assume "unix"-like file paths if {$::tcl_platform(platform)!="unix"} { From 5128e85cde8d9a347ed53b53b290c9b7a7fd7629 Mon Sep 17 00:00:00 2001 From: drh Date: Tue, 24 Apr 2012 13:14:49 +0000 Subject: [PATCH 05/10] New test cases for the ".dump" and ".mode insert" commands of the shell. FossilOrigin-Name: dfce8569765614462a3952d1761c10d579984665 --- manifest | 20 ++++++++++---------- manifest.uuid | 2 +- test/shell1.test | 29 +++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/manifest b/manifest index 1e7684a508..89e022de88 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Move\sthe\sshell\stest\sscripts\sinto\sthe\stest/\ssubfolder\sso\sthat\sthey\sare\nrun\sautomatically\sby\s"make\stest". -D 2012-04-24T12:46:05.097 +C New\stest\scases\sfor\sthe\s".dump"\sand\s".mode\sinsert"\scommands\sof\sthe\sshell. +D 2012-04-24T13:14:49.245 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 2f37e468503dbe79d35c9f6dffcf3fae1ae9ec20 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -689,11 +689,11 @@ F test/shared6.test 866bb4982c45ce216c61ded5e8fde4e7e2f3ffa9 F test/shared7.test 960760bc8d03e1419e70dea69cf41db62853616e F test/shared_err.test 91e26ec4f3fbe07951967955585137e2f18993de F test/sharedlock.test ffa0a3c4ac192145b310f1254f8afca4d553eabf -F test/shell1.test 6d5a331713a7eadde26802b6be7f6330a1064319 w tool/shell1.test -F test/shell2.test 037d6ad16e873354195d30bb2dc4b5321788154a w tool/shell2.test -F test/shell3.test 9196c42772d575685e722c92b4b39053c6ebba59 w tool/shell3.test -F test/shell4.test aa4eef8118b412d1a01477a53426ece169ea86a9 w tool/shell4.test -F test/shell5.test fa2188bbb13fe2d183fd04a5f7b512650c35ef5d w tool/shell5.test +F test/shell1.test 7dcd612b0018ddad783647d984fffa76791ffd3d +F test/shell2.test 037d6ad16e873354195d30bb2dc4b5321788154a +F test/shell3.test 9196c42772d575685e722c92b4b39053c6ebba59 +F test/shell4.test aa4eef8118b412d1a01477a53426ece169ea86a9 +F test/shell5.test fa2188bbb13fe2d183fd04a5f7b512650c35ef5d F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3 F test/shrink.test 8c70f62b6e8eb4d54533de6d65bd06b1b9a17868 F test/sidedelete.test f0ad71abe6233e3b153100f3b8d679b19a488329 @@ -995,7 +995,7 @@ F tool/tostr.awk e75472c2f98dd76e06b8c9c1367f4ab07e122d06 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f F tool/warnings-clang.sh a8a0a3babda96dfb1ff51adda3cbbf3dfb7266c2 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381 -P 282f2a74c23aa3fca6087bdeaf5d961b4f5bbe47 -R fb1a373c8aab089fc987aaa3b3a46480 +P 9fb7da6904e479f4671eeebf1a4b7e4e4e4f2b7b +R 6b8677ba1888a50ec99e6b37809051dc U drh -Z d17213a51e75d7b6538576f270927349 +Z 48923fe0d240285d5799cf9cb9177252 diff --git a/manifest.uuid b/manifest.uuid index 4c160508c7..83609d3c1d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -9fb7da6904e479f4671eeebf1a4b7e4e4e4f2b7b \ No newline at end of file +dfce8569765614462a3952d1761c10d579984665 \ No newline at end of file diff --git a/test/shell1.test b/test/shell1.test index 473d5a3845..a05b3dcab2 100644 --- a/test/shell1.test +++ b/test/shell1.test @@ -691,4 +691,33 @@ do_test shell1-3-28.1 { ".log stdout\nSELECT coalesce(sqlite_log(123,'hello'),'456');" } "0 {(123) hello\n456}" +# Test the output of the ".dump" command +# +do_test shell1-4.1 { + db eval { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES(null), (1), (2.25), ('hello'), (x'807f'); + } + catchcmd test.db {.dump} +} {0 {PRAGMA foreign_keys=OFF; +BEGIN TRANSACTION; +CREATE TABLE t1(x); +INSERT INTO "t1" VALUES(NULL); +INSERT INTO "t1" VALUES(1); +INSERT INTO "t1" VALUES(2.25); +INSERT INTO "t1" VALUES('hello'); +INSERT INTO "t1" VALUES(X'807F'); +COMMIT;}} + +# Test the output of ".mode insert" +# +do_test shell1-4.2 { + catchcmd test.db ".mode insert t1\nselect * from t1;" +} {0 {INSERT INTO t1 VALUES(NULL); +INSERT INTO t1 VALUES(1); +INSERT INTO t1 VALUES(2.25); +INSERT INTO t1 VALUES('hello'); +INSERT INTO t1 VALUES(X'807f');}} + + finish_test From 9250581af4c512da09bfe05dd127c444f9e33b97 Mon Sep 17 00:00:00 2001 From: drh Date: Thu, 26 Apr 2012 22:47:20 +0000 Subject: [PATCH 06/10] All virtual table constructors to be invoked recursively. A test case for this has been added to TH3. FossilOrigin-Name: 696a5a40bb28c4a54c9951f877b67015dc00bf55 --- manifest | 12 ++++++------ manifest.uuid | 2 +- src/vtab.c | 5 +++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/manifest b/manifest index 89e022de88..e43f7adf6d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C New\stest\scases\sfor\sthe\s".dump"\sand\s".mode\sinsert"\scommands\sof\sthe\sshell. -D 2012-04-24T13:14:49.245 +C All\svirtual\stable\sconstructors\sto\sbe\sinvoked\srecursively.\s\sA\stest\scase\sfor\nthis\shas\sbeen\sadded\sto\sTH3. +D 2012-04-26T22:47:20.079 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 2f37e468503dbe79d35c9f6dffcf3fae1ae9ec20 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -243,7 +243,7 @@ F src/vdbeblob.c 32f2a4899d67f69634ea4dd93e3f651936d732cb F src/vdbemem.c cb55e84b8e2c15704968ee05f0fae25883299b74 F src/vdbesort.c b25814d385895544ebc8118245c8311ded7f81c9 F src/vdbetrace.c d6e50e04e1ec498150e519058f617d91b8f5c843 -F src/vtab.c ab90fb600a3f5e4b7c48d22a4cdb2d6b23239847 +F src/vtab.c ae657b1c22cff43863458e768a44f915c07bc0e4 F src/wal.c 7bb3ad807afc7973406c805d5157ec7a2f65e146 F src/wal.h 29c197540b19044e6cd73487017e5e47a1d3dac6 F src/walker.c 3112bb3afe1d85dc52317cb1d752055e9a781f8f @@ -995,7 +995,7 @@ F tool/tostr.awk e75472c2f98dd76e06b8c9c1367f4ab07e122d06 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f F tool/warnings-clang.sh a8a0a3babda96dfb1ff51adda3cbbf3dfb7266c2 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381 -P 9fb7da6904e479f4671eeebf1a4b7e4e4e4f2b7b -R 6b8677ba1888a50ec99e6b37809051dc +P dfce8569765614462a3952d1761c10d579984665 +R bec439cef707f78d31474bb08e34e278 U drh -Z 48923fe0d240285d5799cf9cb9177252 +Z 9d4076ab5b78bad3c903da31eaeee8ce diff --git a/manifest.uuid b/manifest.uuid index 83609d3c1d..f648b7b37a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -dfce8569765614462a3952d1761c10d579984665 \ No newline at end of file +696a5a40bb28c4a54c9951f877b67015dc00bf55 \ No newline at end of file diff --git a/src/vtab.c b/src/vtab.c index c7221cd3b7..c561f7198f 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -447,7 +447,7 @@ static int vtabCallConstructor( int (*xConstruct)(sqlite3*,void*,int,const char*const*,sqlite3_vtab**,char**), char **pzErr ){ - VtabCtx sCtx; + VtabCtx sCtx, *pPriorCtx; VTable *pVTable; int rc; const char *const*azArg = (const char *const*)pTab->azModuleArg; @@ -472,9 +472,10 @@ static int vtabCallConstructor( assert( xConstruct ); sCtx.pTab = pTab; sCtx.pVTable = pVTable; + pPriorCtx = db->pVtabCtx; db->pVtabCtx = &sCtx; rc = xConstruct(db, pMod->pAux, nArg, azArg, &pVTable->pVtab, &zErr); - db->pVtabCtx = 0; + db->pVtabCtx = pPriorCtx; if( rc==SQLITE_NOMEM ) db->mallocFailed = 1; if( SQLITE_OK!=rc ){ From 3f17aefb353999cea242d938862fe35394268e3c Mon Sep 17 00:00:00 2001 From: drh Date: Fri, 27 Apr 2012 01:08:02 +0000 Subject: [PATCH 07/10] Enhance the do_test proc in the test suite so that if the expected result is of the form "/.../" or "~/.../" then regular expression matching is done between result and the "..." part of the expectation. In the ~/.../ case, we expect there to be no match. FossilOrigin-Name: c9a734406c016329e80d887f7438206e41c52ce7 --- manifest | 12 ++++++------ manifest.uuid | 2 +- test/tester.tcl | 22 +++++++++++++++++----- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/manifest b/manifest index e43f7adf6d..8bf337192d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C All\svirtual\stable\sconstructors\sto\sbe\sinvoked\srecursively.\s\sA\stest\scase\sfor\nthis\shas\sbeen\sadded\sto\sTH3. -D 2012-04-26T22:47:20.079 +C Enhance\sthe\sdo_test\sproc\sin\sthe\stest\ssuite\sso\sthat\sif\sthe\sexpected\sresult\nis\sof\sthe\sform\s"/.../"\sor\s"~/.../"\sthen\sregular\sexpression\smatching\sis\sdone\nbetween\sresult\sand\sthe\s"..."\spart\sof\sthe\sexpectation.\s\sIn\sthe\s~/.../\scase,\nwe\sexpect\sthere\sto\sbe\sno\smatch. +D 2012-04-27T01:08:02.413 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 2f37e468503dbe79d35c9f6dffcf3fae1ae9ec20 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -725,7 +725,7 @@ F test/tclsqlite.test 1597d353308531527583481d14d9da52ea8ed0af F test/tempdb.test 19d0f66e2e3eeffd68661a11c83ba5e6ace9128c F test/temptable.test 51edd31c65ed1560dd600b1796e8325df96318e2 F test/temptrigger.test 26670ed7a39cf2296a7f0a9e0a1d7bdb7abe936d -F test/tester.tcl dc0f9daa0a7c257df86a1a7603d5df2236e49145 +F test/tester.tcl 17b5f402d0e60e8c8ce751288b228fe9337f40c2 F test/thread001.test 7cc2ce08f9cde95964736d11e91f9ab610f82f91 F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -995,7 +995,7 @@ F tool/tostr.awk e75472c2f98dd76e06b8c9c1367f4ab07e122d06 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f F tool/warnings-clang.sh a8a0a3babda96dfb1ff51adda3cbbf3dfb7266c2 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381 -P dfce8569765614462a3952d1761c10d579984665 -R bec439cef707f78d31474bb08e34e278 +P 696a5a40bb28c4a54c9951f877b67015dc00bf55 +R cec8a879e3e3cce3faf837e46a46ad14 U drh -Z 9d4076ab5b78bad3c903da31eaeee8ce +Z 3ce2501ea7e4d58e039b2073955afa3e diff --git a/manifest.uuid b/manifest.uuid index f648b7b37a..8010df66ac 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -696a5a40bb28c4a54c9951f877b67015dc00bf55 \ No newline at end of file +c9a734406c016329e80d887f7438206e41c52ce7 \ No newline at end of file diff --git a/test/tester.tcl b/test/tester.tcl index d1c4f0bd6c..71100a2e6e 100644 --- a/test/tester.tcl +++ b/test/tester.tcl @@ -474,7 +474,6 @@ proc incr_ntest {} { # Invoke the do_test procedure to run a single test # proc do_test {name cmd expected} { - global argv cmdlinearg fix_testname name @@ -505,11 +504,24 @@ proc do_test {name cmd expected} { if {[catch {uplevel #0 "$cmd;\n"} result]} { puts "\nError: $result" fail_test $name - } elseif {[string compare $result $expected]} { - puts "\nExpected: \[$expected\]\n Got: \[$result\]" - fail_test $name } else { - puts " Ok" + if {[regexp {^~?/.*/$} $expected]} { + if {[string index $expected 0]=="~"} { + set re [string range $expected 2 end-1] + set ok [expr {![regexp $re $result]}] + } else { + set re [string range $expected 1 end-1] + set ok [regexp $re $result] + } + } else { + set ok [expr {[string compare $result $expected]==0}] + } + if {!$ok} { + puts "\nExpected: \[$expected\]\n Got: \[$result\]" + fail_test $name + } else { + puts " Ok" + } } } else { puts " Omitted" From 70331cd725e1accad75eac281689d3a8b6a9b700 Mon Sep 17 00:00:00 2001 From: drh Date: Fri, 27 Apr 2012 01:09:06 +0000 Subject: [PATCH 08/10] Enhance the processing of ORDER BY clauses on compound queries to better match terms of the order by against expressions in the result set, in order to enable better query optimization. FossilOrigin-Name: a49e909c8738317c8383ce93771c0a9c4cf270bc --- manifest | 14 +++++++------- manifest.uuid | 2 +- src/resolve.c | 7 ++++++- test/select9.test | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 9 deletions(-) diff --git a/manifest b/manifest index 8bf337192d..21aacd2021 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Enhance\sthe\sdo_test\sproc\sin\sthe\stest\ssuite\sso\sthat\sif\sthe\sexpected\sresult\nis\sof\sthe\sform\s"/.../"\sor\s"~/.../"\sthen\sregular\sexpression\smatching\sis\sdone\nbetween\sresult\sand\sthe\s"..."\spart\sof\sthe\sexpectation.\s\sIn\sthe\s~/.../\scase,\nwe\sexpect\sthere\sto\sbe\sno\smatch. -D 2012-04-27T01:08:02.413 +C Enhance\sthe\sprocessing\sof\sORDER\sBY\sclauses\son\scompound\squeries\sto\sbetter\nmatch\sterms\sof\sthe\sorder\sby\sagainst\sexpressions\sin\sthe\sresult\sset,\sin\sorder\nto\senable\sbetter\squery\soptimization. +D 2012-04-27T01:09:06.388 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 2f37e468503dbe79d35c9f6dffcf3fae1ae9ec20 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -171,7 +171,7 @@ F src/pragma.c e708b3bb5704605816f617e0b1d63a5488060715 F src/prepare.c ec4989f7f480544bdc4192fe663470d2a2d7d61e F src/printf.c 7ffb4ebb8b341f67e049695ba031da717b3d2699 F src/random.c cd4a67b3953b88019f8cd4ccd81394a8ddfaba50 -F src/resolve.c 969ec2bc52db1b068054ecf5ddc74f244102a71d +F src/resolve.c 748e75299faff345f34f0e5bd02a2bac8aa69fcd F src/rowset.c f6a49f3e9579428024662f6e2931832511f831a1 F src/select.c d7b9018b7dd2e821183d69477ab55c39b8272335 F src/shell.c 04399b2f9942bd02ed5ffee3b84bcdb39c52a1e6 @@ -676,7 +676,7 @@ F test/select5.test e758b8ef94f69b111df4cb819008856655dcd535 F test/select6.test cc25a8650cf9a4d4f74e586c45a75f9836516b18 F test/select7.test dad6f00f0d49728a879d6eb6451d4752db0b0abe F test/select8.test 391de11bdd52339c30580dabbbbe97e3e9a3c79d -F test/select9.test 74c0fb2c6eecb0219cbed0cbe3df136f8fbf9343 +F test/select9.test c0ca3cd87a8ebb04de2cb1402c77df55d911a0ea F test/selectA.test 06d1032fa9009314c95394f2ca2e60d9f7ae8532 F test/selectB.test 954e4e49cf1f896d61794e440669e03a27ceea25 F test/selectC.test 871fb55d884d3de5943c4057ebd22c2459e71977 @@ -995,7 +995,7 @@ F tool/tostr.awk e75472c2f98dd76e06b8c9c1367f4ab07e122d06 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f F tool/warnings-clang.sh a8a0a3babda96dfb1ff51adda3cbbf3dfb7266c2 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381 -P 696a5a40bb28c4a54c9951f877b67015dc00bf55 -R cec8a879e3e3cce3faf837e46a46ad14 +P c9a734406c016329e80d887f7438206e41c52ce7 +R 726c49ce225b59d324679d0a91302d23 U drh -Z 3ce2501ea7e4d58e039b2073955afa3e +Z fb90c7a1a92b2c585d3a7791f60ba9a6 diff --git a/manifest.uuid b/manifest.uuid index 8010df66ac..3897809680 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c9a734406c016329e80d887f7438206e41c52ce7 \ No newline at end of file +a49e909c8738317c8383ce93771c0a9c4cf270bc \ No newline at end of file diff --git a/src/resolve.c b/src/resolve.c index 6590cd8ac4..090fa79842 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -883,7 +883,7 @@ static int resolveOrderGroupBy( ExprList *pOrderBy, /* An ORDER BY or GROUP BY clause to resolve */ const char *zType /* Either "ORDER" or "GROUP", as appropriate */ ){ - int i; /* Loop counter */ + int i, j; /* Loop counters */ int iCol; /* Column number */ struct ExprList_item *pItem; /* A term of the ORDER BY clause */ Parse *pParse; /* Parsing context */ @@ -920,6 +920,11 @@ static int resolveOrderGroupBy( if( sqlite3ResolveExprNames(pNC, pE) ){ return 1; } + for(j=0; jpEList->nExpr; j++){ + if( sqlite3ExprCompare(pE, pSelect->pEList->a[j].pExpr)==0 ){ + pItem->iOrderByCol = j+1; + } + } } return sqlite3ResolveOrderGroupBy(pParse, pSelect, pOrderBy, zType); } diff --git a/test/select9.test b/test/select9.test index 085dee0bd1..9f54014cf9 100644 --- a/test/select9.test +++ b/test/select9.test @@ -415,5 +415,40 @@ do_test select9-4.X { } } {} +# Testing to make sure that queries involving a view of a compound select +# are planned efficiently. This detects a problem reported on the mailing +# list on 2012-04-26. See +# +# http://www.mail-archive.com/sqlite-users%40sqlite.org/msg69746.html +# +# For additional information. +# +do_test select9-5.1 { + db eval { + CREATE TABLE t51(x, y); + CREATE TABLE t52(x, y); + CREATE VIEW v5 as + SELECT x, y FROM t51 + UNION ALL + SELECT x, y FROM t52; + CREATE INDEX t51x ON t51(x); + CREATE INDEX t52x ON t52(x); + EXPLAIN QUERY PLAN + SELECT * FROM v5 WHERE x='12345' ORDER BY y; + } +} {~/SCAN TABLE/} ;# Uses indices with "*" +do_test select9-5.2 { + db eval { + EXPLAIN QUERY PLAN + SELECT x, y FROM v5 WHERE x='12345' ORDER BY y; + } +} {~/SCAN TABLE/} ;# Uses indices with "x, y" +do_test select9-5.3 { + db eval { + EXPLAIN QUERY PLAN + SELECT x, y FROM v5 WHERE +x='12345' ORDER BY y; + } +} {/SCAN TABLE/} ;# Full table scan if the "+x" prevents index usage. + finish_test From a4c5860e6fe8aede90f3b693d2955989fc6a97d1 Mon Sep 17 00:00:00 2001 From: drh Date: Fri, 27 Apr 2012 16:38:11 +0000 Subject: [PATCH 09/10] Fix a minor deviation from the coding style guidelines. FossilOrigin-Name: 1e51bffe777587cd05bd7db5e02d6291c3eb8c1a --- manifest | 12 ++++++------ manifest.uuid | 2 +- src/pager.c | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/manifest b/manifest index 21aacd2021..0d9e6e5a6e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Enhance\sthe\sprocessing\sof\sORDER\sBY\sclauses\son\scompound\squeries\sto\sbetter\nmatch\sterms\sof\sthe\sorder\sby\sagainst\sexpressions\sin\sthe\sresult\sset,\sin\sorder\nto\senable\sbetter\squery\soptimization. -D 2012-04-27T01:09:06.388 +C Fix\sa\sminor\sdeviation\sfrom\sthe\scoding\sstyle\sguidelines. +D 2012-04-27T16:38:11.705 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 2f37e468503dbe79d35c9f6dffcf3fae1ae9ec20 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -161,7 +161,7 @@ F src/os_common.h 92815ed65f805560b66166e3583470ff94478f04 F src/os_os2.c 4a75888ba3dfc820ad5e8177025972d74d7f2440 F src/os_unix.c 424d46e0edab969293c2223f09923b2178171f47 F src/os_win.c 5e9e933a412ab35de2a6506b3c6a8295b31b309e -F src/pager.c 85988507fa20acc60defb834722eddf4633e4aeb +F src/pager.c bb5635dde0b152797836d1c72275284724bb563c F src/pager.h ef1eaf8593e78f73885c1dfac27ad83bee23bdc5 F src/parse.y eb054bb40a5bf90d3422a01ed0e5df229461727a F src/pcache.c f8043b433a57aba85384a531e3937a804432a346 @@ -995,7 +995,7 @@ F tool/tostr.awk e75472c2f98dd76e06b8c9c1367f4ab07e122d06 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f F tool/warnings-clang.sh a8a0a3babda96dfb1ff51adda3cbbf3dfb7266c2 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381 -P c9a734406c016329e80d887f7438206e41c52ce7 -R 726c49ce225b59d324679d0a91302d23 +P a49e909c8738317c8383ce93771c0a9c4cf270bc +R b188dfa7c7a5aca6dba55435d29b09a3 U drh -Z fb90c7a1a92b2c585d3a7791f60ba9a6 +Z afa94c75b9ca22f35aabedc6458ef96f diff --git a/manifest.uuid b/manifest.uuid index 3897809680..e3f6e297e3 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -a49e909c8738317c8383ce93771c0a9c4cf270bc \ No newline at end of file +1e51bffe777587cd05bd7db5e02d6291c3eb8c1a \ No newline at end of file diff --git a/src/pager.c b/src/pager.c index 2d603122d5..b93e0aa866 100644 --- a/src/pager.c +++ b/src/pager.c @@ -3003,7 +3003,7 @@ static int pagerWalFrames( PgHdr *p; PgHdr **ppNext = &pList; nList = 0; - for(p=pList; (*ppNext = p); p=p->pDirty){ + for(p=pList; (*ppNext = p)!=0; p=p->pDirty){ if( p->pgno<=nTruncate ){ ppNext = &p->pDirty; nList++; From 7a9fc59efad39cc1780aa0ba6fb77414190c2ec3 Mon Sep 17 00:00:00 2001 From: dan Date: Tue, 1 May 2012 14:21:57 +0000 Subject: [PATCH 10/10] Update a test in io.test to account for [05f98d4eec]. FossilOrigin-Name: bfa61e781cb442be641486e7e55a1518e888d830 --- manifest | 14 +++++++------- manifest.uuid | 2 +- test/io.test | 6 +++++- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/manifest b/manifest index 0d9e6e5a6e..4f0a71baca 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sa\sminor\sdeviation\sfrom\sthe\scoding\sstyle\sguidelines. -D 2012-04-27T16:38:11.705 +C Update\sa\stest\sin\sio.test\sto\saccount\sfor\s[05f98d4eec]. +D 2012-05-01T14:21:57.706 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 2f37e468503dbe79d35c9f6dffcf3fae1ae9ec20 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -536,7 +536,7 @@ F test/insert5.test 394f96728d1258f406fe5f5aeb0aaf29487c39a6 F test/intarray.test 066b7d7ac38d25bf96f87f1b017bfc687551cdd4 F test/interrupt.test 42e7cf98646fd9cb4a3b131a93ed3c50b9e149f1 F test/intpkey.test 537669fd535f62632ca64828e435b9e54e8d677f -F test/io.test b278aa8fa609ed0dcc825df31b2d9f526c5a52bd +F test/io.test 36d251507d72e92b965fb2f0801c2f0b56335bcf F test/ioerr.test 40bb2cfcab63fb6aa7424cd97812a84bc16b5fb8 F test/ioerr2.test 9d71166f8466eda510f1af6137bdabaa82b5408d F test/ioerr3.test d3cec5e1a11ad6d27527d0d38573fbff14c71bdd @@ -995,7 +995,7 @@ F tool/tostr.awk e75472c2f98dd76e06b8c9c1367f4ab07e122d06 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f F tool/warnings-clang.sh a8a0a3babda96dfb1ff51adda3cbbf3dfb7266c2 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381 -P a49e909c8738317c8383ce93771c0a9c4cf270bc -R b188dfa7c7a5aca6dba55435d29b09a3 -U drh -Z afa94c75b9ca22f35aabedc6458ef96f +P 1e51bffe777587cd05bd7db5e02d6291c3eb8c1a +R 2c954175673c357079573f576ee6e960 +U dan +Z 199b9a45180c95acb6ae422881191984 diff --git a/manifest.uuid b/manifest.uuid index e3f6e297e3..8d74f647e3 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -1e51bffe777587cd05bd7db5e02d6291c3eb8c1a \ No newline at end of file +bfa61e781cb442be641486e7e55a1518e888d830 \ No newline at end of file diff --git a/test/io.test b/test/io.test index 58caeeebbc..9363b0c292 100644 --- a/test/io.test +++ b/test/io.test @@ -146,11 +146,15 @@ do_test io-2.2 { # written because page 1 - the change-counter page - is written using # an out-of-band method that bypasses the write counter. # +# UPDATE: As of [05f98d4eec] (adding SQLITE_DBSTATUS_CACHE_WRITE), the +# second write is also counted. So this now reports two writes and a +# single fsync. +# sqlite3_simulate_device -char atomic do_test io-2.3 { execsql { INSERT INTO abc VALUES(3, 4) } list [nWrite db] [nSync] -} {1 1} +} {2 1} # Test that the journal file is not created and the change-counter is # updated when the atomic-write optimization is used.